From 2d4e84d74f2831787d63d9c312632f0d46e5e146 Mon Sep 17 00:00:00 2001 From: cinnaboot Date: Sun, 27 Feb 2022 14:05:46 -0500 Subject: [PATCH] setup working tree to merge shader_testing fixes --- Makefile | 18 +- examples/Makefile | 42 +- examples/assimp_loading/animation_testing.cpp | 72 -- examples/assimp_loading/main.cpp | 85 -- examples/data/blender/Color Palette 140.png | 3 - examples/data/blender/icosphere.bin | Bin 32640 -> 0 bytes examples/data/blender/icosphere.blend | 3 - examples/data/blender/icosphere.gltf | 133 ---- examples/data/blender/spaceship.bin | Bin 63464 -> 0 bytes examples/data/blender/spaceship.blend | 3 - examples/data/blender/spaceship.gltf | 453 ----------- examples/data/icosphere.glb | 3 - examples/data/spaceship.glb | 3 - examples/hello_world/main.cpp | 16 - examples/main.cpp | 331 ++++++++ examples/render_groups/main.cpp | 233 ------ examples/simple_mesh/main.cpp | 89 --- include/GLDebug.h | 147 ++++ include/animation.h | 18 - include/asset.h | 84 +- include/camera.h | 66 -- include/dumbLog.h | 9 +- include/dummy_shader.h | 30 + include/entity.h | 34 +- include/input.h | 23 - include/lights.h | 34 - include/mesh.h | 64 -- include/platform_wait_for_vblank.h | 102 --- include/render_object.h | 27 - include/renderer.h | 132 --- include/shader.h | 222 ++++++ include/shader_program.h | 57 -- include/tangerine.h | 279 +++++++ include/types.h | 17 +- include/util.h | 237 ++++-- include/util_image.h | 35 - src/asset.cpp | 193 ++--- src/camera.cpp | 204 ----- src/default_shaders.cpp | 162 ---- src/dumbLog.cpp | 34 +- src/entity.cpp | 92 +-- src/input.cpp | 42 - src/libs.cpp | 12 - src/lights.cpp | 55 -- src/mesh.cpp | 262 ------ src/render_object.cpp | 246 ------ src/renderer.cpp | 369 --------- src/shader.cpp | 751 ++++++++++++++++++ src/shader_program.cpp | 146 ---- src/tangerine.cpp | 386 +++++++++ src/tiny_gltf.cc | 5 + src/util.cpp | 197 ----- src/util_image.cpp | 59 -- 53 files changed, 2594 insertions(+), 3725 deletions(-) delete mode 100644 examples/assimp_loading/animation_testing.cpp delete mode 100644 examples/assimp_loading/main.cpp delete mode 100644 examples/data/blender/Color Palette 140.png delete mode 100644 examples/data/blender/icosphere.bin delete mode 100644 examples/data/blender/icosphere.blend delete mode 100644 examples/data/blender/icosphere.gltf delete mode 100644 examples/data/blender/spaceship.bin delete mode 100644 examples/data/blender/spaceship.blend delete mode 100644 examples/data/blender/spaceship.gltf delete mode 100644 examples/data/icosphere.glb delete mode 100644 examples/data/spaceship.glb delete mode 100644 examples/hello_world/main.cpp create mode 100644 examples/main.cpp delete mode 100644 examples/render_groups/main.cpp delete mode 100644 examples/simple_mesh/main.cpp create mode 100644 include/GLDebug.h delete mode 100644 include/animation.h delete mode 100644 include/camera.h create mode 100644 include/dummy_shader.h delete mode 100644 include/input.h delete mode 100644 include/lights.h delete mode 100644 include/mesh.h delete mode 100644 include/platform_wait_for_vblank.h delete mode 100644 include/render_object.h delete mode 100644 include/renderer.h create mode 100644 include/shader.h delete mode 100644 include/shader_program.h create mode 100644 include/tangerine.h delete mode 100644 include/util_image.h delete mode 100644 src/camera.cpp delete mode 100644 src/default_shaders.cpp delete mode 100644 src/input.cpp delete mode 100644 src/libs.cpp delete mode 100644 src/lights.cpp delete mode 100644 src/mesh.cpp delete mode 100644 src/render_object.cpp delete mode 100644 src/renderer.cpp create mode 100644 src/shader.cpp delete mode 100644 src/shader_program.cpp create mode 100644 src/tangerine.cpp create mode 100644 src/tiny_gltf.cc delete mode 100644 src/util.cpp delete mode 100644 src/util_image.cpp diff --git a/Makefile b/Makefile index db68f31..79782d5 100644 --- a/Makefile +++ b/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 diff --git a/examples/Makefile b/examples/Makefile index 5ca0470..b54c414 100644 --- a/examples/Makefile +++ b/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) diff --git a/examples/assimp_loading/animation_testing.cpp b/examples/assimp_loading/animation_testing.cpp deleted file mode 100644 index 9b545bb..0000000 --- a/examples/assimp_loading/animation_testing.cpp +++ /dev/null @@ -1,72 +0,0 @@ - -#include // snprintf -#include // std::cout - -#include -#include -#include - -#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"; -} diff --git a/examples/assimp_loading/main.cpp b/examples/assimp_loading/main.cpp deleted file mode 100644 index 5892f8f..0000000 --- a/examples/assimp_loading/main.cpp +++ /dev/null @@ -1,85 +0,0 @@ - -#include -#include - -#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; -} - diff --git a/examples/data/blender/Color Palette 140.png b/examples/data/blender/Color Palette 140.png deleted file mode 100644 index dd9ce0c..0000000 --- a/examples/data/blender/Color Palette 140.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:455cd0343c1784781ce4ab3b843ce76b3e9477c3e6c0aa158bfd14c1276c7326 -size 572604 diff --git a/examples/data/blender/icosphere.bin b/examples/data/blender/icosphere.bin deleted file mode 100644 index fb768386b4e5a0be476482767c3f5978b0efa435..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 32640 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 Dd~aL| diff --git a/examples/data/blender/icosphere.blend b/examples/data/blender/icosphere.blend deleted file mode 100644 index 994c2c5..0000000 --- a/examples/data/blender/icosphere.blend +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a5ff61b8f94488ade07c3b2077d831ba81523381e6e99a4f471970d3e07d7e7f -size 875348 diff --git a/examples/data/blender/icosphere.gltf b/examples/data/blender/icosphere.gltf deleted file mode 100644 index 97185e8..0000000 --- a/examples/data/blender/icosphere.gltf +++ /dev/null @@ -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" - } - ] -} diff --git a/examples/data/blender/spaceship.bin b/examples/data/blender/spaceship.bin deleted file mode 100644 index 7e0afdf1ed2d519d907f0b5ae3c735b256c0c422..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 63464 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 CwC(%= diff --git a/examples/data/blender/spaceship.blend b/examples/data/blender/spaceship.blend deleted file mode 100644 index f2b19a8..0000000 --- a/examples/data/blender/spaceship.blend +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b84405cbf54587bdacb6cf02b863fe7ae4e8b2965188f9aa3e9e420487c39a28 -size 888704 diff --git a/examples/data/blender/spaceship.gltf b/examples/data/blender/spaceship.gltf deleted file mode 100644 index d1ca2e4..0000000 --- a/examples/data/blender/spaceship.gltf +++ /dev/null @@ -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" - } - ] -} diff --git a/examples/data/icosphere.glb b/examples/data/icosphere.glb deleted file mode 100644 index 73d3fe3..0000000 --- a/examples/data/icosphere.glb +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:50b0efdbf32acbf2d9191856dc882405430d5788e96059b27176c1c7c1ddec70 -size 606540 diff --git a/examples/data/spaceship.glb b/examples/data/spaceship.glb deleted file mode 100644 index 0728575..0000000 --- a/examples/data/spaceship.glb +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:78de8f432fcbfa9490f2bd4b8d0eba2af56fe3c2c1ffb67b92220b60f73647f2 -size 614084 diff --git a/examples/hello_world/main.cpp b/examples/hello_world/main.cpp deleted file mode 100644 index 1113839..0000000 --- a/examples/hello_world/main.cpp +++ /dev/null @@ -1,16 +0,0 @@ - -#include "renderer.h" - - -int -main() -{ - render_state* rs = renInit("Hello World"); - - if (rs == nullptr) - return 1; - - renDoRenderLoop(rs); - - return 0; -} diff --git a/examples/main.cpp b/examples/main.cpp new file mode 100644 index 0000000..cb62320 --- /dev/null +++ b/examples/main.cpp @@ -0,0 +1,331 @@ + +#include +#include + +#include + +#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; +} + diff --git a/examples/render_groups/main.cpp b/examples/render_groups/main.cpp deleted file mode 100644 index 633cff9..0000000 --- a/examples/render_groups/main.cpp +++ /dev/null @@ -1,233 +0,0 @@ - -#include -#include - -#include - -#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; -} - diff --git a/examples/simple_mesh/main.cpp b/examples/simple_mesh/main.cpp deleted file mode 100644 index fd67b6d..0000000 --- a/examples/simple_mesh/main.cpp +++ /dev/null @@ -1,89 +0,0 @@ - -#include - -#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; -} diff --git a/include/GLDebug.h b/include/GLDebug.h new file mode 100644 index 0000000..eb13bfc --- /dev/null +++ b/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 + +#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 + diff --git a/include/animation.h b/include/animation.h deleted file mode 100644 index 277ed0f..0000000 --- a/include/animation.h +++ /dev/null @@ -1,18 +0,0 @@ - -#pragma once - -#include - -#include "util.h" - - -struct render_object; - -struct node_animation -{ - render_object* ro; - glm::mat4* xform_array; - uint key_count; - uint cur_frame; -}; - diff --git a/include/asset.h b/include/asset.h index d0b1ba6..0d67609 100644 --- a/include/asset.h +++ b/include/asset.h @@ -4,73 +4,61 @@ #include #include -#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); diff --git a/include/camera.h b/include/camera.h deleted file mode 100644 index 4230fc4..0000000 --- a/include/camera.h +++ /dev/null @@ -1,66 +0,0 @@ - -#pragma once -#include -#include -#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); diff --git a/include/dumbLog.h b/include/dumbLog.h index 769cfa4..be27e25 100644 --- a/include/dumbLog.h +++ b/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 + +#define LOGF(level, format_str, ...) \ + dumbLogF(level, __FUNCTION__, format_str, ##__VA_ARGS__); +void dumbLogF(log_level l, const char* func, const char* fmt, ...); diff --git a/include/dummy_shader.h b/include/dummy_shader.h new file mode 100644 index 0000000..38316e7 --- /dev/null +++ b/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"; + diff --git a/include/entity.h b/include/entity.h index aadf54e..b85a78f 100644 --- a/include/entity.h +++ b/include/entity.h @@ -1,30 +1,32 @@ #pragma once -#include - #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); diff --git a/include/input.h b/include/input.h deleted file mode 100644 index 806a716..0000000 --- a/include/input.h +++ /dev/null @@ -1,23 +0,0 @@ - -#pragma once - -#include - - -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); - diff --git a/include/lights.h b/include/lights.h deleted file mode 100644 index e5cc141..0000000 --- a/include/lights.h +++ /dev/null @@ -1,34 +0,0 @@ - -#pragma once - -#include - -#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); - diff --git a/include/mesh.h b/include/mesh.h deleted file mode 100644 index 1fe81b6..0000000 --- a/include/mesh.h +++ /dev/null @@ -1,64 +0,0 @@ -/* - * mesh.h - * - wrapper for assimp http://www.assimp.org - */ - -#pragma once - -#include - -#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(); - diff --git a/include/platform_wait_for_vblank.h b/include/platform_wait_for_vblank.h deleted file mode 100644 index d11a353..0000000 --- a/include/platform_wait_for_vblank.h +++ /dev/null @@ -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 - -#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 -} - diff --git a/include/render_object.h b/include/render_object.h deleted file mode 100644 index eefe9e8..0000000 --- a/include/render_object.h +++ /dev/null @@ -1,27 +0,0 @@ - -#pragma once - -#include -#include - -#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); - diff --git a/include/renderer.h b/include/renderer.h deleted file mode 100644 index 02eeafb..0000000 --- a/include/renderer.h +++ /dev/null @@ -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 -#else - #include -#endif -#include -#include - -#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); - diff --git a/include/shader.h b/include/shader.h new file mode 100644 index 0000000..898fa9e --- /dev/null +++ b/include/shader.h @@ -0,0 +1,222 @@ + +#pragma once + +#include +#include +#include + +#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); + diff --git a/include/shader_program.h b/include/shader_program.h deleted file mode 100644 index dba1f55..0000000 --- a/include/shader_program.h +++ /dev/null @@ -1,57 +0,0 @@ - -#pragma once - -#include - -#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); diff --git a/include/tangerine.h b/include/tangerine.h new file mode 100644 index 0000000..d16958b --- /dev/null +++ b/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 + +#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); diff --git a/include/types.h b/include/types.h index 59fcbe7..e2bbdbd 100644 --- a/include/types.h +++ b/include/types.h @@ -1,16 +1,13 @@ #pragma once +#include -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; diff --git a/include/util.h b/include/util.h index 237b37f..00a94a3 100644 --- a/include/util.h +++ b/include/util.h @@ -1,118 +1,193 @@ -#pragma once +#ifndef UTIL_H +#define UTIL_H -#include #include +#include +#include +#include +#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 diff --git a/include/util_image.h b/include/util_image.h deleted file mode 100644 index 1d27d34..0000000 --- a/include/util_image.h +++ /dev/null @@ -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); - diff --git a/src/asset.cpp b/src/asset.cpp index db15512..289d294 100644 --- a/src/asset.cpp +++ b/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"; diff --git a/src/camera.cpp b/src/camera.cpp deleted file mode 100644 index 13ab4a5..0000000 --- a/src/camera.cpp +++ /dev/null @@ -1,204 +0,0 @@ - -#include - -#include - -#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); -} diff --git a/src/default_shaders.cpp b/src/default_shaders.cpp deleted file mode 100644 index 063f5dd..0000000 --- a/src/default_shaders.cpp +++ /dev/null @@ -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"; - diff --git a/src/dumbLog.cpp b/src/dumbLog.cpp index 13b7f14..a31b9be 100644 --- a/src/dumbLog.cpp +++ b/src/dumbLog.cpp @@ -3,6 +3,8 @@ #include #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(now.time_since_epoch()).count(); + u64 total_ms = std::chrono::duration_cast( + now.time_since_epoch() + ).count(); return int(total_ms % 1000); } + +#include +#include + + +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__); + } +} diff --git a/src/entity.cpp b/src/entity.cpp index 081884c..7c8f7f5 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -1,76 +1,68 @@ -#include - -#include +#include #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 (e->render_objs == nullptr) { - entFree(e); - return false; - } + if (mdl->diffuse_texture) { + e->diffuse_texture = getGLTexture(gl_ctx, mdl->diffuse_texture); - return true; -} - -void -entFree(entity* e) -{ - roFree(e->render_objs); - e->render_objs = nullptr; -} + if (!e->diffuse_texture) + return false; + } -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)); } diff --git a/src/input.cpp b/src/input.cpp deleted file mode 100644 index 627360d..0000000 --- a/src/input.cpp +++ /dev/null @@ -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); -} - diff --git a/src/libs.cpp b/src/libs.cpp deleted file mode 100644 index e04c0b5..0000000 --- a/src/libs.cpp +++ /dev/null @@ -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" - diff --git a/src/lights.cpp b/src/lights.cpp deleted file mode 100644 index 340689a..0000000 --- a/src/lights.cpp +++ /dev/null @@ -1,55 +0,0 @@ - -#include // 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; -} - diff --git a/src/mesh.cpp b/src/mesh.cpp deleted file mode 100644 index 6020251..0000000 --- a/src/mesh.cpp +++ /dev/null @@ -1,262 +0,0 @@ -#include - -#if 0 -#include -#include -#include -#endif - -#include -#include -#include - -#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 - diff --git a/src/render_object.cpp b/src/render_object.cpp deleted file mode 100644 index d67a1c5..0000000 --- a/src/render_object.cpp +++ /dev/null @@ -1,246 +0,0 @@ - -#include - -#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); -} - diff --git a/src/renderer.cpp b/src/renderer.cpp deleted file mode 100644 index 2d28e9b..0000000 --- a/src/renderer.cpp +++ /dev/null @@ -1,369 +0,0 @@ - -#if defined (_WIN32) - #include -#else - #include -#endif -#include -#include -#include - -#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; -} diff --git a/src/shader.cpp b/src/shader.cpp new file mode 100644 index 0000000..dbfabfc --- /dev/null +++ b/src/shader.cpp @@ -0,0 +1,751 @@ + +#include +#include +#include +#include +#include +#include + +#include + +#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; +} + diff --git a/src/shader_program.cpp b/src/shader_program.cpp deleted file mode 100644 index f2d94cb..0000000 --- a/src/shader_program.cpp +++ /dev/null @@ -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; -} - diff --git a/src/tangerine.cpp b/src/tangerine.cpp new file mode 100644 index 0000000..f799512 --- /dev/null +++ b/src/tangerine.cpp @@ -0,0 +1,386 @@ + +#include + +#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; +} + diff --git a/src/tiny_gltf.cc b/src/tiny_gltf.cc new file mode 100644 index 0000000..e5a9eba --- /dev/null +++ b/src/tiny_gltf.cc @@ -0,0 +1,5 @@ + +#define STB_IMAGE_IMPLEMENTATION +#define STB_IMAGE_WRITE_IMPLEMENTATION +#define TINYGLTF_IMPLEMENTATION +#include "tiny_gltf.h" diff --git a/src/util.cpp b/src/util.cpp deleted file mode 100644 index 4ab78f8..0000000 --- a/src/util.cpp +++ /dev/null @@ -1,197 +0,0 @@ - -#include -#include -#include -#include -#include - -#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; -} - diff --git a/src/util_image.cpp b/src/util_image.cpp deleted file mode 100644 index ed44be5..0000000 --- a/src/util_image.cpp +++ /dev/null @@ -1,59 +0,0 @@ - -#include - -#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; -}