Browse Source

setup working tree to merge shader_testing fixes

remotes/bxxa/master
cinnaboot 4 years ago
parent
commit
2d4e84d74f
  1. 18
      Makefile
  2. 42
      examples/Makefile
  3. 72
      examples/assimp_loading/animation_testing.cpp
  4. 85
      examples/assimp_loading/main.cpp
  5. BIN
      examples/data/blender/Color Palette 140.png
  6. BIN
      examples/data/blender/icosphere.bin
  7. BIN
      examples/data/blender/icosphere.blend
  8. 133
      examples/data/blender/icosphere.gltf
  9. BIN
      examples/data/blender/spaceship.bin
  10. BIN
      examples/data/blender/spaceship.blend
  11. 453
      examples/data/blender/spaceship.gltf
  12. BIN
      examples/data/icosphere.glb
  13. BIN
      examples/data/spaceship.glb
  14. 16
      examples/hello_world/main.cpp
  15. 331
      examples/main.cpp
  16. 233
      examples/render_groups/main.cpp
  17. 89
      examples/simple_mesh/main.cpp
  18. 147
      include/GLDebug.h
  19. 18
      include/animation.h
  20. 84
      include/asset.h
  21. 66
      include/camera.h
  22. 9
      include/dumbLog.h
  23. 30
      include/dummy_shader.h
  24. 34
      include/entity.h
  25. 23
      include/input.h
  26. 34
      include/lights.h
  27. 64
      include/mesh.h
  28. 102
      include/platform_wait_for_vblank.h
  29. 27
      include/render_object.h
  30. 132
      include/renderer.h
  31. 222
      include/shader.h
  32. 57
      include/shader_program.h
  33. 279
      include/tangerine.h
  34. 17
      include/types.h
  35. 237
      include/util.h
  36. 35
      include/util_image.h
  37. 193
      src/asset.cpp
  38. 204
      src/camera.cpp
  39. 162
      src/default_shaders.cpp
  40. 34
      src/dumbLog.cpp
  41. 90
      src/entity.cpp
  42. 42
      src/input.cpp
  43. 12
      src/libs.cpp
  44. 55
      src/lights.cpp
  45. 262
      src/mesh.cpp
  46. 246
      src/render_object.cpp
  47. 369
      src/renderer.cpp
  48. 751
      src/shader.cpp
  49. 146
      src/shader_program.cpp
  50. 386
      src/tangerine.cpp
  51. 5
      src/tiny_gltf.cc
  52. 197
      src/util.cpp
  53. 59
      src/util_image.cpp

18
Makefile

@ -11,8 +11,10 @@ OBJDIR = build
SRCDIR = src
LIBNAME = libTangerine.a
RENDER_SOURCES = $(wildcard $(SRCDIR)/*.cpp)
RENDER_OBJECTS = $(patsubst $(SRCDIR)/%.cpp, $(OBJDIR)/%.o, $(RENDER_SOURCES))
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)
@ -25,12 +27,18 @@ mkdirs:
@mkdir -p $(OBJDIR)
.PHONY: mkdirs
$(LIBNAME): $(RENDER_OBJECTS)
ar -crsuU $(OBJDIR)/$(LIBNAME) $(RENDER_OBJECTS)
$(LIBNAME): $(OBJECTS) $(NDBG_OBJS)
ar -crsuU $(OBJDIR)/$(LIBNAME) $(OBJECTS) $(NDBG_OBJS)
$(RENDER_OBJECTS): $(OBJDIR)/%.o : $(SRCDIR)/%.cpp
$(OBJECTS): $(OBJDIR)/%.o : $(SRCDIR)/%.cpp
$(CXX) $(CXXFLAGS) -c -MMD $< -o $@
# 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)
.PHONY: examples

42
examples/Makefile

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

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

85
examples/assimp_loading/main.cpp

@ -1,85 +0,0 @@
#include <SDL2/SDL.h>
#include <glm/glm.hpp>
#include "camera.h"
#include "dumbLog.h"
#include "input.h"
#include "entity.h"
#include "renderer.h"
#include "shader_program.h"
void
doFrameCallback(render_state* rs)
{
static input_state is = {};
inputProcessEvents(&is);
if (is.window_closed || is.escape) {
rs->running = false;
return;
}
int rotate_mod = 0;
if (is.left)
rotate_mod = -1;
if (is.right)
rotate_mod = 1;
// 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 * rotate_mod, axis);
}
// TODO: remove/refactor this when we get animation working
#include "animation_testing.cpp"
int
main()
{
render_state* rs = renInit("assimp loading");
rs->render_groups = UTIL_ALLOC(256, render_group*);
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[0] = renAllocateGroup(1, sw);
rs->render_group_count = 1;
entity& spaceship = rs->render_groups[0]->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;
}

BIN
examples/data/blender/Color Palette 140.png (Stored with Git LFS)

Binary file not shown.

BIN
examples/data/blender/icosphere.bin

Binary file not shown.

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

Binary file not shown.

133
examples/data/blender/icosphere.gltf

@ -1,133 +0,0 @@
{
"asset" : {
"generator" : "Khronos glTF Blender I/O v1.6.16",
"version" : "2.0"
},
"scene" : 0,
"scenes" : [
{
"name" : "Scene",
"nodes" : [
0
]
}
],
"nodes" : [
{
"mesh" : 0,
"name" : "Icosphere"
}
],
"materials" : [
{
"doubleSided" : true,
"name" : "Material.001",
"pbrMetallicRoughness" : {
"baseColorTexture" : {
"index" : 0
},
"metallicFactor" : 0,
"roughnessFactor" : 0.5
}
}
],
"meshes" : [
{
"name" : "Icosphere",
"primitives" : [
{
"attributes" : {
"POSITION" : 0,
"NORMAL" : 1,
"TEXCOORD_0" : 2
},
"indices" : 3,
"material" : 0
}
]
}
],
"textures" : [
{
"sampler" : 0,
"source" : 0
}
],
"images" : [
{
"mimeType" : "image/png",
"name" : "Color Palette 140",
"uri" : "Color%20Palette%20140.png"
}
],
"accessors" : [
{
"bufferView" : 0,
"componentType" : 5126,
"count" : 960,
"max" : [
1,
1,
0.9999999403953552
],
"min" : [
-0.9999999403953552,
-1,
-0.9999999403953552
],
"type" : "VEC3"
},
{
"bufferView" : 1,
"componentType" : 5126,
"count" : 960,
"type" : "VEC3"
},
{
"bufferView" : 2,
"componentType" : 5126,
"count" : 960,
"type" : "VEC2"
},
{
"bufferView" : 3,
"componentType" : 5123,
"count" : 960,
"type" : "SCALAR"
}
],
"bufferViews" : [
{
"buffer" : 0,
"byteLength" : 11520,
"byteOffset" : 0
},
{
"buffer" : 0,
"byteLength" : 11520,
"byteOffset" : 11520
},
{
"buffer" : 0,
"byteLength" : 7680,
"byteOffset" : 23040
},
{
"buffer" : 0,
"byteLength" : 1920,
"byteOffset" : 30720
}
],
"samplers" : [
{
"magFilter" : 9729,
"minFilter" : 9987
}
],
"buffers" : [
{
"byteLength" : 32640,
"uri" : "icosphere.bin"
}
]
}

BIN
examples/data/blender/spaceship.bin

Binary file not shown.

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

Binary file not shown.

453
examples/data/blender/spaceship.gltf

@ -1,453 +0,0 @@
{
"asset" : {
"generator" : "Khronos glTF Blender I/O v1.6.16",
"version" : "2.0"
},
"scene" : 0,
"scenes" : [
{
"name" : "Scene",
"nodes" : [
4
]
}
],
"nodes" : [
{
"mesh" : 0,
"name" : "Spaceship_Rear_Hatch",
"rotation" : [
2.8049830902432404e-08,
8.798715533941959e-09,
0.7938249707221985,
0.6081464290618896
],
"scale" : [
0.17620019614696503,
0.1893281489610672,
0.1893281191587448
],
"translation" : [
0.9305203557014465,
-0.8287604479340512,
-1.1537752442336568e-07
]
},
{
"children" : [
0
],
"name" : "Bone.001",
"rotation" : [
-1.9689133878841858e-08,
-6.542580166524203e-08,
-0.12394285947084427,
0.9922893643379211
],
"scale" : [
1,
1.0000001192092896,
1
],
"translation" : [
9.194034422677078e-17,
1.0409293174743652,
5.551115123125783e-17
]
},
{
"mesh" : 1,
"name" : "Spaceship",
"rotation" : [
-3.244472424057676e-08,
-1.6142790215667446e-08,
0.7123286724090576,
0.7018461227416992
],
"scale" : [
0.17620022594928741,
0.1893281489610672,
0.1893281191587448
],
"translation" : [
0.6980774998664856,
0.008746981620788574,
2.8927825468372248e-08
]
},
{
"children" : [
1,
2
],
"name" : "Bone",
"rotation" : [
1.2074783839466363e-08,
2.1153852003408247e-08,
-0.007412227801978588,
0.9999725222587585
],
"scale" : [
0.9999998807907104,
1,
1
]
},
{
"children" : [
3
],
"name" : "Armature",
"rotation" : [
0,
0,
-0.7071067690849304,
0.7071068286895752
]
}
],
"animations" : [
{
"channels" : [
{
"sampler" : 0,
"target" : {
"node" : 3,
"path" : "translation"
}
},
{
"sampler" : 1,
"target" : {
"node" : 3,
"path" : "rotation"
}
},
{
"sampler" : 2,
"target" : {
"node" : 3,
"path" : "scale"
}
},
{
"sampler" : 3,
"target" : {
"node" : 1,
"path" : "translation"
}
},
{
"sampler" : 4,
"target" : {
"node" : 1,
"path" : "rotation"
}
},
{
"sampler" : 5,
"target" : {
"node" : 1,
"path" : "scale"
}
}
],
"name" : "ArmatureAction.001",
"samplers" : [
{
"input" : 8,
"interpolation" : "LINEAR",
"output" : 9
},
{
"input" : 8,
"interpolation" : "LINEAR",
"output" : 10
},
{
"input" : 8,
"interpolation" : "LINEAR",
"output" : 11
},
{
"input" : 8,
"interpolation" : "LINEAR",
"output" : 12
},
{
"input" : 8,
"interpolation" : "LINEAR",
"output" : 13
},
{
"input" : 8,
"interpolation" : "LINEAR",
"output" : 14
}
]
}
],
"materials" : [
{
"doubleSided" : true,
"name" : "Material.001",
"pbrMetallicRoughness" : {
"baseColorTexture" : {
"index" : 0
},
"metallicFactor" : 0,
"roughnessFactor" : 0.5
}
}
],
"meshes" : [
{
"name" : "Cube.001",
"primitives" : [
{
"attributes" : {
"POSITION" : 0,
"NORMAL" : 1,
"TEXCOORD_0" : 2
},
"indices" : 3,
"material" : 0
}
]
},
{
"name" : "Cube.002",
"primitives" : [
{
"attributes" : {
"POSITION" : 4,
"NORMAL" : 5,
"TEXCOORD_0" : 6
},
"indices" : 7,
"material" : 0
}
]
}
],
"textures" : [
{
"sampler" : 0,
"source" : 0
}
],
"images" : [
{
"mimeType" : "image/png",
"name" : "Color Palette 140",
"uri" : "Color%20Palette%20140.png"
}
],
"accessors" : [
{
"bufferView" : 0,
"componentType" : 5126,
"count" : 56,
"max" : [
8.864760398864746,
3.6763174533843994,
2.2041707038879395
],
"min" : [
5.919982433319092,
2.8251190185546875,
-2.2041707038879395
],
"type" : "VEC3"
},
{
"bufferView" : 1,
"componentType" : 5126,
"count" : 56,
"type" : "VEC3"
},
{
"bufferView" : 2,
"componentType" : 5126,
"count" : 56,
"type" : "VEC2"
},
{
"bufferView" : 3,
"componentType" : 5123,
"count" : 90,
"type" : "SCALAR"
},
{
"bufferView" : 4,
"componentType" : 5126,
"count" : 1487,
"max" : [
9.09161376953125,
4.241826057434082,
5.432835578918457
],
"min" : [
-9.08266544342041,
0.25478607416152954,
-5.432835578918457
],
"type" : "VEC3"
},
{
"bufferView" : 5,
"componentType" : 5126,
"count" : 1487,
"type" : "VEC3"
},
{
"bufferView" : 6,
"componentType" : 5126,
"count" : 1487,
"type" : "VEC2"
},
{
"bufferView" : 7,
"componentType" : 5123,
"count" : 2334,
"type" : "SCALAR"
},
{
"bufferView" : 8,
"componentType" : 5126,
"count" : 110,
"max" : [
4.583333333333333
],
"min" : [
0.041666666666666664
],
"type" : "SCALAR"
},
{
"bufferView" : 9,
"componentType" : 5126,
"count" : 110,
"type" : "VEC3"
},
{
"bufferView" : 10,
"componentType" : 5126,
"count" : 110,
"type" : "VEC4"
},
{
"bufferView" : 11,
"componentType" : 5126,
"count" : 110,
"type" : "VEC3"
},
{
"bufferView" : 12,
"componentType" : 5126,
"count" : 110,
"type" : "VEC3"
},
{
"bufferView" : 13,
"componentType" : 5126,
"count" : 110,
"type" : "VEC4"
},
{
"bufferView" : 14,
"componentType" : 5126,
"count" : 110,
"type" : "VEC3"
}
],
"bufferViews" : [
{
"buffer" : 0,
"byteLength" : 672,
"byteOffset" : 0
},
{
"buffer" : 0,
"byteLength" : 672,
"byteOffset" : 672
},
{
"buffer" : 0,
"byteLength" : 448,
"byteOffset" : 1344
},
{
"buffer" : 0,
"byteLength" : 180,
"byteOffset" : 1792
},
{
"buffer" : 0,
"byteLength" : 17844,
"byteOffset" : 1972
},
{
"buffer" : 0,
"byteLength" : 17844,
"byteOffset" : 19816
},
{
"buffer" : 0,
"byteLength" : 11896,
"byteOffset" : 37660
},
{
"buffer" : 0,
"byteLength" : 4668,
"byteOffset" : 49556
},
{
"buffer" : 0,
"byteLength" : 440,
"byteOffset" : 54224
},
{
"buffer" : 0,
"byteLength" : 1320,
"byteOffset" : 54664
},
{
"buffer" : 0,
"byteLength" : 1760,
"byteOffset" : 55984
},
{
"buffer" : 0,
"byteLength" : 1320,
"byteOffset" : 57744
},
{
"buffer" : 0,
"byteLength" : 1320,
"byteOffset" : 59064
},
{
"buffer" : 0,
"byteLength" : 1760,
"byteOffset" : 60384
},
{
"buffer" : 0,
"byteLength" : 1320,
"byteOffset" : 62144
}
],
"samplers" : [
{
"magFilter" : 9729,
"minFilter" : 9987
}
],
"buffers" : [
{
"byteLength" : 63464,
"uri" : "spaceship.bin"
}
]
}

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

Binary file not shown.

BIN
examples/data/spaceship.glb (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;
}

331
examples/main.cpp

@ -0,0 +1,331 @@
#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] = glm::vec4(-2, 1, 3, 1);
lb->dl_colors[idx] = glm::vec4(0.5, 0.5, 0.3, 1);
lb->dl_intensities[idx] = glm::uvec4(1, 0, 0, 0);
#endif
// NOTE: add a point light
idx = (*lb->active_p_lights)++;
lb->pl_positions[idx] = glm::vec4(-10, 0, -10, 1);
lb->pl_colors[idx] = glm::vec4(1, 1, 0.8, 1);
lb->pl_intensities[idx] = glm::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, glm::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;
glm::vec3 cube_locs[NUM_CUBES] = {
glm::vec3( 0, 0, 0),
glm::vec3(-10, 10, 0),
glm::vec3(-10, -10, 0),
glm::vec3( 10, 10, 0),
glm::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(rs);
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, glm::vec3(0, -10, -15));
} else {
return false;
}
return true;
}
bool
testColoredVertices(RenderState* rs)
{
Mesh m = {0};
m.num_vertices = 5;
m.num_indices = 12;
glm::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
};
glm::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;
glm::mat4 xform = glm::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, glm::vec3(0, -10, 15));
} else {
return false;
}
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 1
if (!loadSpaceShip(rs))
return false;
#endif
// NOTE: initilize the 'camera'
glm::vec3 cam_pos = { 0, 15, 40 };
glm::vec3 look_pos = { 0, 0, 0 };
glm::vec3 up = { 0, 1, 0 };
rs->xforms->view_xform = glm::lookAt(cam_pos, look_pos, up);
GLBuffer* xforms_ubo = getUBOByName(rs->gl_ctx, "matrices");
assert(xforms_ubo);
updateGLBuffer(xforms_ubo, rs->xforms);
return loadLights(rs);
}
void
orbitPositionZ0(glm::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)
{
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, glm::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 glm::vec4 cam_pos;
static glm::vec3 look_pos;
static glm::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->xforms->view_xform =
glm::lookAt(glm::vec3(cam_pos), glm::vec3(0, 0, 0), glm::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->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,
glm::vec3(0, 1, 0),
direction * (float) M_PI / (3 * 60));
}
}
int
main()
{
RenderState* rs = initRenderState();
if (rs) {
if (!loadScene(rs)) {
LOGF(Error, "error loading scene\n");
return 1;
}
doRenderLoop(rs, 60, render_cb_pre, nullptr);
freeRenderState(rs);
return 0;
}
LOGF(Error, "error loading scene\n");
return 1;
}

233
examples/render_groups/main.cpp

@ -1,233 +0,0 @@
#include <cstdlib>
#include <ctime>
#include <glm/glm.hpp>
#include "asset.h"
#include "dumbLog.h"
#include "input.h"
#include "mesh.h"
#include "renderer.h"
simple_mesh*
makeSquareMesh()
{
// NOTE: vertices arranged for GL_LINE_LOOP, non-indexed
uint num_vertices = 4;
simple_mesh* sm = meInitMesh(num_vertices);
sm->num_vertices = num_vertices;
sm->vertices[0] = glm::vec3(-1000, 0, 1000);
sm->vertices[1] = glm::vec3(-1000, 0, -1000);
sm->vertices[2] = glm::vec3(1000, 0, -1000);
sm->vertices[3] = glm::vec3(1000, 0, 1000);
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
getRand(int lower = 0, int upper = RAND_MAX)
{
// NOTE: only need to seed prng once
static bool seeded = false;
if (!seeded) {
srand(time(NULL));
seeded = true;
}
return (rand() % (upper - lower) + lower);
}
bool
createModelEntities(render_group* rg,
const char* model_file,
uint item_count,
glm::vec3 min_pos,
glm::vec3 max_pos,
glm::vec3 scaling)
{
#if 0
for (uint i = 0; i < item_count; i++) {
entity& e = rg->entities[i];
if (!entInitModel(e, model_file)) {
LOG(Error) << "Error initializing entity, exiting\n";
return false;
}
entSetWorldPosition(e,
glm::vec3(getRand(min_pos.x, max_pos.x),
getRand(min_pos.y, max_pos.y),
getRand(min_pos.z, max_pos.z))
);
entScale(e, glm::vec3(scaling.x, scaling.y, scaling.z));
}
return true;
#endif
return false;
}
void
doFrameCallbackPre(render_state* rs)
{
#if 0
static input_state is = {};
inputProcessEvents(&is);
if (is.window_closed || is.escape) {
rs->running = false;
return;
}
// NOTE: rotate meshes on z-axis every frame
static float angle = 1.2 / 60; // NOTE: 60 FPS
static glm::vec3 axis(0, 0, 1);
for (uint i = 0; i < rs->render_groups->count; i++) {
render_group* rg = &rs->render_groups->groups[i];
for (uint j = 0; j < rg->count; j++) {
entity* e = &rg->entities[j];
entRotate(e, angle, axis);
}
}
point_light& l1 = rs->lights->lights[0];
point_light& l2 = rs->lights->lights[1];
entity& square = rs->render_groups[2]->entities[0];
l1.position = glm::vec3(
square.world_transform * glm::vec4(10000, 0, 1000, 1));
l2.position = glm::vec3(
square.world_transform * glm::vec4(10000, 0, -1000, 1));
rs->lights->needs_update = true;
#endif
static input_state is = {};
inputProcessEvents(&is);
if (is.window_closed || is.escape) {
rs->running = false;
return;
}
static float angle = 1.2 / 60; // NOTE: 60 FPS
static glm::vec3 axis(0, 1, 0);
entity* spaceship = &rs->render_groups->groups[0].entities[0];
entRotate(spaceship, angle, axis);
}
int
main()
{
render_state* rs = renInit("render group example");
if (rs == nullptr) {
LOG(Error) << "Error Initialzing renderer\n";
return 1;
}
#if 0
cameraInitPerspective(
rs->cam,
glm::vec3(0, -2000, 0),
glm::vec3(0, 0, 0),
glm::vec3(0,0,1)
);
renAddLight(rs, glm::vec3());
renAddLight(rs, glm::vec3());
// FIXME: this introduces a potential buffer overrun. Need to implement
// a memory manager for render_groups, eg:
// renPushGroup(rs, new_group)
// rgPushEntity(rg, new_ent)
rs->render_groups = UTIL_ALLOC(256, render_group*);
// ship entities
const uint item_count = 20;
// 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
shader_wrapper sw = { DEFAULT_SHADER, rs->default_shader, nullptr };
rs->render_groups[0] = renAllocateGroup(item_count, sw);
rs->render_group_count++;
bool ret = createModelEntities(rs->render_groups[0],
"../data/spaceship.glb",
item_count,
glm::vec3(-750, -750, -750),
glm::vec3(750, 750, 750),
glm::vec3(10, 10, 10)
);
assert(ret == true);
// sphere entities
// NOTE: can also re-use the default shader already defined above
shader_wrapper sw2 = { DEFAULT_SHADER, rs->default_shader, nullptr };
rs->render_groups[1] = renAllocateGroup(item_count, sw2);
rs->render_group_count++;
ret = createModelEntities(rs->render_groups[1],
"../data/icosphere.glb",
item_count,
glm::vec3(-750, -750, -750),
glm::vec3(750, 750, 750),
glm::vec3(100, 100, 100)
);
assert(ret == true);
// simple_mesh/shader
shader_wrapper sw3 = { SIMPLE_SHADER, nullptr, rs->simple_shader };
rs->render_groups[2] = renAllocateGroup(1, sw3);
rs->render_group_count++;
entity& square = rs->render_groups[2]->entities[0];
simple_mesh* sm = makeSquareMesh();
entInitMesh(square, sm, GL_LINE_LOOP);
renDoRenderLoop(rs, 60, doFrameCallbackPre);
#endif
// NOTE: testing entity system with new asset structures
cameraInitPerspective(
rs->cam,
glm::vec3(0, 50, -100),
glm::vec3(0, 0, 0),
glm::vec3(0,1,0)
);
shader_wrapper sw = { DEFAULT_SHADER, rs->default_shader, nullptr };
render_group* rg = rgAlloc(rs->render_groups, 64, sw);
#if 1
entity* e =
rgAppend(rg, rs->assets, rs->textures, rs->arena,
"../data/blender/spaceship.gltf");
uint scale = 4;
//entRotate(e, -1 * M_PI_2, glm::vec3(1, 0, 0));
#else
entity* e =
rgAppend(rg, rs->assets, rs->textures, rs->arena,
"../data/blender/icosphere.gltf");
uint scale = 20;
#endif
if (e != nullptr) {
entScale(*e, glm::vec3(scale, scale, scale));
//renDoRenderLoop(rs, 60);
renDoRenderLoop(rs, 60, doFrameCallbackPre);
} else {
LOG(Error) << "failed to load entity\n";
}
renShutdown(rs);
return 0;
}

89
examples/simple_mesh/main.cpp

@ -1,89 +0,0 @@
#include <glm/glm.hpp>
#include "dumbLog.h"
#include "input.h"
#include "mesh.h"
#include "renderer.h"
void
doFrameCallbackPre(render_state* rs)
{
static input_state is = {};
inputProcessEvents(&is);
if (is.window_closed || is.escape) {
rs->running = false;
return;
}
int rotate_mod = 0;
if (is.left)
rotate_mod = -1;
if (is.right)
rotate_mod = 1;
// 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 * rotate_mod, 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");
rs->render_groups = UTIL_ALLOC(256, render_group*);
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[0] = 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, doFrameCallbackPre);
renShutdown(rs);
return 0;
}

147
include/GLDebug.h

@ -0,0 +1,147 @@
// 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;
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;
};

84
include/asset.h

@ -4,73 +4,61 @@
#include <GL/glew.h>
#include <glm/glm.hpp>
#include "animation.h"
#include "types.h"
#include "util.h"
#include "util_image.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
struct Mesh
{
GLenum draw_mode; // NOTE: GL_LINES, GL_TRIANGLES
GLenum usage; // NOTE: GL_STATIC_DRAW, GL_DYNAMIC_DRAW
uint num_vertices;
uint num_indices;
u32 num_vertices;
u32 num_indices;
glm::vec3* vertices;
glm::vec3* normals;
glm::vec2* texture_coords;
uint* indices;
glm::vec2* uvs;
glm::vec3* colors;
u16* indices; // NOTE: u16 to match tinygltf library output
glm::mat4* xform;
};
// TODO: will eventually need a tree structure with child nodes and transforms
// at each branch. Can then combine each transform down from the root node to
// the mesh, and send the final combination down to the shader
#define MAX_PATH_SIZE 256
struct model
struct Model
{
uint num_meshes;
char* filepath;
uint64_t filepath_hash;
mesh* meshes; // NOTE: fixed sized array
// NOTE: pointer to a texture in render_state->textures
util_image* diffuse_texture;
// FIXME: node_animation has a pointer to a render_object. need to decouple
// that somehow
//node_animation* node_anim;
};
struct model_assets
{
model* models;
uint count;
uint max;
u64 filepath_hash;
uint num_meshes;
Mesh* meshes;
Texture* diffuse_texture;
};
struct texture_assets
struct Assets
{
util_image* images;
uint count;
uint max;
MemoryArena* arena;
u32 num_models;
u32 max_models;
Model* models;
u32 num_textures;
u32 max_textures;
Texture* textures;
};
// TODO: would be nice to make these init functions generic
model_assets*
assetInitModelBlock(memory_arena* arena, uint asset_count);
texture_assets*
assetInitTextureBlock(memory_arena* arena, uint asset_count);
model*
assetLoadFromFile(model_assets* assets,
texture_assets* textures,
memory_arena* arena,
const char* filename);
Model* getModelByPath(Assets* assets, const char* filepath);
model*
assetGetCached(model_assets* assets, uint64_t path_hash);
Texture* getTextureByPath(Assets* assets, const char* filepath);

66
include/camera.h

@ -1,66 +0,0 @@
#pragma once
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include "util.h"
struct camera
{
float hAngle;
float vAngle;
glm::vec3 position;
glm::vec3 forward;
glm::vec3 up;
glm::vec3 left;
glm::vec3 target;
glm::vec3 world_up;
glm::mat4 model;
glm::mat4 view;
glm::mat4 projection;
glm::mat4 MVP;
};
enum projection_type
{
PERSPECTIVE,
ORTHOGRAPHIC,
};
v2f
cameraUnproject(camera& cam, int x, int y, int vp_width, int vp_height);
v3f
cameraCreateRay(camera& cam, v2i vp_coords, v2i vp_dims);
bool
cameraIntersectPlane(camera& cam,
v3f ray,
v3f plane_origin,
v3f plane_normal,
v3f& intersection);
void
cameraInitPerspective(camera* cam,
glm::vec3 position,
glm::vec3 target,
glm::vec3 world_up,
float aspect_ratio = 16.f / 9.f);
void
cameraMove(camera& cam,
bool up,
bool left,
bool down,
bool right,
bool forward,
bool backward);
void
cameraRotate(camera& cam, int32 xrel, int32 yrel);
void
cameraRoll(camera& cam, bool CW, bool CCW);

9
include/dumbLog.h

@ -24,7 +24,14 @@ struct dumbLog
static dumbLog logger;
#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) << "] " \
<< "(" << __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";

34
include/entity.h

@ -1,30 +1,32 @@
#pragma once
#include <glm/glm.hpp>
#include "asset.h"
#include "render_object.h"
#include "shader.h"
#include "types.h"
#include "util.h"
struct entity
struct Entity
{
glm::mat4 world_transform;
uint64_t model_id; // NOTE: filepath hash
render_objects* render_objs;
u32 num_meshes;
GLMesh* meshes;
GLTexture* diffuse_texture; // NOTE: pointer into gl_ctx->textures array
glm::mat4* model_xform;
char* name;
};
bool entInitModel(entity* e, model* mdl);
// FIXME: might as well stay consistent and make all these pointers
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 = "");
void entScale(entity& e, glm::vec3 v);
void setEntityPosition(Entity* e, glm::vec3 pos);
void entRotate(entity* e, float angle, glm::vec3 axis);
void rotateEntity(Entity* e, glm::vec3 axis, float radians);
void scaleEntity(Entity* e, float scale);

23
include/input.h

@ -1,23 +0,0 @@
#pragma once
#include <SDL2/SDL.h>
struct input_state
{
bool window_closed;
bool escape;
bool left;
bool right;
bool up;
bool down;
};
void
inputProcessEvent(input_state* is, SDL_Event& e);
// NOTE: convenience function that provides a while(SDL_PollEvents()) loop
void
inputProcessEvents(input_state* 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);

64
include/mesh.h

@ -1,64 +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;
// FIXME: WTF? why are we storing a copy of the texture on each mesh_info
// instead of once per mesh_group?
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;
};
// 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
}

27
include/render_object.h

@ -1,27 +0,0 @@
#pragma once
#include <GL/glew.h>
#include <glm/glm.hpp>
#include "asset.h"
#include "camera.h"
#include "lights.h"
#include "shader_program.h"
struct render_objects;
render_objects*
roInitModel(model* mdl);
void
roFree(render_objects* r_objs);
void
roDraw(render_objects* r_ojbs,
glm::mat4 world_transform,
camera* cam,
shader_wrapper sw,
light_group* lights);

132
include/renderer.h

@ -1,132 +0,0 @@
/*
* libTangerine, a small, modern openGL renderer specializing in flat shaded,
* solid color models using pallete textures. See README for build instructions
* and look in the examples folder for... examples
*
* fixmes)
* FIXME: load textures into texture asset array, and map in model structure
* FIXME: better shader abstraction so we can store a list of shaders on
* render_state
* FIXME: clean up examples with new asset system
* FIXME: remove mesh.h, mesh.cpp
* FIXME: merge render_group_fix branch back into master
* FIXME: remove platform_wait_for_vblank
* FIXME: make a test case for overflowing default array sizes, rg->assets
* rg_info->groups, render_group->enitities, rg->textures
* FIXME: look for glm::mat4 in structs, and replace with pointers
*
* todos)
* TODO: make assetInit* functions generic
* TODO: make initModel/initTexture functions generic
* TODO: resizable arrays for entities and asset system
* TODO: defaults include for various #defines
*/
#pragma once
#if defined (_WIN32)
#include <SDL.h>
#else
#include <SDL2/SDL.h>
#endif
#include <GL/glew.h>
#include <glm/glm.hpp>
#include "asset.h"
#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 count;
uint max_size;
shader_wrapper shader;
};
struct rg_info
{
render_group* groups;
uint count;
uint max_size;
};
render_group*
rgAlloc(rg_info* rgi, uint num_entites, shader_wrapper shader);
entity*
rgAppend(render_group* rg,
model_assets* assets,
texture_assets* textures,
memory_arena* arena,
const char* model_path);
struct SDL_Handles
{
SDL_Window *window;
SDL_GLContext glContext;
SDL_DisplayMode currentDisplayMode;
};
#define DEFAULT_RG_COUNT 256
struct render_state
{
glm::vec2 viewport_dims;
camera* cam;
util_RGBA clear_col;
SDL_Handles* handles;
bool running;
rg_info* render_groups;
memory_arena* arena;
model_assets* assets;
texture_assets* textures;
// TODO: hide shaders behind a better abstraction than 'shader_wrapper'
default_shader_program* default_shader;
simple_shader_program* simple_shader;
light_group* lights;
};
#define DEFAULT_ASSET_SIZE 256
render_state*
renInit(const char* title = "Tangerine",
glm::vec2 viewport_dims = glm::vec2(1280, 720),
Uint32 SDL_init_flags = 0,
size_t arena_size = DEFAULT_ARENA_SIZE,
uint asset_size = DEFAULT_ASSET_SIZE);
void renShutdown(render_state* rs);
// NOTE: callback function signature to use with renDoRenderLoop()
typedef void (*frame_callback_fn) (render_state*);
// 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
renDoRenderLoop(render_state* rs,
uint framerate = 60,
frame_callback_fn cb_func_pre = nullptr,
frame_callback_fn cb_func_post = 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);
glm::vec2
renGetWindowDims(render_state* rs);

222
include/shader.h

@ -0,0 +1,222 @@
#pragma once
#include <SDL2/SDL.h>
#include <GL/glew.h>
#include <glm/glm.hpp>
#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;
GLenum draw_mode; // NOTE: GL_LINES, GL_TRIANGLES
GLenum usage; // NOTE: GL_STATIC_DRAW, GL_DYNAMIC_DRAW
glm::mat4* node_xform;
u32 num_vertex_attrib_buffers;
GLBuffer* vertex_attrib_buffers;
GLBuffer* element_buf;
};
struct Animation;
struct Transforms
{
glm::mat4 view_xform;
glm::mat4 proj_xform;
glm::mat4 normal_xform;
};
const float DEFAULT_FOV = 60.f;
const float NEAR_CLIP_PLANE = 5.f;
const float DEFAULT_ASPECT_RATIO = 16.f / 9.f;
GLContext* initGLContext(MemoryArena* arena,
u32 max_shaders,
u32 max_textures,
u32 max_ubos);
// 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);
void updateGLBuffer(GLBuffer* gl_buf, void* data);
void renderVAO(GLMesh* glmesh,
glm::mat4* node_xform,
ShaderProgram* shader,
GLTexture* gl_tex);
GLVertexAttrib* getVertexAttribByName(ShaderProgram* shader, const char* name);
GLMesh loadGLMesh(MemoryArena* arena,
const Mesh& m,
GLenum draw_mode,
GLTexture* diffuse_texture,
u32 num_mappings,
GLBufferToAttribMapping mappings[]);
void initTransforms(MemoryArena* arena,
Transforms* xforms,
GLBuffer* xform_ubo,
GLContext* gl_ctx,
float fov = DEFAULT_FOV,
float near_clip_plane = NEAR_CLIP_PLANE,
float aspect_ratio = DEFAULT_ASPECT_RATIO);

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

279
include/tangerine.h

@ -0,0 +1,279 @@
/*
* 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: ===
* - 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
* - add libTangerine namespace?
* - merge back into libTangerine
* - 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
* - clean up examples with new asset system
* - merge render_group_fix branch back into master
* - make a test case for overflowing default array sizes, rg->assets
* rg_info->groups, render_group->enitities, rg->textures
* - look for glm::mat4 in structs, and replace with pointers (easier debugging)
* - resizable arrays for entities and asset system
* - defaults include for various #defines
*
* === 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()
*/
#pragma once
#include <SDL2/SDL.h>
#include "asset.h"
#include "entity.h"
#include "dumbLog.h"
#include "GLDebug.h"
#include "shader.h"
#include "types.h"
#include "util.h"
struct SDLHandles
{
SDL_Window* window;
SDL_GLContext sdl_gl_ctx;
SDL_DisplayMode display_mode;
};
// TODO: node/tree structure for entities
struct Node;
struct RenderGroup
{
ShaderProgram* shader;
u32 num_entities;
u32 max_entities;
Entity* entities;
char* name;
};
#if 0
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;
glm::vec4* ambient_color;
glm::vec4* pl_positions;
glm::vec4* pl_colors;
glm::uvec4* pl_intensities;
glm::vec4* dl_directions;
glm::vec4* dl_colors;
glm::uvec4* dl_intensities;
void* buffer;
};
// FIXME: re-implement Camera interface
struct Camera;
struct RenderState
{
bool running;
GLClearColor clear_col;
Transforms* xforms; // NOTE: would be part of camera in libTangerine
SDLHandles handles;
GLContext* gl_ctx;
Assets assets;
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;
// FIXME: re-integrating missing libTangerine render_state properties
Camera* camera;
glm::vec2 viewport_dims;
///
};
#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(GLClearColor clear_col = DEFAULT_CLEAR_COLOR,
glm::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);
// NOTE: callback function signature to use with renDoRenderLoop()
typedef void (*frame_callback_fn) (RenderState*);
// 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 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);

17
include/types.h

@ -1,16 +1,13 @@
#pragma once
#include <cstdint>
enum shader_t
{
SIMPLE_SHADER,
DEFAULT_SHADER
};
enum mesh_t
{
SIMPLE_MESH,
DEFAULT_MESHES
};
typedef uint8_t u8;
typedef uint16_t u16;
typedef uint32_t u32;
typedef uint64_t u64;
typedef int32_t i32;
typedef int64_t i64;

237
include/util.h

@ -1,118 +1,193 @@
#pragma once
#ifndef UTIL_H
#define UTIL_H
#include <cstdint>
#include <cassert>
#include <cstring>
#include <cstdio>
#include <cstdlib>
#include "dumbLog.h"
#include "types.h"
typedef float real32;
typedef float GLfloat;
typedef double real64;
typedef int32_t bool32;
typedef int32_t int32;
typedef int64_t int64;
typedef uint8_t uint8;
typedef uint32_t uint32;
typedef uint32_t uint;
//-----------------
// Hashing
struct v2f
{
v2f(): x(0), y(0) {}
v2f(real64 a, real64 b): x(a), y(b) {}
real64 x;
real64 y;
};
// 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 v2i
{
v2i(int a, int b): x(a), y(b) {}
v2i() : x(0), y(0) {}
int32 x;
int32 y;
};
//-----------------
// Memory allocation
struct v3f
struct MemoryArena
{
v3f(): x(0), y(0), z(0) {}
v3f(real64 a, real64 b, real64 c): x(a), y(b), z(c) {}
real64 x;
real64 y;
real64 z;
size_t max_size;
size_t free_size;
void* head;
void* next_free;
};
inline int32
SafeTruncateToInt32(int64 val)
{
assert(val <= INT32_MAX && val >= INT32_MIN);
return (int32) val;
}
#define DEFAULT_ARENA_SIZE 10 * 1024 * 1024 // 10MB
MemoryArena* arenaInit(size_t initial_size = DEFAULT_ARENA_SIZE);
inline void
utilConvertColor(GLfloat buf[3], uint32 color)
{
// NOTE: not using the alpha values
buf[0] = (GLfloat) ((color >> 24) & 0xFF) / (GLfloat) 255;
buf[1] = (GLfloat) ((color >> 16) & 0xFF) / (GLfloat) 255;
buf[2] = (GLfloat) ((color >> 8) & 0xFF) / (GLfloat) 255;
}
void arenaFree(MemoryArena*& arena);
//-----------------
// C Strings
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);
// NOTE: max_len should be the allocated size of dest
bool utilCopyCStr(char* dest, const char* src, uint max_len);
void utilSafeFree(void* p);
// NOTE: returns new string with '/' between
// NOTE: max_len should be the size of return buffer.
char* utilConcatPath(char* out, const char* base_dir, const char* file_name, uint max_len);
//---------------
// C string utils
const char* utilBaseName(const char* path_str);
bool utilCStrMatch(const char* str1, const char* str2);
// NOTE: returns true if the first 'sz' characters from each string match
bool utilMatchPrefix(const char* lhs, const char* rhs, int sz);
#endif // UTIL_H
#ifdef UTIL_IMPLEMENTATION
//-----------------
// Hashing
// NOTE: FNV1a hashing algorithm http://www.isthe.com/chongo/tech/comp/fnv/
#define FNV1_64_INIT ((uint64_t) 0xcbf29ce484222325ULL)
#define FNV_64_PRIME ((uint64_t) 0x100000001b3ULL)
uint64_t
utilFNV64a_str(const char *str, uint64_t hval = FNV1_64_INIT);
u64
utilFNV64a_str(const char* str, u64 hval)
{
unsigned char* s = (unsigned char *)str; // unsigned string
// FNV-1a hash each octet of the string
while (*s) {
// xor the bottom with the current octet
hval ^= (uint64_t)*s++;
// multiply by the 64 bit FNV magic prime mod 2^64
hval *= FNV_64_PRIME;
}
return hval;
}
//-----------------
// Memory allocation
// NOTE: Wrapper for calloc that will send error message on out of memory
#define UTIL_ALLOC(len, type) (type *) utilLogAlloc((len), sizeof(type), __FILE__, __LINE__)
void* utilLogAlloc(uint item_count, uint type_size, const char* file_name, const int line);
MemoryArena*
arenaInit(size_t initial_size)
{
u32 sz = sizeof(MemoryArena);
MemoryArena* arena =
(MemoryArena*) std::calloc(initial_size + sz, sizeof(u8));
arena->head = arena->next_free = (uint8_t*) arena + sz;
arena->max_size = initial_size;
return arena;
}
// TODO: replace instances of 'utilSafeFree()' with macro that casts to void
// pointer reference. Can then set the pointer to nullptr in free function
#define UTIL_FREE(mem_ptr) utilSafeFree((void*&) mem_ptr)
void utilSafeFree(const void* mem);
void
arenaFree(MemoryArena*& arena)
{
if (arena != nullptr) {
std::free(arena);
arena = nullptr;
}
}
struct memory_arena
uint
arenaGetFreeSize(MemoryArena* arena)
{
size_t max_size;
void* head;
void* next_free;
};
return (uint8_t*) arena->head
+ arena->max_size
- (uint8_t*) arena->next_free;
}
#define DEFAULT_ARENA_SIZE 10 * 1024 * 1024 // 10MB
memory_arena* arenaInit(size_t initial_size = DEFAULT_ARENA_SIZE);
void*
arenaAllocateBlock(MemoryArena* arena, size_t block_size)
{
// TODO: resizable memory arena
assert(arenaGetFreeSize(arena) >= block_size);
assert(block_size > 0);
void* ret = arena->next_free;
arena->next_free = (uint8_t*) arena->next_free + block_size;
arena->free_size = arenaGetFreeSize(arena);
return ret;
}
void*
arenaGetAddressOffset(void* address, u32 offset)
{
return (void*) ((u8*) address + offset);
}
void arenaFree(memory_arena*& arena);
char*
arenaCopyCStr(MemoryArena* arena, const char* input, u32 max_len)
{
u32 name_len = std::strlen(input) + 1;
assert(name_len > 1 && name_len < max_len);
char* out = ARENA_ALLOC(arena, char, name_len);
std::strncpy(out, input, name_len);
return out;
}
uint arenaGetFreeSize(memory_arena* arena);
void*
utilAllocate(u32 count, u32 type_size)
{
void* out = std::calloc(count, type_size);
assert(out != nullptr);
return out;
}
void* arenaAllocateBlock(memory_arena* arena, size_t block_size);
char*
utilAllocateCStr(const char* str, u32 max_len)
{
u32 len = strlen(str) + 1;
//-----------------
// File I/O
if (len > max_len) {
LOGF(Error, "%s , longer than %i\n", str, max_len);
return nullptr;
}
char* utilDumpTextFile(const char* filename);
char* out = (char*) std::calloc(len, sizeof(u8));
strncpy(out, str, len - 1);
return out;
}
bool utilWriteTextFile(const char* filename, const char* text);
void
utilSafeFree(void* p)
{
if (p)
free(p);
else
LOGF(Error, "free called on nullptr\n");
}
//---------------
// C string utils
bool
utilCStrMatch(const char* str1, const char* str2)
{
assert(str1 != nullptr && str2 != nullptr);
u32 l1 = strlen(str1);
u32 l2 = strlen(str2);
return (l1 == l2)
&& (strncmp(str1, str2, l1) == 0);
}
#endif

35
include/util_image.h

@ -1,35 +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;
uint64_t filepath_hash;
// FIXME: should use a pointer here, and just add the length of file_path
// onto the allocation for util_image
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);

193
src/asset.cpp

@ -10,49 +10,82 @@
// forward declarations
// TODO: move to GLDebug.h?
void dumpNodes(tinygltf::Model t_mdl);
model* initModel(model_assets* assets,
memory_arena* arena,
tinygltf::Model t_mdl,
const char* filename);
bool parseMeshNode(mesh* m,
texture_assets* textures,
memory_arena* arena,
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
model_assets*
assetInitModelBlock(memory_arena* arena, uint asset_count)
Model*
getModelByPath(Assets* assets, const char* filepath)
{
model_assets* assets =
(model_assets*) arenaAllocateBlock(arena, sizeof(model_assets));
assets->models =
(model*) arenaAllocateBlock(arena, asset_count * sizeof(model));
assets->max = asset_count;
Model* mdl = getCachedModel(assets, utilFNV64a_str(filepath));
if (!mdl)
mdl = loadModelFile(assets, filepath);
return assets;
return mdl;
}
texture_assets*
assetInitTextureBlock(memory_arena* arena, uint asset_count)
Texture*
getTextureByPath(Assets* assets, const char* filepath)
{
texture_assets* assets =
(texture_assets*) arenaAllocateBlock(arena, sizeof(texture_assets));
assets->images = (util_image*) arenaAllocateBlock(
arena, asset_count * sizeof(util_image));
assets->max = asset_count;
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 assets;
return texture;
}
// FIXME: move to internal when finished
util_image*
copyDiffuseTexture(texture_assets* textures,
memory_arena* arena,
const tinygltf::Model& t_mdl)
// 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
@ -60,30 +93,29 @@ copyDiffuseTexture(texture_assets* textures,
&& t_mdl.images.size() == 1
&& t_mdl.images[0].image.size() > 0);
tinygltf::Image t_img = t_mdl.images[0];
Texture* dtex = getFreeTexture(assets);
// TODO: re-alloc array when out of space
assert(textures->count < textures->max && arena != nullptr);
util_image* dtex = &textures->images[textures->count];
textures->count++;
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 = (uint8*) arenaAllocateBlock(arena, dtex->data_len);
std::strncpy(dtex->file_path, t_img.uri.c_str(), t_img.uri.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*
assetLoadFromFile(model_assets* assets,
texture_assets* textures,
memory_arena* arena,
const char* filename)
Model*
loadModelFile(Assets* assets, const char* filename)
{
tinygltf::Model t_mdl;
tinygltf::TinyGLTF gltf_ctx;
@ -97,28 +129,25 @@ assetLoadFromFile(model_assets* assets,
<< " , 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, arena, t_mdl, filename);
// FIXME: check for header overwriting here as seen in shader_testing
mdl->diffuse_texture = copyDiffuseTexture(textures, arena, t_mdl);
#if 1
if (mdl->diffuse_texture == nullptr) {
LOG(Error) << "Error Loading diffuse texture\n";
// TODO: reclaim arena memory
return nullptr;
}
#endif
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++],
textures, arena, node, t_mdl))
assets->arena,
node,
t_mdl))
{
LOG(Error) << "Error parsing node\n";
return nullptr;
@ -129,47 +158,33 @@ assetLoadFromFile(model_assets* assets,
return mdl;
}
model*
assetGetCached(model_assets* assets, uint64_t path_hash)
Model*
getCachedModel(Assets* assets, u64 path_hash)
{
for (uint i = 0; i < assets->count; i++) {
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: " << path_hash << "\n";
LOG(Debug) << "asset not cached, hash: " << path_hash << "\n";
return nullptr;
}
// internal
model*
initModel(model_assets* assets,
memory_arena* arena,
tinygltf::Model t_mdl,
const char* filename)
Texture*
getCachedTexture(Assets* assets, u64 path_hash)
{
// TODO: re-alloc array when out of space
assert(assets->count < assets->max && arena != nullptr);
model* mdl = &assets->models[assets->count];
assets->count++;
uint buf_count = t_mdl.bufferViews.size();
mdl->meshes = (mesh*) arenaAllocateBlock(arena, buf_count * sizeof(mesh));
mdl->num_meshes = t_mdl.meshes.size();
uint name_len = std::strlen(filename);
assert(name_len < MAX_PATH_SIZE);
mdl->filepath = (char*) arenaAllocateBlock(arena, name_len + 1);
std::strncpy(mdl->filepath, filename, name_len);
mdl->filepath_hash = utilFNV64a_str(mdl->filepath);
for (u32 i = 0; i < assets->num_textures; i++) {
if (assets->textures[i].filepath_hash == path_hash)
return &assets->textures[i];
}
return mdl;
LOG(Debug) << "asset not cached, hash: " << path_hash << "\n";
return nullptr;
}
bool
copyBuffer(uint8_t*& buffer_ref,
memory_arena* arena,
MemoryArena* arena,
int acc_idx,
const tinygltf::Model& t_mdl)
{
@ -191,7 +206,7 @@ copyBuffer(uint8_t*& buffer_ref,
}
assert(bv.byteStride == 0);
buffer_ref = (uint8_t*) arenaAllocateBlock(arena, bv.byteLength);
buffer_ref = ARENA_ALLOC(arena, u8, bv.byteLength);
std::memcpy(buffer_ref, &t_buf.data[bv.byteOffset], bv.byteLength);
return buffer_ref != nullptr;
@ -199,14 +214,13 @@ copyBuffer(uint8_t*& buffer_ref,
// FIXME: need to implement tree structure for blender models to work properly
glm::mat4*
parseNodeTransform(memory_arena* arena, const tinygltf::Node* node)
parseNodeTransform(MemoryArena* arena, const tinygltf::Node* node)
{
if (node->rotation.size() == 4
&& node->scale.size() == 3
&& node->translation.size() == 3)
{
glm::mat4* xform =
(glm::mat4*) arenaAllocateBlock(arena, sizeof(glm::mat4));
glm::mat4* xform = ARENA_ALLOC(arena, glm::mat4, 1);
*xform = glm::mat4(1.0f);
*xform = glm::rotate(*xform, (float) node->rotation[3],
glm::vec3((float) node->rotation[0],
@ -232,9 +246,8 @@ parseNodeTransform(memory_arena* arena, const tinygltf::Node* node)
}
bool
parseMeshNode(mesh* m,
texture_assets* textures,
memory_arena* arena,
parseMeshNode(Mesh* m,
MemoryArena* arena,
const tinygltf::Node& node,
const tinygltf::Model& t_mdl)
{
@ -254,11 +267,9 @@ parseMeshNode(mesh* m,
const tinygltf::Accessor& index_acc = t_mdl.accessors[prim.indices];
m->num_vertices = vert_acc.count;
m->num_indices = index_acc.count;
m->draw_mode = prim.mode;
m->usage = GL_STATIC_DRAW; // TODO: logic for updating dynamic meshes
// FIXME: the node transforms from blender only work as part of a node tree
#if 1
m->xform = (glm::mat4*) arenaAllocateBlock(arena, sizeof(glm::mat4));
m->xform = ARENA_ALLOC(arena, glm::mat4, 1);
*m->xform = glm::mat4(1.0f);
#else
m->xform = parseNodeTransform(arena, &node);
@ -269,7 +280,7 @@ parseMeshNode(mesh* m,
prim.attributes["POSITION"], t_mdl)
&& copyBuffer((uint8_t*&) m->normals, arena,
prim.attributes["NORMAL"], t_mdl)
&& copyBuffer((uint8_t*&) m->texture_coords, arena,
&& copyBuffer((uint8_t*&) m->uvs, arena,
prim.attributes["TEXCOORD_0"], t_mdl)
&& copyBuffer((uint8_t*&) m->indices, arena,
prim.indices, t_mdl))
@ -330,11 +341,11 @@ getDrawMode(int drawMode)
}
void
dumpBuffer(tinygltf::Model model, tinygltf::Accessor acc)
dumpBuffer(tinygltf::Model mdl, tinygltf::Accessor acc)
{
size_t bv_idx = acc.bufferView;
assert(bv_idx >= 0 && bv_idx < model.bufferViews.size());
tinygltf::BufferView bv = model.bufferViews[bv_idx];
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";

204
src/camera.cpp

@ -1,204 +0,0 @@
#include <cassert>
#include <GL/glew.h>
#include "camera.h"
// TODO: add these props to scene json
#define MOVE_SPEED 5.f
#define ROTATE_SPEED 0.005f
#define CAMERA_Z_CLAMP_ANGLE 85.f
#define FOV 60.f
#define NEAR_CLIP_PLANE 20.f
// forward declarations
inline glm::vec3 convertv3f(v3f v);
// interface
void
cameraInitPerspective(camera* cam,
glm::vec3 position,
glm::vec3 target,
glm::vec3 world_up,
float aspect_ratio)
{
assert(aspect_ratio > 0);
cam->position = position;
cam->target = target;
cam->world_up = world_up;
cam->projection = glm::infinitePerspective(glm::radians(FOV),
aspect_ratio,
NEAR_CLIP_PLANE);
cam->forward = glm::normalize(target - position);
cam->left = glm::normalize(glm::cross(cam->world_up, cam->forward));
cam->up = glm::normalize(glm::cross(cam->forward, cam->left));
cam->hAngle = glm::atan(cam->forward.x, cam->forward.y);
// NOTE: get absolute value of relative axis for vAngle component
real32 len = glm::sqrt(glm::pow(cam->forward.y, 2) +
glm::pow(cam->forward.x, 2));
cam->vAngle = glm::atan(cam->forward.z, len);
cam->view =
glm::lookAt(cam->position, cam->position + cam->forward, cam->up);
cam->model = glm::mat4(1.0f);
cam->MVP = cam->projection * cam->view * cam->model;
}
void
cameraInitOrthographic(/*camera& cam, */)
{
#if 0
// left, right, bottom, top, zNear, zFar
cam.projection = glm::ortho(0.f, 1280.0f, 0.f, 720.0f, 0.1f, 100.0f);
cam.view = glm::lookAt(
glm::vec3(0.0f, 0.0f, 1.0f), // camera position
glm::vec3(0.0f, 0.0f, 0.0f), // look at position
glm::vec3(0,1,0) // "up" vector
);
cam.model = glm::mat4(1.0f);
cam.MVP = cam.projection * cam.view * cam.model;
#endif
}
v2f
cameraUnproject(camera& cam, int x, int y, int vp_width, int vp_height)
{
// NOTE: using depth buffer may not be as accurate as doing ray-cast
GLfloat depth;
glReadPixels(x, y, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &depth);
glm::vec4 viewport = glm::vec4(0, 0, vp_width, vp_height);
glm::vec3 wincoord = glm::vec3(x, y, depth);
glm::vec3 vU = glm::unProject(wincoord, cam.view, cam.projection, viewport);
v2f v(vU.x, vU.y);
return v;
}
v3f
cameraCreateRay(camera& cam, v2i vp_coords, v2i vp_dims)
{
// NOTE: http://antongerdelan.net/opengl/raycasting.html
float x = 2.f * vp_coords.x / vp_dims.x - 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);
glm::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
glm::vec4 ray_world = glm::normalize(glm::inverse(cam.view) * ray_eye);
return v3f(ray_world.x, ray_world.y, ray_world.z);
}
bool
cameraIntersectPlane(camera& cam, v3f ray, v3f plane_origin, v3f plane_normal, v3f& 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;
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
return false;
float distance = glm::dot((p_o - c_o), p_n) / divisor;
glm::vec3 xsect = c_o + (r * distance);
intersection = v3f(xsect.x, xsect.y, xsect.z);
return true;
}
void
cameraMove(camera& cam, bool up, bool left, bool down, bool right, bool forward, bool backward)
{
if (!up && !left && !down && !right && !forward && !backward)
return;
glm::vec3 f = cam.forward;
glm::vec3 u = cam.up;
glm::vec3 old = cam.position;
glm::vec3 &p = cam.position;
glm::vec3 v(0.f); // normalized direction
// TODO: still seems like we're adding magnitude when moving in 2 directions
#if 0
if (forward) v = glm::normalize(v + f);
if (backward) v = glm::normalize(v - f);
if (up) v = glm::normalize(v + u);
if (down) v = glm::normalize(v - u);
if (left) v -= glm::normalize(glm::cross(f, u));
if (right) v -= glm::normalize(glm::cross(u, f));
#else
if (forward) v += f;
if (backward) v -= f;
if (up) v += u;
if (down) v -= u;
if (left) v -= glm::cross(f, u);
if (right) v -= glm::cross(u, f);
#endif
p += (v * MOVE_SPEED);
glm::vec3 diff = old - p;
cam.view = glm::translate(cam.view, diff);
cam.MVP = cam.projection * cam.view * cam.model;
}
void
cameraRotate(camera& cam, int32 xrel, int32 yrel)
{
float &h = cam.hAngle;
float &v = cam.vAngle;
h += ROTATE_SPEED * xrel;
v -= ROTATE_SPEED * yrel;
// clamp vAngle to prevent gimbal lock
float a = glm::radians(CAMERA_Z_CLAMP_ANGLE);
if (v < (-1 * a)) v = (-1 * a);
if (v > a) v = a;
cam.forward = glm::vec3(
glm::cos(v) * glm::sin(h),
glm::cos(v) * glm::cos(h),
glm::sin(v)
);
glm::normalize(cam.forward);
cam.left = glm::normalize(glm::cross(cam.forward, cam.world_up));
cam.up = glm::normalize(glm::cross(cam.left, cam.forward));
cam.view = glm::lookAt(cam.position, cam.position + cam.forward, cam.up);
cam.MVP = cam.projection * cam.view * cam.model;
}
void
cameraRoll(camera& cam, bool CW, bool CCW)
{
if ((!CW && !CCW) || (CW && CCW))
return;
float a = 0.005f;
if (CW) a *= 1;
if (CCW) a *= -1;
glm::mat4 m = glm::rotate(glm::mat4(1.f), a, cam.forward);
glm::vec4 v(cam.up.x, cam.up.y, cam.up.z, 0);
v = v * m;
cam.up = glm::vec3(v.x, v.y, v.z);
cam.view *= m;
cam.MVP = cam.projection * cam.view * cam.model;
}
// internal
inline glm::vec3
convertv3f(v3f v)
{
return glm::vec3(v.x, v.y, v.z);
}

162
src/default_shaders.cpp

@ -1,162 +0,0 @@
// NOTE: default shader
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 = vec4(world_transform * vec4(normal, 1)).xyz;
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));
float brightness = dot(normal, surfaceToLight) / length(surfaceToLight);
totalBrightness += brightness;
}
color = clamp(totalBrightness, 0, 1) * texture(sampler, fragUV.st);
}
)FS";
// NOTE: debug shader
const char* DEBUG_VERTEX_SHADER = R"DVS(
#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;
uniform mat4 world_transform;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
uniform mat3 normal_matrix;
void main()
{
fragNormal = vec4(world_transform * vec4(normal, 1)).xyz;
fragVertex = vertexPosition_modelspace;
gl_Position = projection * view *
world_transform * model * vec4(vertexPosition_modelspace, 1);
}
)DVS";
const char* DEBUG_FRAGMENT_SHADER = R"DFS(
#version 330 core
in vec3 fragVertex;
in vec3 fragNormal;
out vec4 color;
uniform mat4 model;
uniform mat3 normal_matrix;
void main()
{
color = vec4(normalize(
vec3(fragNormal.x, fragNormal.y, -1 * fragNormal.z)
), 1.0);
}
)DFS";
// NOTE: simple shader
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 "dumbLog.h"
#include "types.h"
void dumbLog::setOutputStream(std::ostream* out)
{
@ -16,6 +18,7 @@ dumbLog::logLevelToString(log_level level)
case log_level::Error: return "Error";
case log_level::Warning: return "Warning";
case log_level::Info: return "Info";
case log_level::Debug: return "Debug";
default: return "Potato";
}
};
@ -32,7 +35,36 @@ int
dumbLog::getCurrentMS()
{
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);
}
#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__);
}
}

90
src/entity.cpp

@ -1,76 +1,68 @@
#include <cassert>
#include <glm/ext/matrix_transform.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include "dumbLog.h"
#include "entity.h"
#include "util.h"
// forward declarations
void initDefaults(entity& e);
// interface
bool
entInitModel(entity* e, model* mdl)
initEntity(Entity* e,
GLContext* gl_ctx,
MemoryArena* arena,
Model* mdl,
u32 num_attrib_mappings,
GLBufferToAttribMapping* attrib_mappings,
const char* name)
{
e->render_objs = roInitModel(mdl);
e->world_transform = glm::mat4(1);
e->model_id = mdl->filepath_hash;
e->num_meshes = mdl->num_meshes;
e->meshes = ARENA_ALLOC(arena, GLMesh, e->num_meshes);
e->model_xform = ARENA_ALLOC(arena, glm::mat4, 1);
*e->model_xform = glm::mat4(1.f);
e->name = arenaCopyCStr(arena, name);
if (mdl->diffuse_texture) {
e->diffuse_texture = getGLTexture(gl_ctx, mdl->diffuse_texture);
if (e->render_objs == nullptr) {
entFree(e);
if (!e->diffuse_texture)
return false;
}
return true;
}
void
entFree(entity* e)
{
roFree(e->render_objs);
e->render_objs = 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;
}
for (u32 i = 0; i< e->num_meshes; i++) {
GLMesh* glm = &e->meshes[i];
*glm = loadGLMesh(arena,
mdl->meshes[i],
GL_TRIANGLES,
e->diffuse_texture,
num_attrib_mappings,
attrib_mappings);
if (glm->vao_id == 0) {
LOGF(Error, "error initializing entity\n");
return false;
}
}
void
entTranslate(entity& e, glm::vec3 v)
{
e.world_transform = glm::translate(e.world_transform, v);
return true;
}
void
entScale(entity& e, glm::vec3 v)
setEntityPosition(Entity* e, glm::vec3 pos)
{
e.world_transform = glm::scale(e.world_transform, v);
(*e->model_xform)[3][0] = pos.x;
(*e->model_xform)[3][1] = pos.y;
(*e->model_xform)[3][2] = pos.z;
}
void
entRotate(entity* e, float angle, glm::vec3 axis)
rotateEntity(Entity* e, glm::vec3 axis, float radians)
{
e->world_transform = glm::rotate(e->world_transform, angle, axis);
*e->model_xform = glm::rotate(*e->model_xform, radians, axis);
}
// internal
void
initDefaults(entity& e)
scaleEntity(Entity* e, float scale)
{
e.world_transform = glm::mat4(1.0);
entScale(e, glm::vec3(1.0));
entSetWorldPosition(e, glm::vec3(0, 0, 0));
*e->model_xform =
glm::scale(*e->model_xform, glm::vec3(scale, scale, scale));
}

42
src/input.cpp

@ -1,42 +0,0 @@
#include "input.h"
void
inputProcessEvent(input_state* 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(input_state* is)
{
SDL_Event e;
while (SDL_PollEvent(&e))
inputProcessEvent(is, e);
}

12
src/libs.cpp

@ -1,12 +0,0 @@
// NOTE: put all the header-only libs in a separate compilation unit to save on
// compile times
#define TINYGLTF_IMPLEMENTATION
#include "tiny_gltf.h"
#define STB_IMAGE_IMPLEMENTATION
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "stb_image.h"
#include "stb_image_write.h"

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

262
src/mesh.cpp

@ -1,262 +0,0 @@
#include <cassert>
#if 0
#include <assimp/cimport.h>
#include <assimp/scene.h>
#include <assimp/postprocess.h>
#endif
#include <glm/glm.hpp>
#include <glm/geometric.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include "dumbLog.h"
#include "mesh.h"
// WIP
bool meInitAssimp() { return false; }
bool meLoadFromFile(mesh_group& mesh_group, const char* filepath)
{
return false;
}
simple_mesh*
meInitMesh(uint num_vertices) { return nullptr; }
void
meFreeMeshGroup(mesh_group& mesh_group) {}
void
meFreeSimpleMesh(simple_mesh* mesh) {}
void
meShutdownAssimp() {}
// WIP
#if 0
// 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;
}
#endif

246
src/render_object.cpp

@ -1,246 +0,0 @@
#include <glm/gtc/matrix_transform.hpp>
#include "dumbLog.h"
#include "render_object.h"
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 render_objects
{
default_render_object* objects;
uint count;
mesh_t mesh_type;
};
// forward declarations
void drawDefault(render_objects* r_objs,
glm::mat4 world_transform,
camera* cam,
shader_wrapper sw,
light_group* lights);
bool loadMeshIntoGL(default_render_object* ro_out, mesh* me_in);
// interface
render_objects*
roInitModel(model* mdl)
{
uint count = mdl->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], &mdl->meshes[i])) {
roFree(r_objs);
return nullptr;
}
}
return r_objs;
}
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 = 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)
{
assert(r_objs->mesh_type == DEFAULT_MESHES);
// FIXME: might as well move this function now that we only have one path
drawDefault(r_objs, world_transform, cam, sw, lights);
}
// internal
bool
initGLBuffer(void* buffer,
uint count,
GLuint* buffer_id,
uint el_count = 3,
uint el_size = sizeof(float),
GLenum target = GL_ARRAY_BUFFER,
GLenum usage = GL_STATIC_DRAW)
{
if ((el_count == 3 && el_size == sizeof(float))
|| (el_count == 2 && el_size == sizeof(float))
|| (el_count == 1 && el_size == sizeof(unsigned short)))
{
glGenBuffers(1, buffer_id);
glBindBuffer(target, *buffer_id);
glBufferData(target, count * el_count * el_size, buffer, usage);
return (glGetError() == GL_NO_ERROR);
}
return false;
}
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
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* mesh_in)
{
assert(mesh_in != nullptr && ro_out != nullptr);
if (initGLBuffer(mesh_in->vertices, mesh_in->num_vertices,
&ro_out->vertex_buffer_id)
&& initGLBuffer(mesh_in->normals, mesh_in->num_vertices,
&ro_out->normal_buffer_id)
&& initGLBuffer(mesh_in->texture_coords, mesh_in->num_vertices,
&ro_out->uv_buffer_id, 2)
&& initGLBuffer(mesh_in->indices, mesh_in->num_indices,
&ro_out->index_buffer_id, 1, sizeof(unsigned short),
GL_ELEMENT_ARRAY_BUFFER)
// FIXME: re-implement diffuse texture, but with index into an array
// on render_state
#if 0
&& initGLTexture(mesh_in->diffuse_texture, ro_out->tex_id))
#endif
)
{
ro_out->node_xform = *mesh_in->xform;
ro_out->index_buffer_count = mesh_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]);
}
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 = r_objs->objects;
glUseProgram(shader->program_id);
updateMatrices(shader, cam, world_transform, objects->node_xform);
// FIXME: re-enable lights
#if 0
if (lights->needs_update) lightsUpdate(lights, shader);
#endif
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);
// FIXME: re-enable textures
#if 0
glBindTexture(GL_TEXTURE_2D, objects[i].tex_id);
glUniform1i(shader->sampler_id, 0);
#endif
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, objects[i].index_buffer_id);
// FIXME: tinygltf uses unsigned short as index type
#if 0
glDrawElements(GL_TRIANGLES,
objects[i].index_buffer_count,
GL_UNSIGNED_INT,
0);
#endif
glDrawElements(GL_TRIANGLES,
objects[i].index_buffer_count,
GL_UNSIGNED_SHORT,
0);
glDisableVertexAttribArray(0);
glDisableVertexAttribArray(1);
glDisableVertexAttribArray(2);
}
glUseProgram(0);
}

369
src/renderer.cpp

@ -1,369 +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 "default_shaders.cpp"
#include "dumbLog.h"
#include "input.h"
#include "render_object.h"
#include "renderer.h"
#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
bool createWindow(const char* title,
SDL_Handles* handles,
glm::vec2& viewport_dims);
bool initContext(SDL_Handles* handles);
bool initGlOptions();
bool initSDL(SDL_Handles* handles, Uint32 SDL_init_flags);
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_group*
rgAlloc(rg_info* rgi, uint num_entites, shader_wrapper shader)
{
if (rgi->count < rgi->max_size) {
render_group* rg = &rgi->groups[rgi->count];
rgi->count++;
rg->entities = UTIL_ALLOC(num_entites, entity);
rg->max_size = num_entites;
rg->shader = shader;
return rg;
}
LOG(Error) << "no free render_group\n";
return nullptr;
}
entity*
rgAppend(render_group* rg,
model_assets* assets,
texture_assets* textures,
memory_arena* arena,
const char* model_path)
{
if (rg->count < rg->max_size) {
entity* e = &rg->entities[rg->count];
model* mdl = assetGetCached(assets, utilFNV64a_str(model_path));
// NOTE: not cached
if (mdl == nullptr) {
mdl = assetLoadFromFile(assets, textures, arena, model_path);
if (mdl == nullptr) {
LOG(Error) << "Error loading model: " << model_path << "\n";
return nullptr;
}
}
if (entInitModel(e, mdl)) {
rg->count++;
return e;
} else {
LOG(Error) << "Error initializing GL buffers\n";
return nullptr;
}
}
LOG(Error) << "no free entity in render_group\n";
return nullptr;
}
render_state*
renInit(const char* title,
glm::vec2 viewport_dims,
Uint32 SDL_init_flags,
size_t arena_size,
uint asset_size)
{
render_state* rs = UTIL_ALLOC(1, render_state);
rs->handles = UTIL_ALLOC(1, SDL_Handles);
rs->cam = UTIL_ALLOC(1, camera);
// TODO: add parameter for custom render_group count
rs->render_groups = UTIL_ALLOC(1, rg_info);
rs->render_groups->groups = UTIL_ALLOC(DEFAULT_RG_COUNT, render_group);
rs->render_groups->max_size = DEFAULT_RG_COUNT;
rs->arena = arenaInit(arena_size);
rs->assets = assetInitModelBlock(rs->arena, asset_size);
rs->textures = assetInitTextureBlock(rs->arena, asset_size);
rs->lights = lightsInit();
setDefaults(rs, viewport_dims);
if (rs->assets != nullptr &&
initSDL(rs->handles, SDL_init_flags) &&
createWindow(title, rs->handles, rs->viewport_dims) &&
initContext(rs->handles) &&
initGlOptions() &&
initShaders(rs))
{
return rs;
}
LOG(Error) << "renderer initialization failed, aborting\n";
return nullptr;
}
void
renShutdown(render_state* rs)
{
for (uint i = 0; i < rs->render_groups->count; i++) {
render_group* rg = &rs->render_groups->groups[i];
for (uint j = 0; j < rg->count; j++)
entFree(&rg->entities[j]);
}
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);
rs->render_groups = nullptr;
arenaFree(rs->arena);
SDL_GL_DeleteContext(rs->handles->glContext);
SDL_DestroyWindow(rs->handles->window);
SDL_Quit();
utilSafeFree(rs->handles);
}
void
renDoRenderLoop(render_state* rs,
uint framerate,
frame_callback_fn cb_func_pre,
frame_callback_fn cb_func_post)
{
uint delay = (framerate > 0) ? 1 / framerate : 0;
uint frameStart, frameTime;
static input_state is = {};
while (rs->running) {
frameStart = SDL_GetTicks();
if (cb_func_pre != nullptr) {
cb_func_pre(rs);
} else {
inputProcessEvents(&is);
if (is.window_closed || is.escape) {
rs->running = false;
return;
}
}
renRenderFrame(rs);
if (cb_func_post != nullptr) cb_func_post(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_groups->count; i++) {
render_group* rg = &rs->render_groups->groups[i];
for (uint j = 0; j < rg->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;
}
glm::vec2
renGetWindowDims(render_state* rs)
{
int x = 0, y = 0;
SDL_GetWindowSize(rs->handles->window, &x, &y);
glm::vec2 dims(x, y);
return dims;
}
// 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;
}
// TODO: this doesn't work inside VM with QXL graphics
if (SDL_GL_SetSwapInterval(1) != 0) // vsync
LOG(Error) << "SDL Errors: " << SDL_GetError() << "\n";
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, Uint32 SDL_init_flags)
{
Uint32 flags = SDL_INIT_VIDEO | SDL_init_flags;
if (SDL_Init(flags) != 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 =
// FIXME: debug shader
#if 0
shaderInitDefault(DEFAULT_VERTEX_SHADER, DEFAULT_FRAGMENT_SHADER);
#endif
shaderInitDefault(DEBUG_VERTEX_SHADER, DEBUG_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->running = true;
rs->viewport_dims = viewport_dims;
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;
}

751
src/shader.cpp

@ -0,0 +1,751 @@
#include <cassert>
#include <cstddef>
#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <fstream>
#include <glm/gtc/matrix_transform.hpp>
#include "asset.h"
#include "dumbLog.h"
#include "dummy_shader.h"
#define GL_DEBUG_IMPLEMENTATION
#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);
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; 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;
}
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,
glm::mat4* node_xform,
ShaderProgram* shader,
GLTexture* gl_tex)
{
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(
glmesh->draw_mode, glmesh->num_indices, GL_UNSIGNED_SHORT, 0);
glBindVertexArray(0);
}
void
initTransforms(MemoryArena* arena,
Transforms* xforms,
GLBuffer* xform_ubo,
GLContext* gl_ctx,
float fov,
float near_clip_plane,
float aspect_ratio)
{
xforms->proj_xform = glm::infinitePerspective(
glm::radians(fov), aspect_ratio, near_clip_plane);
glGenBuffers(1, &xform_ubo->id);
xform_ubo->target = GL_UNIFORM_BUFFER;
xform_ubo->data_type = GL_FLOAT;
xform_ubo->name = arenaCopyCStr(arena, "matrices");
xform_ubo->data_size = sizeof(*xforms);
glBindBuffer(xform_ubo->target, xform_ubo->id);
glBufferData(xform_ubo->target, xform_ubo->data_size, xforms,
GL_DYNAMIC_DRAW);
// NOTE: bindbufferbase
xform_ubo->binding_idx = gl_ctx->binding_count++;
glBindBufferBase(xform_ubo->target, xform_ubo->binding_idx, xform_ubo->id);
glBindBuffer(xform_ubo->target, 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,
GLenum draw_mode)
{
GLMesh glm = {0};
glm.num_indices = m.num_indices;
glm.draw_mode = draw_mode;
glm.usage = GL_STATIC_DRAW; // TODO: logic for updating dynamic meshes
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, glm::mat4, 1);
*glm.node_xform = glm::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;
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 void copying GLMesh twice that way
GLMesh
loadGLMesh(MemoryArena* arena,
const Mesh& m,
GLenum draw_mode,
GLTexture* diffuse_texture,
u32 num_mappings,
GLBufferToAttribMapping mappings[])
{
GLMesh glm = initGLMesh(arena, m, num_mappings, draw_mode);
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,
glm.usage);
glVertexAttribPointer(attrib->location, attrib->num_components,
attrib->component_type, GL_FALSE, 0, 0);
glEnableVertexAttribArray(attrib->location);
}
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,
glm.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 (std::strstr(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;
}

146
src/shader_program.cpp

@ -1,146 +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");
// FIXME: re-enable textures
#if 0
sp->num_lights_id = glGetUniformLocation(sp->program_id, "num_lights");
sp->sampler_id = glGetUniformLocation(sp->program_id, "sampler");
#endif
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;
}

386
src/tangerine.cpp

@ -0,0 +1,386 @@
#include <cassert>
#include "tangerine.h"
#define UTIL_IMPLEMENTATION
#include "util.h"
// forward declarations
bool initGraphics(SDLHandles* handles);
LightsBuffer* initLights(MemoryArena* arena, GLContext* gl_ctx, u32 max_lights,
glm::vec4 ambient_color);
// interface
RenderState*
initRenderState(GLClearColor clear_col,
glm::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);
if (!initGraphics(&rs->handles)) {
LOGF(Error, "error initializing renderer\n");
return nullptr;
}
rs->gl_ctx = initGLContext(rs->assets.arena,
max_shaders,
max_textures,
max_ubos);
rs->xforms = ARENA_ALLOC(rs->assets.arena, Transforms, 1);
GLBuffer* xforms_ubo = getFreeUBO(rs->gl_ctx);
assert(xforms_ubo);
initTransforms(rs->assets.arena, rs->xforms, xforms_ubo, rs->gl_ctx);
rs->lights_buf = initLights(rs->rg_arena, rs->gl_ctx, max_lights,
ambient_color);
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;
}
void
doRenderLoop(RenderState* rs,
u32 framerate,
frame_callback_fn cb_func_pre,
frame_callback_fn cb_func_post)
{
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);
} 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);
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);
}
}
}
}
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)
{
handles->window = SDL_CreateWindow(
"shader_testing",
SDL_WINDOWPOS_CENTERED_DISPLAY(0),
SDL_WINDOWPOS_CENTERED_DISPLAY(0),
1280,
720,
SDL_WINDOW_OPENGL|SDL_WINDOW_RESIZABLE);
if (SDL_Init(SDL_INIT_VIDEO) != 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(MemoryArena* arena,
GLContext* gl_ctx,
u32 max_lights,
glm::vec4 ambient_color)
{
// FIXME: revisit for 'Scene' abstraction
LightsBuffer* lb = ARENA_ALLOC(arena, LightsBuffer, 1);
lb->buf_size = 8 * sizeof(u32) // NOTE: 'header'
+ sizeof(glm::vec4) // NOTE: ambient color
+ 6 * max_lights * sizeof(glm::vec4); // NOTE: vector arrays
LOGF(Debug, "buf_size: %d\n", lb->buf_size);
lb->buffer = ARENA_ALLOC(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 = (glm::vec4*) arr_start;
*lb->ambient_color = ambient_color;
// NOTE: set offsets for array pointers
u32 arr_size = max_lights * sizeof(glm::vec4);
lb->pl_positions = //(glm::vec4*) arr_start;
(glm::vec4*) arenaGetAddressOffset(arr_start, sizeof(glm::vec4));
lb->pl_colors =
(glm::vec4*) arenaGetAddressOffset(lb->pl_positions, arr_size);
lb->pl_intensities =
(glm::uvec4*) arenaGetAddressOffset(lb->pl_colors, arr_size);
lb->dl_directions =
(glm::vec4*) arenaGetAddressOffset(lb->pl_intensities, arr_size);
lb->dl_colors =
(glm::vec4*) arenaGetAddressOffset(lb->dl_directions, arr_size);
lb->dl_intensities =
(glm::uvec4*) arenaGetAddressOffset(lb->dl_colors, arr_size);
GLBuffer* lights_ubo = getFreeUBO(gl_ctx);
assert(lights_ubo);
lights_ubo->target = GL_UNIFORM_BUFFER;
lights_ubo->data_type = GL_BYTE; // NOTE: mixed types in structure
lights_ubo->data_size = lb->buf_size;
lights_ubo->name = arenaCopyCStr(arena, "lights");
assert((GLint) gl_ctx->binding_count < gl_ctx->max_binding_points);
lights_ubo->binding_idx = gl_ctx->binding_count++;
glGenBuffers(1, &lights_ubo->id);
glBindBuffer(lights_ubo->target, lights_ubo->id);
glBufferData(lights_ubo->target,
lb->buf_size,
lb->buffer,
GL_DYNAMIC_DRAW);
glBindBufferBase(lights_ubo->target,
lights_ubo->binding_idx,
lights_ubo->id);
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"

197
src/util.cpp

@ -1,197 +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);
}
//-----------------
// Hashing
uint64_t
utilFNV64a_str(const char* str, uint64_t hval)
{
unsigned char* s = (unsigned char *)str; // unsigned string
// FNV-1a hash each octet of the string
while (*s) {
// xor the bottom with the current octet
hval ^= (uint64_t)*s++;
// multiply by the 64 bit FNV magic prime mod 2^64
hval *= FNV_64_PRIME;
}
return hval;
}
//-----------------
// 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);
}
memory_arena*
arenaInit(size_t initial_size)
{
uint sz = sizeof(memory_arena);
memory_arena* arena =
(memory_arena*) UTIL_ALLOC(initial_size + sz, uint8_t);
arena->head = arena->next_free = (uint8_t*) arena + sz;
arena->max_size = initial_size;
return arena;
}
void
arenaFree(memory_arena*& arena)
{
utilSafeFree(arena);
arena = nullptr;
}
uint
arenaGetFreeSize(memory_arena* arena)
{
return (uint8_t*) arena->head
+ arena->max_size
- (uint8_t*) arena->next_free;
}
void*
arenaAllocateBlock(memory_arena* arena, size_t block_size)
{
// TODO: resizable memory arena
assert(arenaGetFreeSize(arena) >= block_size);
void* ret = arena->next_free;
arena->next_free = (uint8_t*) arena->next_free + block_size;
return ret;
}
//-----------------
// 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;
}

59
src/util_image.cpp

@ -1,59 +0,0 @@
#include <cstring>
#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