Compare commits

...

112 Commits

Author SHA1 Message Date
cinnaboot 669e903fa6 re-order linker flags in example Makefile 6 months ago
cinnaboot 4bd4442242 use the new TANGERINE_GL_DEBUG_QUIET in Makefile 2 years ago
cinnaboot ea075247f4 add TANGERINE_GL_DEBUG_QUIET environment option 2 years ago
cinnaboot 75f4ec43b8 include input.h in tangerine.h 2 years ago
cinnaboot 1723d04274 only use XYZW coordinates in glm library structures 4 years ago
cinnaboot ab46498957 properly initialize loop variable 4 years ago
cinnaboot ee8b64487b fix typo 4 years ago
cinnaboot b500a08945 add uniitialized variable warning to Makefile 4 years ago
cinnaboot daf2ced423 add a user_data parameter for render loop callback functions 4 years ago
cinnaboot 77fd7c6957 fix typo in getEntityByName 4 years ago
cinnaboot cfb661c6f7 add getEntityByName interface function 4 years ago
cinnaboot c3d585201d add interface functions meshInit(), modelInitManual() 4 years ago
cinnaboot 0821166a74 move draw_mode and usage from GLMesh to Entity 4 years ago
cinnaboot cecb7ebfab use sdl init flags in the correct call 4 years ago
cinnaboot 0f8f98718e add window_dims property to RenderState 4 years ago
cinnaboot 7ac0e7be52 add option to pass extra SDL flags to initRenderState 4 years ago
cinnaboot cfd087cc18 missed a namespace in initRenderState 4 years ago
cinnaboot 92313472f8 replace remaining 'glm::' types with using directives 4 years ago
cinnaboot 11fdcb892a add window title and dimension options to initRenderState 4 years ago
cinnaboot 7431b0af5f should be testing for 'RenderGroup' in example 4 years ago
cinnaboot 9506ce2fa6 update main TODO list 4 years ago
cinnaboot 18a44d0ead add more using directives for glm types 4 years ago
cinnaboot 3ee554287b update example application with camera changes 4 years ago
cinnaboot 774f04a0f7 add FIXME notes, and update global TODO list 4 years ago
cinnaboot fe7afa17c6 store Transform structure on Camera instead of RenderState 4 years ago
cinnaboot a087d95f4a use new initGLBackingBuffer helper for lights and transform UBOs 4 years ago
cinnaboot 238eddc18f add using directive for glm::vec4 4 years ago
cinnaboot b1be9e1451 add initGLBackingBuffer helper function 4 years ago
cinnaboot d304b21834 forgot to add glm::vec4 using directive 4 years ago
cinnaboot 3c61bf4ace add using directives for glm types 4 years ago
cinnaboot 59cc44cddb use utilCStrMatch to compare strings instead of strstr 4 years ago
cinnaboot a8799ed2f7 add extra null checsk and remove assert from utilCStrMatch() 4 years ago
cinnaboot 35e61250e9 re-add camera and input interfaces 4 years ago
cinnaboot 49dd1e47e4 fix .gitignore preventing looking in examples/data 4 years ago
cinnaboot 179ad5b075 update lfs entries 4 years ago
cinnaboot 2d4e84d74f setup working tree to merge shader_testing fixes 4 years ago
cinnaboot eae759a2f7 add more TODOs and FIXMEs 5 years ago
cinnaboot a5abe0064b re-order list of fixmes 5 years ago
cinnaboot ace5120306 load textures from gltf to asset system 5 years ago
cinnaboot ec1985873f add project description to the top of renderer.h 5 years ago
cinnaboot baf65e6dab reorganize asset.cpp 5 years ago
cinnaboot 4019d1cded remove some nullptr assertions 5 years ago
cinnaboot da2cd4f573 remove FIXME in render_object.cpp 5 years ago
cinnaboot f4c50602b4 clean up rgAppend() 5 years ago
cinnaboot 865804256c remove old entInitModel() referencing assimp meshes 5 years ago
cinnaboot c2a7a2e224 update debug shader 5 years ago
cinnaboot b8b923c5c1 update render_group_fix demo 5 years ago
cinnaboot 99d5b808df add parseNodeTransform() for tinygltf nodes 5 years ago
cinnaboot 3cdf2eae02 update git lfs and gitingore to stop tracking examples/data 5 years ago
cinnaboot 7f18d955d9 update git lfs to track entire example data dir 5 years ago
cinnaboot 21e92f8b92 fix default rendering, and add debug shader 5 years ago
cinnaboot e4a390773e add list of FIXMEs, remove safeRatio() 5 years ago
cinnaboot 004e0539cf restructure high-lvel interface for new asset system 5 years ago
cinnaboot a46e53b783 copy buffer logic for asset system 5 years ago
cinnaboot 86ac27a995 break out model init, add more tinygltf debug 5 years ago
cinnaboot c108a7735c add libs.cpp for header-only libs compilation unit 5 years ago
cinnaboot b10a4d97d9 add more tinygltf debugging 5 years ago
cinnaboot a1ef81d648 do memory arena init in renInit() 5 years ago
cinnaboot 9f4852cd69 use a complex model in render_group test 5 years ago
cinnaboot 5aaf90655c debug tinygltf structure 5 years ago
cinnaboot c9bd4807e5 add FNV hashing algorithm 5 years ago
cinnaboot 2f4fd301be testing tinygltf API 5 years ago
cinnaboot 2ebbf895ea replace assimp dependancy with TinyGLTF 5 years ago
cinnaboot 33cf7fdbed replace block nonsense with arena allocator 5 years ago
cinnaboot 43bdfeed0c get linear memory allocation working 5 years ago
cinnaboot ddb7622994 update makefiles to not always rebuild archive 5 years ago
cinnaboot 263b5a737b remove note about lighting bug 5 years ago
cinnaboot 2e8ab5c510 update README 5 years ago
cinnaboot 7c65c9dfd2 limit examples built while making memory changes 5 years ago
cinnaboot 036f66f0bb remove note about extension detection 5 years ago
cinnaboot 17807d6c5d update utilSafeFree function signature style 5 years ago
cinnaboot 6ba869d7d2 update icosphere blender file 5 years ago
cinnaboot 9c5799b3b5 move submesh info to new struct 5 years ago
cinnaboot 57749c7889 add FIXME for util_image->file_path 5 years ago
cinnaboot 41c37773a4 inital structure for linear asset memory allocation 5 years ago
cinnaboot d128181d6b add FIXME for mesh_group diffuse texture storage 5 years ago
cinnaboot af36fc9c82 add TODO for utilSafeFree() 5 years ago
cinnaboot 1e01e4d667 I don't think it's needed to multiply by normal length in shader here 5 years ago
cinnaboot 2f1bb4d47b add more entities to render_groups example 5 years ago
cinnaboot ff0a676349 add note about GL_DEBUG_OUTPUT detection 5 years ago
cinnaboot 33ae53ecb4 work on making multiple render_groups actually usable 5 years ago
cinnaboot db33593f17 add icospehere files to git LFS 5 years ago
cinnaboot 1933186d6c fix lighting bug in default shader 5 years ago
cinnaboot ef0eb6193b condense example Makefile 5 years ago
cinnaboot 3c19807b76 Merge branch 'master' of ssh://gitlab.com/cinnaboot/Tangerine 5 years ago
cinnaboot 10f72bec14 clean up example Makefile 5 years ago
cinnaboot 44acbc91cd add aspect ratio option to cameraInitPerspective() 5 years ago
cinnaboot 9e549fcb88 fix example makefile dependencies 5 years ago
cinnaboot ca151b5c77 add option to specify GL usage in initGLFloatBuffer() 5 years ago
cinnaboot d121c2472d add interface to update simple_mesh 5 years ago
cinnaboot ef11271049 break out SDL_PollEvents() while loop to new function 5 years ago
cinnaboot e412f21c7e add simple SDL input handling and callbacks 5 years ago
cinnaboot 6be56bc532 expose SDL_Handles struct 5 years ago
cinnaboot dd0659c029 fix make bug when not using internal assimp 5 years ago
cinnaboot 8ddd69a601 hopefully fix parallel Makefile dependencies 5 years ago
cinnaboot 0e17858999 fix typo in examples Makefile 5 years ago
cinnaboot b2fd07e148 also update examples makefile for internal config.h 5 years ago
cinnaboot 7ad31909e4 update Makefile for internal assimp config.h 5 years ago
cinnaboot ee7efd060e add assimp target as depndency for examples 5 years ago
cinnaboot 338addacca update Makefile and build instructions 5 years ago
cinnaboot 7676be82d4 add assimp target as optional dependancy of all 5 years ago
cinnaboot c246258bbd update README 5 years ago
cinnaboot 501cecaf28 add alternate library location for internal assimp 5 years ago
cinnaboot 9c72c84404 Merge branch 'master' of ssh://gitlab.com/cinnaboot/Tangerine 5 years ago
cinnaboot 522cb186e0 Merge branch 'master' of ssh://192.168.11.143:2223/home/gitweb/projects/Tangerine 5 years ago
cinnaboot 787234c969 add option to build a recent version of assimp 5 years ago
cinnaboot 3d947b1193 Add LICENSE 5 years ago
cinnaboot 9b1d73e24f add option to pass SDL init flags to renInint() 6 years ago
cinnaboot 23c1ac5c85 Merge branch 'master' of ssh://192.168.11.143:2223/home/gitweb/projects/Tangerine 6 years ago
cinnaboot 5de92d3f1e add option to pass SDL init flags to renInit() 6 years ago
cinnaboot a05a546193 add option to pass SDL init flags to renInint() 6 years ago
cinnaboot ea1c5f7f9a remove completed TODO: 6 years ago
  1. 5
      .gitattributes
  2. 2
      .gitignore
  3. 3
      .gitmodules
  4. 674
      LICENSE
  5. 52
      Makefile
  6. 45
      README
  7. 22
      README.txt
  8. 55
      examples/Makefile
  9. 72
      examples/assimp_loading/animation_testing.cpp
  10. 68
      examples/assimp_loading/main.cpp
  11. 0
      examples/data/Color Palette 140.png
  12. BIN
      examples/data/blender/spaceship.blend
  13. 11
      examples/data/colored_vertices.frag
  14. 22
      examples/data/colored_vertices.vert
  15. 11
      examples/data/debug.frag
  16. 26
      examples/data/debug.vert
  17. 67
      examples/data/full_lighting.frag
  18. 27
      examples/data/full_lighting.vert
  19. BIN
      examples/data/spaceship.bin
  20. BIN
      examples/data/spaceship.glb
  21. BIN
      examples/data/spaceship.gltf
  22. 21
      examples/data/texture_only.frag
  23. 22
      examples/data/texture_only.vert
  24. BIN
      examples/data/textured_cube.bin
  25. BIN
      examples/data/textured_cube.gltf
  26. 16
      examples/hello_world/main.cpp
  27. 344
      examples/main.cpp
  28. 72
      examples/simple_mesh/main.cpp
  29. 1
      ext/tinygltf
  30. 152
      include/GLDebug.h
  31. 18
      include/animation.h
  32. 76
      include/asset.h
  33. 77
      include/camera.h
  34. 9
      include/dumbLog.h
  35. 30
      include/dummy_shader.h
  36. 51
      include/entity.h
  37. 23
      include/input.h
  38. 34
      include/lights.h
  39. 65
      include/mesh.h
  40. 102
      include/platform_wait_for_vblank.h
  41. 33
      include/render_object.h
  42. 80
      include/renderer.h
  43. 214
      include/shader.h
  44. 57
      include/shader_program.h
  45. 296
      include/tangerine.h
  46. 28
      include/types.h
  47. 226
      include/util.h
  48. 32
      include/util_image.h
  49. 430
      src/asset.cpp
  50. 165
      src/camera.cpp
  51. 104
      src/default_shaders.cpp
  52. 34
      src/dumbLog.cpp
  53. 109
      src/entity.cpp
  54. 42
      src/input.cpp
  55. 55
      src/lights.cpp
  56. 234
      src/mesh.cpp
  57. 307
      src/render_object.cpp
  58. 302
      src/renderer.cpp
  59. 748
      src/shader.cpp
  60. 143
      src/shader_program.cpp
  61. 408
      src/tangerine.cpp
  62. 5
      src/tiny_gltf.cc
  63. 141
      src/util.cpp
  64. 60
      src/util_image.cpp

5
.gitattributes vendored

@ -1,3 +1,8 @@
*.png filter=lfs diff=lfs merge=lfs -text *.png filter=lfs diff=lfs merge=lfs -text
*.glb filter=lfs diff=lfs merge=lfs -text *.glb filter=lfs diff=lfs merge=lfs -text
*.blend 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

2
.gitignore vendored

@ -1,7 +1,9 @@
bin/ bin/
build/ build/
ext/assimp
doc/ doc/
msvc/.vs/ msvc/.vs/
msvc/x64/ msvc/x64/
tags tags
*.swp *.swp

3
.gitmodules vendored

@ -1,3 +1,6 @@
[submodule "ext/stb_libs"] [submodule "ext/stb_libs"]
path = ext/stb_libs path = ext/stb_libs
url = https://github.com/nothings/stb.git url = https://github.com/nothings/stb.git
[submodule "ext/tinygltf"]
path = ext/tinygltf
url = https://github.com/syoyo/tinygltf

674
LICENSE

@ -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>.

52
Makefile

@ -1,31 +1,49 @@
CXX = g++ SHELL := /bin/sh
CXXFLAGS = -std=c++11 -g -ggdb3 -Wall -Iinclude -I/usr/include/SDL2 -Iext/stb_libs CXX := g++
EXAMPLEDIR = examples # NOTE: have to set -O or -O1 here to get warnings for uninitialized variables
OBJDIR = build CXXFLAGS := -std=c++11 -g -ggdb3 -Wall -O -Wall -Wuninitialized \
SRCDIR = src -Iinclude \
LIBNAME = libTangerine.a -I/usr/include/SDL2 \
-Iext/stb_libs \
RENDER_SOURCES = $(wildcard $(SRCDIR)/*.cpp) -Iext/tinygltf
RENDER_OBJECTS = $(patsubst $(SRCDIR)/%.cpp, $(OBJDIR)/%.o, $(RENDER_SOURCES)) EXAMPLEDIR := examples
OBJDIR := build
SRCDIR := src
all: mkdirs $(LIBNAME) examples LIBNAME := libTangerine.a
# NOTE: optional library settings
CXXFLAGS += -DTANGERINE_GL_DEBUG_QUIET
SOURCES := $(wildcard $(SRCDIR)/*.cpp)
OBJECTS := $(patsubst $(SRCDIR)/%.cpp, $(OBJDIR)/%.o, $(SOURCES))
NDBG_SOURCES := $(wildcard $(SRCDIR)/*.cc)
NDBG_OBJS := $(patsubst $(SRCDIR)/%.cc, $(OBJDIR)/%.o, $(NDBG_SOURCES))
all: mkdirs $(LIBNAME)
$(MAKE) examples
.PHONY: all .PHONY: all
-include $(OBJDIR)/*.d -include $(OBJDIR)/*.d
mkdirs: mkdirs:
mkdir -p $(OBJDIR) @mkdir -p $(OBJDIR)
.PHONY: mkdirs .PHONY: mkdirs
$(LIBNAME): $(RENDER_OBJECTS) $(LIBNAME): $(OBJECTS) $(NDBG_OBJS)
ar -crs $(OBJDIR)/$(LIBNAME) $(RENDER_OBJECTS) ar -crsuU $(OBJDIR)/$(LIBNAME) $(OBJECTS) $(NDBG_OBJS)
$(RENDER_OBJECTS): $(OBJDIR)/%.o : $(SRCDIR)/%.cpp $(OBJECTS): $(OBJDIR)/%.o : $(SRCDIR)/%.cpp
$(CXX) $(CXXFLAGS) -c -MMD $< -o $@ $(CXX) $(CXXFLAGS) -c -MMD $< -o $@
examples: $(LIBNAME) # FIXME: re-using CXXFLAGS here defats the purpose of separating out NDBG_OJBS
# see shader_testing Makefile
$(NDBG_OBJS): $(OBJDIR)/%.o : $(SRCDIR)/%.cc
$(CXX) $(CXXFLAGS) -c $< -o $@
strip -d $@
examples:
$(MAKE) -C $(EXAMPLEDIR) $(MAKE) -C $(EXAMPLEDIR)
.PHONY: examples .PHONY: examples

45
README

@ -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

22
README.txt

@ -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

55
examples/Makefile

@ -1,36 +1,45 @@
SHELL = /bin/sh
CXX = g++ CXX = g++
CXXFLAGS = -std=c++11 -g -ggdb3 -Wall -I../include -I/usr/include/SDL2 CXXFLAGS = -std=c++11 -g -ggdb3 -Wall \
LDFLAGS = -lSDL2 -lGLEW -lGL -lassimp -I../include \
-I/usr/include/SDL2 \
-I../ext/tinygltf
LDFLAGS = -lSDL2 -lGLEW -lGL
OBJDIR = build OBJDIR = build
LIB = ../build/libTangerine.a LIB = ../build/libTangerine.a
BINDIR = bin BINDIR = bin
#EXAMPLE_SOURCES = \
all: mkdirs \ # render_groups/main.cpp \
$(OBJDIR)/hello_world.o \ # #assimp_loading/main.cpp
$(OBJDIR)/assimp_loading.o \ # #hello_world/main.cpp \
$(OBJDIR)/simple_mesh.o # #simple_mesh/main.cpp
.PHONY: all #EXAMPLE_OBJECTS = $(patsubst %/, $(OBJDIR)/%.o, $(dir $(EXAMPLE_SOURCES)))
#
-include $(OBJDIR)/*.d #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: mkdirs:
mkdir -p $(BINDIR) $(OBJDIR) @mkdir -p $(BINDIR) $(OBJDIR)
.PHONY: mkdirs .PHONY: mkdirs
$(OBJDIR)/hello_world.o: $(LIB)
$(CXX) $(CXXFLAGS) -c -MMD hello_world/main.cpp -o $@
$(CXX) -o $(BINDIR)/hello_world $(LDFLAGS) $@ $(LIB)
$(OBJDIR)/assimp_loading.o: $(LIB)
$(CXX) $(CXXFLAGS) -c -MMD assimp_loading/main.cpp -o $@
$(CXX) -o $(BINDIR)/assimp_loading $(LDFLAGS) $@ $(LIB)
$(OBJDIR)/simple_mesh.o: $(LIB)
$(CXX) $(CXXFLAGS) -c -MMD simple_mesh/main.cpp -o $@
$(CXX) -o $(BINDIR)/simple_mesh $(LDFLAGS) $@ $(LIB)
clean: clean:
rm -rf $(OBJDIR)/* bin/* rm -rf $(OBJDIR)/* bin/*
.PHONY: clean .PHONY: clean

72
examples/assimp_loading/animation_testing.cpp

@ -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";
}

68
examples/assimp_loading/main.cpp

@ -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;
}

0
examples/data/blender/Color Palette 140.png → examples/data/Color Palette 140.png

BIN
examples/data/blender/spaceship.blend (Stored with Git LFS)

Binary file not shown.

11
examples/data/colored_vertices.frag

@ -0,0 +1,11 @@
#version 330 core
in vec3 frag_color;
out vec4 color;
void main()
{
color = vec4(frag_color, 1);
}

22
examples/data/colored_vertices.vert

@ -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);
}

11
examples/data/debug.frag

@ -0,0 +1,11 @@
#version 330 core
in vec3 frag_normal;
out vec4 color;
void main()
{
color = vec4(frag_normal, 1);
}

26
examples/data/debug.vert

@ -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);
}

67
examples/data/full_lighting.frag

@ -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);
}

27
examples/data/full_lighting.vert

@ -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);
}

BIN
examples/data/spaceship.bin (Stored with Git LFS)

Binary file not shown.

BIN
examples/data/spaceship.glb (Stored with Git LFS)

Binary file not shown.

BIN
examples/data/spaceship.gltf (Stored with Git LFS)

Binary file not shown.

21
examples/data/texture_only.frag

@ -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);
}

22
examples/data/texture_only.vert

@ -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);
}

BIN
examples/data/textured_cube.bin (Stored with Git LFS)

Binary file not shown.

BIN
examples/data/textured_cube.gltf (Stored with Git LFS)

Binary file not shown.

16
examples/hello_world/main.cpp

@ -1,16 +0,0 @@
#include "renderer.h"
int
main()
{
render_state* rs = renInit("Hello World");
if (rs == nullptr)
return 1;
renDoRenderLoop(rs);
return 0;
}

344
examples/main.cpp

@ -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;
}

72
examples/simple_mesh/main.cpp

@ -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;
}

1
ext/tinygltf

@ -0,0 +1 @@
Subproject commit 19a41d20ec03d35cee68ff85511bc5875f6037b8

152
include/GLDebug.h

@ -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

18
include/animation.h

@ -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;
};

76
include/asset.h

@ -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);

77
include/camera.h

@ -1,45 +1,70 @@
#pragma once #pragma once
#define GLM_FORCE_XYZW_ONLY
#include <glm/glm.hpp> #include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include "util.h"
#include "types.h"
#include "shader.h"
struct Transforms
{
mat4 view;
mat4 projection;
mat4 normal;
};
struct camera struct Camera
{ {
float hAngle; float hAngle;
float vAngle; float vAngle;
glm::vec3 position; vec3 position;
glm::vec3 forward; vec3 forward;
glm::vec3 up; vec3 up;
glm::vec3 left; vec3 left;
glm::vec3 target; vec3 target;
glm::vec3 world_up; vec3 world_up;
glm::mat4 model;
glm::mat4 view;
glm::mat4 projection;
glm::mat4 MVP;
};
enum projection_type Transforms xforms;
{
PERSPECTIVE,
ORTHOGRAPHIC,
}; };
v2f cameraUnproject(camera& cam, int x, int y, int vp_width, int vp_height); // FIXME: we should keep to our convention of passing pointers to structs in
// these interface functions
vec2
cameraUnproject(Camera& cam, int x, int y, int vp_width, int vp_height);
vec3
cameraCreateRay(Camera& cam, ivec2 vp_coords, ivec2 vp_dims);
v3f cameraCreateRay(camera& cam, v2i vp_coords, v2i vp_dims); bool
cameraIntersectPlane(Camera& cam,
vec3 ray,
vec3 plane_origin,
vec3 plane_normal,
vec3& intersection);
bool cameraIntersectPlane(camera& cam, v3f ray, v3f plane_origin, v3f plane_normal, v3f& intersection); void
cameraInitPerspective(Camera* cam,
vec3 position,
vec3 target,
vec3 world_up,
float aspect_ratio = 16.f / 9.f);
void cameraInitPerspective(camera& cam, glm::vec3 position, glm::vec3 target, glm::vec3 world_up); void
cameraMove(Camera& cam,
bool up,
bool left,
bool down,
bool right,
bool forward,
bool backward);
void cameraMove(camera& cam, bool up, bool left, bool down, bool right, bool forward, bool backward); void
cameraRotate(Camera& cam, i32 xrel, i32 yrel);
void cameraRotate(camera& cam, int32 xrel, int32 yrel); void
cameraRoll(Camera& cam, bool CW, bool CCW);
void cameraRoll(camera& cam, bool CW, bool CCW);

9
include/dumbLog.h

@ -24,7 +24,14 @@ struct dumbLog
static dumbLog logger; static dumbLog logger;
#define LOG(level) *logger.OUT \ #define LOG(level) *logger.OUT \
<< std::put_time(logger.getCurrentTime(), "%F %T.") << logger.getCurrentMS() << " " \ << std::put_time(logger.getCurrentTime(), "%F %T.") \
<< logger.getCurrentMS() << " " \
<< "[" << logger.logLevelToString(level) << "] " \ << "[" << logger.logLevelToString(level) << "] " \
<< "(" << __FUNCTION__ << ") " << "(" << __FUNCTION__ << ") "
#include <cstdio>
#define LOGF(level, format_str, ...) \
dumbLogF(level, __FUNCTION__, format_str, ##__VA_ARGS__);
void dumbLogF(log_level l, const char* func, const char* fmt, ...);

30
include/dummy_shader.h

@ -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";

51
include/entity.h

@ -1,40 +1,37 @@
#pragma once #pragma once
#include <glm/glm.hpp> #include "asset.h"
#include "shader.h"
#include "mesh.h"
#include "render_object.h"
#include "types.h" #include "types.h"
#include "util.h"
// TODO: having a separate code path for each shader is annoying and error struct Entity
// 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
{ {
glm::mat4 world_transform; u32 num_meshes;
// TODO: hide simple_mesh/mesh_group pointer behind abstraction GLMesh* meshes;
simple_mesh* mesh; GLTexture* diffuse_texture; // NOTE: pointer into gl_ctx->textures array
mat4* model_xform;
// TODO: should be a pointer into a global array of mesh_info(s) or char* name;
// mesh_groups stored on the render_state object
mesh_group meshes; GLenum draw_mode; // NOTE: GL_LINES, GL_TRIANGLES (default)
render_objects* render_objs; 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);

23
include/input.h

@ -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);

34
include/lights.h

@ -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);

65
include/mesh.h

@ -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();

102
include/platform_wait_for_vblank.h

@ -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
}

33
include/render_object.h

@ -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);

80
include/renderer.h

@ -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);

214
include/shader.h

@ -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[]);

57
include/shader_program.h

@ -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);

296
include/tangerine.h

@ -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);

28
include/types.h

@ -1,16 +1,24 @@
#pragma once #pragma once
#include <cstdint>
#define GLM_FORCE_XYZW_ONLY
#include <glm/glm.hpp>
enum shader_t
{
SIMPLE_SHADER,
DEFAULT_SHADER
};
enum mesh_t typedef uint8_t u8;
{ typedef uint16_t u16;
SIMPLE_MESH, typedef uint32_t u32;
DEFAULT_MESHES 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;

226
include/util.h

@ -1,100 +1,194 @@
#pragma once #ifndef UTIL_H
#define UTIL_H
#include <cstdint>
#include <cassert> #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) {} size_t max_size;
v2f(real64 a, real64 b): x(a), y(b) {} size_t free_size;
real64 x; void* head;
real64 y; 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) {} unsigned char* s = (unsigned char *)str; // unsigned string
v2i() : x(0), y(0) {}
int32 x; // FNV-1a hash each octet of the string
int32 y; 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;
}
return hval;
}
struct v3f //-----------------
// Memory allocation
MemoryArena*
arenaInit(size_t initial_size)
{ {
v3f(): x(0), y(0), z(0) {} u32 sz = sizeof(MemoryArena);
v3f(real64 a, real64 b, real64 c): x(a), y(b), z(c) {} MemoryArena* arena =
real64 x; (MemoryArena*) std::calloc(initial_size + sz, sizeof(u8));
real64 y; arena->head = arena->next_free = (uint8_t*) arena + sz;
real64 z; arena->max_size = initial_size;
}; return arena;
}
inline real32 void
SafeRatio(real32 dividend, real32 divisor) arenaFree(MemoryArena*& arena)
{ {
if (divisor == 0) if (arena != nullptr) {
// TODO: log warning (don't require aixlog) std::free(arena);
return 0; arena = nullptr;
else }
return dividend / divisor;
} }
inline int32 uint
SafeTruncateToInt32(int64 val) arenaGetFreeSize(MemoryArena* arena)
{ {
assert(val <= INT32_MAX && val >= INT32_MIN); return (uint8_t*) arena->head
return (int32) val; + arena->max_size
- (uint8_t*) arena->next_free;
} }
inline void void*
utilConvertColor(GLfloat buf[3], uint32 color) arenaAllocateBlock(MemoryArena* arena, size_t block_size)
{ {
// NOTE: not using the alpha values // TODO: resizable memory arena
buf[0] = (GLfloat) ((color >> 24) & 0xFF) / (GLfloat) 255; assert(arenaGetFreeSize(arena) >= block_size);
buf[1] = (GLfloat) ((color >> 16) & 0xFF) / (GLfloat) 255; assert(block_size > 0);
buf[2] = (GLfloat) ((color >> 8) & 0xFF) / (GLfloat) 255; void* ret = arena->next_free;
arena->next_free = (uint8_t*) arena->next_free + block_size;
arena->free_size = arenaGetFreeSize(arena);
return ret;
} }
//----------------- void*
// C Strings arenaGetAddressOffset(void* address, u32 offset)
{
return (void*) ((u8*) address + offset);
}
// NOTE: max_len should be the allocated size of dest char*
bool utilCopyCStr(char* dest, const char* src, uint max_len); 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 void*
// NOTE: max_len should be the size of return buffer. utilAllocate(u32 count, u32 type_size)
char* utilConcatPath(char* out, const char* base_dir, const char* file_name, uint max_len); {
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 if (len > max_len) {
bool utilMatchPrefix(const char* lhs, const char* rhs, int sz); LOGF(Error, "%s , longer than %i\n", str, max_len);
return nullptr;
}
//----------------- char* out = (char*) std::calloc(len, sizeof(u8));
// Memory allocation strncpy(out, str, len - 1);
return out;
}
// NOTE: Wrapper for calloc that will send error message on out of memory void
#define UTIL_ALLOC(len, type) (type *) utilLogAlloc((len), sizeof(type), __FILE__, __LINE__) utilSafeFree(void* p)
void* utilLogAlloc(uint item_count, uint type_size, const char* file_name, const int line); {
if (p)
free(p);
else
LOGF(Error, "free called on nullptr\n");
}
void utilSafeFree(const void* mem); //---------------
// C string utils
//----------------- bool
// File I/O 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

32
include/util_image.h

@ -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);

430
src/asset.cpp

@ -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";
}
}
}

165
src/camera.cpp

@ -1,45 +1,58 @@
#include <cassert>
#include <GL/glew.h> #include <GL/glew.h>
#define GLM_FORCE_XYZW_ONLY
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include "camera.h" #include "camera.h"
// TODO: add these props to scene json // TODO: add these props to scene json
#define MOVE_SPEED 5.f #define MOVE_SPEED 5.f
#define ROTATE_SPEED 0.005f #define ROTATE_SPEED 0.005f
#define CAMERA_Z_CLAMP_ANGLE 85.f #define CAMERA_Z_CLAMP_ANGLE 85.f
#define FOV 60.f #define FOV 60.f
#define ASPECT_RATIO 16.f/9.f #define NEAR_CLIP_PLANE 5.f
#define NEAR_CLIP_PLANE 20.f
// forward declarations // forward declarations
inline glm::vec3 convertv3f(v3f v);
// interface // interface
void void
cameraInitPerspective(camera& cam, glm::vec3 position, glm::vec3 target, glm::vec3 world_up) cameraInitPerspective(Camera* cam,
vec3 position,
vec3 target,
vec3 world_up,
float aspect_ratio)
{ {
cam.position = position; assert(aspect_ratio > 0);
cam.target = target;
cam.world_up = world_up; cam->position = position;
cam.projection = glm::infinitePerspective(glm::radians(FOV), ASPECT_RATIO, NEAR_CLIP_PLANE); cam->target = target;
cam->world_up = world_up;
cam.forward = glm::normalize(target - position); cam->xforms.projection = glm::infinitePerspective(glm::radians(FOV),
cam.left = glm::normalize(glm::cross(cam.world_up, cam.forward)); aspect_ratio,
cam.up = glm::normalize(glm::cross(cam.forward, cam.left)); NEAR_CLIP_PLANE);
cam.hAngle = glm::atan(cam.forward.x, cam.forward.y); cam->forward = glm::normalize(target - position);
// NOTE: using pythagoras' to get absolute value of relative axis for vAngle component cam->left = glm::normalize(glm::cross(cam->world_up, cam->forward));
real32 len = glm::sqrt(glm::pow(cam.forward.y, 2) + glm::pow(cam.forward.x, 2)); cam->up = glm::normalize(glm::cross(cam->forward, cam->left));
cam.vAngle = glm::atan(cam.forward.z, len);
cam->hAngle = glm::atan(cam->forward.x, cam->forward.y);
cam.view = glm::lookAt(cam.position, cam.position + cam.forward, cam.up); // NOTE: get absolute value of relative axis for vAngle component
cam.model = glm::mat4(1.0f); float len = glm::sqrt(glm::pow(cam->forward.y, 2) +
cam.MVP = cam.projection * cam.view * cam.model; glm::pow(cam->forward.x, 2));
cam->vAngle = glm::atan(cam->forward.z, len);
cam->xforms.view=
glm::lookAt(cam->position, cam->position + cam->forward, cam->up);
} }
// TODO: re-add orthographic camera
void void
cameraInitOrthographic(/*camera& cam, */) cameraInitOrthographic(/*camera& cam, */)
{ {
@ -47,75 +60,87 @@ cameraInitOrthographic(/*camera& cam, */)
// left, right, bottom, top, zNear, zFar // left, right, bottom, top, zNear, zFar
cam.projection = glm::ortho(0.f, 1280.0f, 0.f, 720.0f, 0.1f, 100.0f); cam.projection = glm::ortho(0.f, 1280.0f, 0.f, 720.0f, 0.1f, 100.0f);
cam.view = glm::lookAt( cam.view = glm::lookAt(
glm::vec3(0.0f, 0.0f, 1.0f), // camera position vec3(0.0f, 0.0f, 1.0f), // camera position
glm::vec3(0.0f, 0.0f, 0.0f), // look at position vec3(0.0f, 0.0f, 0.0f), // look at position
glm::vec3(0,1,0) // "up" vector vec3(0,1,0) // "up" vector
); );
cam.model = glm::mat4(1.0f); cam.model = mat4(1.0f);
cam.MVP = cam.projection * cam.view * cam.model; cam.MVP = cam.projection * cam.view * cam.model;
#endif #endif
} }
v2f vec2
cameraUnproject(camera& cam, int x, int y, int vp_width, int vp_height) cameraUnproject(Camera& cam, int x, int y, int vp_width, int vp_height)
{ {
#if 0
// NOTE: using depth buffer may not be as accurate as doing ray-cast // NOTE: using depth buffer may not be as accurate as doing ray-cast
GLfloat depth; GLfloat depth;
glReadPixels(x, y, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &depth); glReadPixels(x, y, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &depth);
glm::vec4 viewport = glm::vec4(0, 0, vp_width, vp_height); vec4 viewport = vec4(0, 0, vp_width, vp_height);
glm::vec3 wincoord = glm::vec3(x, y, depth); vec3 wincoord = vec3(x, y, depth);
glm::vec3 vU = glm::unProject(wincoord, cam.view, cam.projection, viewport); vec3 vU = glm::unProject(wincoord, cam.view, cam.projection, viewport);
v2f v(vU.x, vU.y);
return v; return vec2(vU.x, vU.y);
#endif
return vec2();
} }
v3f vec3
cameraCreateRay(camera& cam, v2i vp_coords, v2i vp_dims) cameraCreateRay(Camera& cam, ivec2 vp_coords, ivec2 vp_dims)
{ {
#if 0
// NOTE: http://antongerdelan.net/opengl/raycasting.html // NOTE: http://antongerdelan.net/opengl/raycasting.html
float x = 2.f * vp_coords.x / vp_dims.x - 1.f; float x = 2.f * vp_coords.x / vp_dims.x - 1.f;
float y = 2.f * vp_coords.y / vp_dims.y - 1.f; float y = 2.f * vp_coords.y / vp_dims.y - 1.f;
glm::vec4 ray_clip = glm::vec4(x, y, -1.f, 1.f); vec4 ray_clip = vec4(x, y, -1.f, 1.f);
glm::vec4 ray_eye = glm::inverse(cam.projection) * ray_clip; vec4 ray_eye = glm::inverse(cam.projection) * ray_clip;
ray_eye = glm::vec4(ray_eye.x, ray_eye.y, -1.f, 0); // NOTE: reset as ray ray_eye = vec4(ray_eye.x, ray_eye.y, -1.f, 0); // NOTE: reset as ray
glm::vec4 ray_world = glm::normalize(glm::inverse(cam.view) * ray_eye); vec4 ray_world = glm::normalize(glm::inverse(cam.view) * ray_eye);
return v3f(ray_world.x, ray_world.y, ray_world.z); return vec3(ray_world.x, ray_world.y, ray_world.z);
#endif
return vec3();
} }
bool bool
cameraIntersectPlane(camera& cam, v3f ray, v3f plane_origin, v3f plane_normal, v3f& intersection) cameraIntersectPlane(Camera& cam,
vec3 ray,
vec3 plane_origin,
vec3 plane_normal,
vec3& intersection)
{ {
// NOTE: https://www.scratchapixel.com/lessons/3d-basic-rendering/minimal-ray-tracer-rendering-simple-shapes/ray-plane-and-ray-disk-intersection // NOTE: https://www.scratchapixel.com/lessons/3d-basic-rendering/minimal-ray-tracer-rendering-simple-shapes/ray-plane-and-ray-disk-intersection
glm::vec3 c_o = cam.position; float divisor = glm::dot(ray, plane_normal);
glm::vec3 r = convertv3f(ray);
glm::vec3 p_o = convertv3f(plane_origin);
glm::vec3 p_n = convertv3f(plane_normal);
float divisor = glm::dot(r, p_n);
if (divisor <= 0.000001f && divisor >= -0.000001f) // NOTE: ray and plane are co-planar if (divisor <= 0.000001f && divisor >= -0.000001f) // NOTE: ray and plane are co-planar
return false; return false;
float distance = glm::dot((p_o - c_o), p_n) / divisor; float distance =
glm::vec3 xsect = c_o + (r * distance); glm::dot((plane_origin - cam.position), plane_normal) / divisor;
intersection = v3f(xsect.x, xsect.y, xsect.z); vec3 xsect = cam.position + (ray * distance);
intersection = vec3(xsect.x, xsect.y, xsect.z);
return true; return true;
} }
void void
cameraMove(camera& cam, bool up, bool left, bool down, bool right, bool forward, bool backward) cameraMove(Camera& cam,
bool up,
bool left,
bool down,
bool right,
bool forward,
bool backward)
{ {
if (!up && !left && !down && !right && !forward && !backward) if (!up && !left && !down && !right && !forward && !backward)
return; return;
glm::vec3 f = cam.forward; vec3 f = cam.forward;
glm::vec3 u = cam.up; vec3 u = cam.up;
glm::vec3 old = cam.position; vec3 old = cam.position;
glm::vec3 &p = cam.position; vec3 &p = cam.position;
glm::vec3 v(0.f); // normalized direction vec3 v(0.f); // normalized direction
// TODO: still seems like we're adding magnitude when moving in 2 directions // TODO: still seems like we're adding magnitude when moving in 2 directions
#if 0 #if 0
@ -135,13 +160,14 @@ cameraMove(camera& cam, bool up, bool left, bool down, bool right, bool forward,
#endif #endif
p += (v * MOVE_SPEED); p += (v * MOVE_SPEED);
glm::vec3 diff = old - p; vec3 diff = old - p;
cam.view = glm::translate(cam.view, diff); cam.xforms.view = glm::translate(cam.xforms.view, diff);
cam.MVP = cam.projection * cam.view * cam.model; //cam.view = glm::translate(cam.view, diff);
//cam.MVP = cam.projection * cam.view * cam.model;
} }
void void
cameraRotate(camera& cam, int32 xrel, int32 yrel) cameraRotate(Camera& cam, i32 xrel, i32 yrel)
{ {
float &h = cam.hAngle; float &h = cam.hAngle;
float &v = cam.vAngle; float &v = cam.vAngle;
@ -153,7 +179,7 @@ cameraRotate(camera& cam, int32 xrel, int32 yrel)
if (v < (-1 * a)) v = (-1 * a); if (v < (-1 * a)) v = (-1 * a);
if (v > a) v = a; if (v > a) v = a;
cam.forward = glm::vec3( cam.forward = vec3(
glm::cos(v) * glm::sin(h), glm::cos(v) * glm::sin(h),
glm::cos(v) * glm::cos(h), glm::cos(v) * glm::cos(h),
glm::sin(v) glm::sin(v)
@ -163,31 +189,30 @@ cameraRotate(camera& cam, int32 xrel, int32 yrel)
cam.left = glm::normalize(glm::cross(cam.forward, cam.world_up)); cam.left = glm::normalize(glm::cross(cam.forward, cam.world_up));
cam.up = glm::normalize(glm::cross(cam.left, cam.forward)); cam.up = glm::normalize(glm::cross(cam.left, cam.forward));
cam.view = glm::lookAt(cam.position, cam.position + cam.forward, cam.up); cam.xforms.view = glm::lookAt(cam.position,
cam.MVP = cam.projection * cam.view * cam.model; cam.position + cam.forward,
cam.up);
//cam.MVP = cam.projection * cam.view * cam.model;
} }
void void
cameraRoll(camera& cam, bool CW, bool CCW) cameraRoll(Camera& cam, bool CW, bool CCW)
{ {
#if 0
if ((!CW && !CCW) || (CW && CCW)) if ((!CW && !CCW) || (CW && CCW))
return; return;
float a = 0.005f; float a = 0.005f;
if (CW) a *= 1; if (CW) a *= 1;
if (CCW) a *= -1; if (CCW) a *= -1;
glm::mat4 m = glm::rotate(glm::mat4(1.f), a, cam.forward); mat4 m = glm::rotate(mat4(1.f), a, cam.forward);
glm::vec4 v(cam.up.x, cam.up.y, cam.up.z, 0); vec4 v(cam.up.x, cam.up.y, cam.up.z, 0);
v = v * m; v = v * m;
cam.up = glm::vec3(v.x, v.y, v.z); cam.up = vec3(v.x, v.y, v.z);
cam.view *= m; cam.view *= m;
cam.MVP = cam.projection * cam.view * cam.model; cam.MVP = cam.projection * cam.view * cam.model;
#endif
} }
// internal // internal
inline glm::vec3
convertv3f(v3f v)
{
return glm::vec3(v.x, v.y, v.z);
}

104
src/default_shaders.cpp

@ -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";

34
src/dumbLog.cpp

@ -3,6 +3,8 @@
#include <chrono> #include <chrono>
#include "dumbLog.h" #include "dumbLog.h"
#include "types.h"
void dumbLog::setOutputStream(std::ostream* out) void dumbLog::setOutputStream(std::ostream* out)
{ {
@ -16,6 +18,7 @@ dumbLog::logLevelToString(log_level level)
case log_level::Error: return "Error"; case log_level::Error: return "Error";
case log_level::Warning: return "Warning"; case log_level::Warning: return "Warning";
case log_level::Info: return "Info"; case log_level::Info: return "Info";
case log_level::Debug: return "Debug";
default: return "Potato"; default: return "Potato";
} }
}; };
@ -32,7 +35,36 @@ int
dumbLog::getCurrentMS() dumbLog::getCurrentMS()
{ {
auto now = std::chrono::system_clock::now(); auto now = std::chrono::system_clock::now();
long long total_ms = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()).count(); u64 total_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
now.time_since_epoch()
).count();
return int(total_ms % 1000); return int(total_ms % 1000);
} }
#include <cstdarg>
#include <ctime>
void
dumbLogF(log_level l, const char* func, const char* fmt, ...)
{
const char* level = logger.logLevelToString(l);
char time_str[100];
timespec ts;
timespec_get(&ts, TIME_UTC);
i64 ms = ts.tv_nsec / 1000000;
if (strftime(time_str, sizeof(time_str), "%T", localtime(&ts.tv_sec))) {
// NOTE: print prefix, "H:M:S.ms, log_level, function(), "
printf("%s.%03ld [%s] %s(), ", time_str, ms, level, func);
// NOTE: append user args
va_list args;
va_start(args, fmt);
vprintf(fmt, args);
va_end(args);
} else {
printf("%s(), error getting time\n", __FUNCTION__);
}
}

109
src/entity.cpp

@ -1,95 +1,74 @@
#include <cassert> #define GLM_FORCE_XYZW_ONLY
#include <glm/gtc/matrix_transform.hpp> #include <glm/gtc/matrix_transform.hpp>
#include "dumbLog.h" #include "dumbLog.h"
#include "entity.h" #include "entity.h"
#include "util.h"
// forward declarations
void initDefaults(entity& e);
// interface
bool bool
entInitModel(entity& e, const char* model_path) initEntity(Entity* e,
{ GLContext* gl_ctx,
initDefaults(e); MemoryArena* arena,
Model* mdl,
if (meLoadFromFile(e.meshes, model_path)) u32 num_attrib_mappings,
GLBufferToAttribMapping* attrib_mappings,
const char* name,
GLenum draw_mode,
GLenum usage)
{ {
e.render_objs = roInitModel(e.meshes); e->num_meshes = mdl->num_meshes;
e->meshes = ARENA_ALLOC(arena, GLMesh, e->num_meshes);
if (e.render_objs != nullptr) e->model_xform = ARENA_ALLOC(arena, mat4, 1);
return true; *e->model_xform = mat4(1.f);
} e->name = arenaCopyCStr(arena, name);
e->draw_mode = draw_mode;
entFree(e); e->usage = usage;
if (mdl->diffuse_texture) {
e->diffuse_texture = getGLTexture(gl_ctx, mdl->diffuse_texture);
if (!e->diffuse_texture)
return false; return false;
} }
bool for (u32 i = 0; i< e->num_meshes; i++) {
entInitMesh(entity& e, simple_mesh* mesh, GLenum draw_mode) GLMesh* glm = &e->meshes[i];
{ *glm = loadGLMesh(arena,
initDefaults(e); mdl->meshes[i],
e.mesh = mesh; draw_mode,
e.render_objs = roInitSimpleMesh(*e.mesh, draw_mode); usage,
e->diffuse_texture,
if (e.render_objs == nullptr) num_attrib_mappings,
attrib_mappings);
if (glm->vao_id == 0) {
LOGF(Error, "error initializing entity\n");
return false; 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 return true;
entTranslate(entity& e, glm::vec3 v)
{
e.world_transform = glm::translate(e.world_transform, v);
} }
void 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 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 void
initDefaults(entity& e) scaleEntity(Entity* e, float scale)
{ {
e.world_transform = glm::mat4(1.0); *e->model_xform =
entScale(e, glm::vec3(1.0)); glm::scale(*e->model_xform, vec3(scale, scale, scale));
entSetWorldPosition(e, glm::vec3(0, 0, 0));
} }

42
src/input.cpp

@ -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);
}

55
src/lights.cpp

@ -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;
}

234
src/mesh.cpp

@ -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;
}

307
src/render_object.cpp

@ -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]);
}

302
src/renderer.cpp

@ -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;
}

748
src/shader.cpp

@ -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;
}

143
src/shader_program.cpp

@ -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;
}

408
src/tangerine.cpp

@ -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;
}

5
src/tiny_gltf.cc

@ -0,0 +1,5 @@
#define STB_IMAGE_IMPLEMENTATION
#define STB_IMAGE_WRITE_IMPLEMENTATION
#define TINYGLTF_IMPLEMENTATION
#include "tiny_gltf.h"

141
src/util.cpp

@ -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;
}

60
src/util_image.cpp

@ -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…
Cancel
Save