diff --git a/.gitattributes b/.gitattributes index 48ea584..d471836 100644 --- a/.gitattributes +++ b/.gitattributes @@ -4,3 +4,5 @@ data/textured_cube.gltf filter=lfs diff=lfs merge=lfs -text data/Color[[:space:]]Palette[[:space:]]140.png filter=lfs diff=lfs merge=lfs -text data/shader.frag filter=lfs diff=lfs merge=lfs -text data/shader.vert filter=lfs diff=lfs merge=lfs -text +*.frag filter=lfs diff=lfs merge=lfs -text +*.vert filter=lfs diff=lfs merge=lfs -text diff --git a/.gitignore b/.gitignore index c479d3b..0ff805c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ tags *.d -*.bin build/ +shader_testing diff --git a/Makefile b/Makefile index c452c1d..a3b74f9 100644 --- a/Makefile +++ b/Makefile @@ -1,15 +1,35 @@ SHELL = /bin/sh CXX = g++ -CXXFLAGS = -std=c++11 -g -ggdb3 -Wall -I/usr/include/SDL2 +CXXFLAGS = -std=c++11 -g -ggdb3 -Wall -Isrc/ -I/usr/include/SDL2 LDFLAGS = -lSDL2 -lGLEW -lGL +OBJDIR = build +SRCDIR = src BIN = shader_testing -all: - $(CXX) $(CXXFLAGS) -MMD main.cpp -o $(BIN) $(LDFLAGS) + +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: $(BIN) .PHONY: all +-include $(OBJDIR)/*.d + +$(OBJECTS): $(OBJDIR)/%.o : $(SRCDIR)/%.cpp + $(CXX) $(CXXFLAGS) -c -MMD $< -o $@ + +$(NDBG_OBJS): $(OBJDIR)/%.o : $(SRCDIR)/%.cc + $(CXX) -std=c++11 -Wall -Isrc/ -c $< -o $@ + +$(BIN): $(OBJECTS) $(NDBG_OBJS) + $(CXX) -o $@ $(LDFLAGS) $^ + clean: rm -rf $(BIN) + rm -rf build/* .PHONY: clean diff --git a/main.cpp b/main.cpp deleted file mode 100644 index f35dc02..0000000 --- a/main.cpp +++ /dev/null @@ -1,349 +0,0 @@ - -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include "shader_testing.h" - -#include "dumpShader.inl" - - -SDL_Window *g_window = 0; -SDL_GLContext g_glContext; -SDL_DisplayMode g_display_mode; - -transforms g_xforms; -mesh g_cube_mesh; -const u32 NUM_CUBES = 4; -gl_mesh g_gl_cubes[NUM_CUBES]; -glm::vec3 g_cube_locs[NUM_CUBES] = { - glm::vec3(-10, 10, 0), - glm::vec3(-10, -10, 0), - glm::vec3( 10, 10, 0), - glm::vec3( 10, -10, 0), -}; - - -void -openglDebugCallback(GLenum source, - GLenum type, - GLuint id, - GLenum severity, - GLsizei length, - const GLchar* message, - const void* userParam) -{ - std::cout << ((type == GL_DEBUG_TYPE_ERROR) ? "Error" : "Debug") - << (type == GL_DEBUG_TYPE_ERROR ? "** GL Error **" : "") - << ", type: " << type - << ", severity: " << severity - << ", message: " << message << "\n"; -} - -bool -init() -{ - g_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, &g_display_mode); - - g_glContext = SDL_GL_CreateContext(g_window); - - if (!g_glContext) { - 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 g_window != nullptr; -} - -const std::string -dumpTextFile(const char* filepath) -{ - std::ifstream fs { filepath }; - std::string s { - std::istreambuf_iterator(fs), - std::istreambuf_iterator()}; - - if (!fs || !fs.good()) - std::cout << "error reading file, " << filepath << "\n"; - - return s; -} - -bool -loadShaderProgram(const char* vs, const char* fs, shader* s) -{ - std::string vert = dumpTextFile(vs); - std::string frag = dumpTextFile(fs); - - if (vert.size() > 0 && frag.size() > 0) { - const char* vert_c = vert.c_str(); - const char* frag_c = frag.c_str(); - - GLuint vs_id = glCreateShader(GL_VERTEX_SHADER); - GLuint fs_id = glCreateShader(GL_FRAGMENT_SHADER); - glShaderSource(vs_id, 1, &vert_c, NULL); - glShaderSource(fs_id, 1, &frag_c, NULL); - glCompileShader(vs_id); - glCompileShader(fs_id); - s->prog_id = glCreateProgram(); - glAttachShader(s->prog_id, vs_id); - glAttachShader(s->prog_id, fs_id); - - glLinkProgram(s->prog_id); - - glDetachShader(s->prog_id, vs_id); - glDetachShader(s->prog_id, fs_id); - glDeleteShader(vs_id); - glDeleteShader(fs_id); - - GLint is_linked = 0; - glGetProgramiv(s->prog_id, GL_LINK_STATUS, &is_linked); - return (is_linked == GL_TRUE); - } - - return false; -} - -transforms -initXformUBO(shader* s, - float fov, - float near_clip_plane, - float aspect_ratio, - glm::vec3 cam_pos, - glm::vec3 look_pos) -{ - transforms xforms; - xforms.view_xform = glm::lookAt(cam_pos, look_pos, glm::vec3(0, 1, 0)); - xforms.proj_xform = glm::infinitePerspective( - glm::radians(fov), aspect_ratio, near_clip_plane); - xforms.normal_xform = glm::mat4(1); - - glGenBuffers(1, &s->xforms_ubo_id); - glBindBufferBase(GL_UNIFORM_BUFFER, 0, s->xforms_ubo_id); - glBindBuffer(GL_UNIFORM_BUFFER, s->xforms_ubo_id); - glBufferData(GL_UNIFORM_BUFFER, sizeof(xforms), &xforms, GL_STATIC_DRAW); - glBindBuffer(GL_UNIFORM_BUFFER, 0); - - return xforms; -}; - -mesh -initCubeMesh() -{ - mesh m = {0}; - m.num_vertices = 8; - m.vertices = (glm::vec3*) std::calloc(m.num_vertices, sizeof(glm::vec3)); - m.vertices[0] = { -1, 1, -1 }; - m.vertices[1] = { -1, -1, -1 }; - m.vertices[2] = { 1, -1, -1 }; - m.vertices[3] = { 1, 1, -1 }; - m.vertices[4] = { -1, 1, 1 }; - m.vertices[5] = { -1, -1, 1 }; - m.vertices[6] = { 1, -1, 1 }; - m.vertices[7] = { 1, 1, 1 }; - - m.num_indices = 36; // 6 sides, 2 tris per side, 3 verts per tri - m.indices = (u32*) std::calloc(m.num_indices, sizeof(u32)); - u32 indices[36] = { - 0, 1, 2, 0, 2, 3, - 3, 2, 6, 3, 6, 7, - 7, 6, 5, 7, 5, 4, - 4, 5, 0, 4, 1, 0, - 0, 3, 4, 0, 3, 7, - 1, 2, 5, 2, 6, 5 - }; - std::memcpy(m.indices, indices, m.num_indices * sizeof(u32)); - - return m; -} - -gl_mesh -loadGLMesh(shader* s, const mesh& m, GLenum draw_mode, const glm::vec3& pos) -{ - gl_mesh gm = {0}; - gm.num_indices = m.num_indices; - gm.draw_mode = draw_mode; - - glUseProgram(s->prog_id); - glGenVertexArrays(1, &gm.vao_id); - glBindVertexArray(gm.vao_id); - - gm.model_xform = (glm::mat4*) std::calloc(1, sizeof(glm::mat4)); - *gm.model_xform = glm::translate(glm::mat4(1), pos); - - gm.model_xform_id = glGetUniformLocation(s->prog_id, "model_xform"); - glUniformMatrix4fv( - gm.model_xform_id, 1, GL_FALSE, (float*) gm.model_xform); - - glGenBuffers(1, &gm.vert_buf_id); - glBindBuffer(GL_ARRAY_BUFFER, gm.vert_buf_id); - glBufferData(GL_ARRAY_BUFFER, - m.num_vertices * 3 * sizeof(GLfloat), - m.vertices, - GL_STATIC_DRAW); - glVertexAttribPointer(POSITION, 3, GL_FLOAT, GL_FALSE, 0, 0); - glEnableVertexAttribArray(POSITION); - - glGenBuffers(1, &gm.idx_buf_id); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, gm.idx_buf_id); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, - m.num_indices * sizeof(GLuint), - m.indices, - GL_STATIC_DRAW); - - glBindVertexArray(0); - glUseProgram(0); - - return gm; -} - -void -loop(shader* s, gl_mesh* mesh_arr, u32 num_meshes) -{ - u32 delay = 60; - u32 frame_start, frame_time; - bool running = true; - SDL_Event e; - - while (running) { - frame_start = SDL_GetTicks(); - - while (SDL_PollEvent(&e)) { - if (e.type == SDL_QUIT || - (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_ESCAPE)) - { - running = false; - break; - } - } - - glClearColor(0.2, 0.2, 0.2, 1); - glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT); - glUseProgram(s->prog_id); - glBindBuffer(GL_UNIFORM_BUFFER, s->xforms_ubo_id); - - for (u32 i = 0; i < num_meshes; i++) { - const gl_mesh& m = mesh_arr[i]; - *m.model_xform = glm::rotate( - *m.model_xform, (float) M_PI / 60, glm::vec3(0, 1, 0)); - glBindVertexArray(m.vao_id); - glUniformMatrix4fv( - m.model_xform_id, 1, GL_FALSE, (float*) m.model_xform); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m.idx_buf_id); - glDrawElements(m.draw_mode, m.num_indices, GL_UNSIGNED_INT, 0); - glBindVertexArray(0); - } - - SDL_GL_SwapWindow(g_window); - glUseProgram(0); - frame_time = SDL_GetTicks() - frame_start; - - if (delay > frame_time) - SDL_Delay(delay - frame_time); - } -} - -void -quit(shader* s) -{ - glDeleteProgram(s->prog_id); - SDL_GL_DeleteContext(g_glContext); - SDL_DestroyWindow(g_window); - SDL_Quit(); - - for (u32 i = 0; i < NUM_CUBES; i++) { - gl_mesh& c = g_gl_cubes[i]; - - if (c.model_xform != nullptr) - free(c.model_xform); - } - - mesh& cm = g_cube_mesh; - - if (cm.vertices != nullptr) - free(cm.vertices); - if (cm.normals != nullptr) - free(cm.normals); - if (cm.uvs != nullptr) - free(cm.uvs); - if (cm.colors != nullptr) - free(cm.colors); - if (cm.indices != nullptr) - free(cm.indices); -} - -int -main() -{ - if (!init()) - return 1; - - shader s = {0}; - - if (loadShaderProgram("data/shader.vert", "data/shader.frag", &s)) { - dumpShader(s.prog_id); - g_xforms = initXformUBO(&s); - g_cube_mesh = initCubeMesh(); - - for (u32 i = 0; i < NUM_CUBES; i++) { - g_gl_cubes[i] = - loadGLMesh(&s, g_cube_mesh, GL_TRIANGLES, g_cube_locs[i]); - assert(g_gl_cubes[i].vao_id != 0); - } - - loop(&s, g_gl_cubes, NUM_CUBES); - quit(&s); - return 0; - } - - std::cout << "error loading shader program\n"; - quit(&s); - return 1; -} - diff --git a/shader_testing b/shader_testing deleted file mode 100755 index 7b01d0e..0000000 Binary files a/shader_testing and /dev/null differ diff --git a/shader_testing.h b/shader_testing.h deleted file mode 100644 index 785acff..0000000 --- a/shader_testing.h +++ /dev/null @@ -1,96 +0,0 @@ - -#pragma once - -#include -#include - - -typedef uint32_t u32; - - -struct gl_uniform -{ - GLenum id; - GLenum data_type; -}; - -struct gl_buffer -{ - GLuint id; - GLenum data_type; -}; - -struct shader -{ - GLuint prog_id; - GLuint xforms_ubo_id; -}; - -struct transforms -{ - glm::mat4 view_xform; - glm::mat4 proj_xform; - glm::mat4 normal_xform; -}; - -enum glsl_layout -{ - POSITION, - NORMAL, - UV, - COLOR -}; - -struct mesh -{ - u32 num_vertices; - u32 num_indices; - glm::vec3* vertices; - glm::vec3* normals; - glm::vec3* uvs; - glm::vec3* colors; - u32* indices; -}; - -struct gl_mesh -{ - u32 num_indices; - GLuint vao_id; - GLuint tex_id; - GLenum draw_mode; - - glm::mat4* model_xform; - GLuint model_xform_id; - - GLuint vert_buf_id; - GLuint norm_buf_id; - GLuint uv_buf_id; - GLuint clr_buf_id; - GLuint idx_buf_id; -}; - -struct node; -struct entity; -struct light; -struct animation; - - -const float DEFAULT_FOV = 60.f; -const float NEAR_CLIP_PLANE = 5.f; -const float DEFAULT_ASPECT_RATIO = 16.f / 9.f; -const glm::vec3 DEFAULT_CAM_POS = { 0, 0, -30.f }; -const glm::vec3 DEFAULT_LOOK_POS = { 0, 0, 0 }; - - -bool loadShaderProgram(const char* vs, const char* fs, shader* s); -transforms initXformUBO(shader* s, - float fov = DEFAULT_FOV, - float near_clip_plane = NEAR_CLIP_PLANE, - float aspect_ratio = DEFAULT_ASPECT_RATIO, - glm::vec3 cam_pos = DEFAULT_CAM_POS, - glm::vec3 look_pos = DEFAULT_LOOK_POS); -mesh initCubeMesh(); -gl_mesh loadGLMesh(shader* s, const mesh& m, const glm::vec3& pos); -void loop(shader* s, gl_mesh* mesh_arr, u32 num_meshes); -void quit(shader* s); - diff --git a/src/asset.cpp b/src/asset.cpp new file mode 100644 index 0000000..b3b9391 --- /dev/null +++ b/src/asset.cpp @@ -0,0 +1,442 @@ + +#include +#include + +#include +#include "tiny_gltf.h" + +#include "asset.h" +#include "dumbLog.h" + + +//----------------- +// 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 + +memory_arena* +arenaInit(size_t initial_size) +{ + uint sz = sizeof(memory_arena); + memory_arena* arena = + (memory_arena*) std::calloc(initial_size + sz, sizeof(u8)); + arena->head = arena->next_free = (uint8_t*) arena + sz; + arena->max_size = initial_size; + return arena; +} + +void +arenaFree(memory_arena*& arena) +{ + if (arena != nullptr) { + std::free(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; +} + + +// forward declarations +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, + const tinygltf::Node& node, + const tinygltf::Model& t_mdl); + + +// interface + +model_assets* +assetInitModelBlock(memory_arena* arena, uint asset_count) +{ + model_assets* assets = + (model_assets*) arenaAllocateBlock(arena, sizeof(model_assets)); + assets->models = + (model*) arenaAllocateBlock(arena, asset_count * sizeof(model)); + assets->max = asset_count; + + return assets; +} + +texture_assets* +assetInitTextureBlock(memory_arena* arena, uint asset_count) +{ + 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; + + return assets; +} + +// FIXME: move to internal when finished +util_image* +copyDiffuseTexture(texture_assets* textures, + memory_arena* arena, + const tinygltf::Model& t_mdl) +{ + // NOTE: assuming material[0] since we're using pallete texture + assert(t_mdl.materials.size() == 1 + && t_mdl.textures.size() == 1 + && t_mdl.images.size() == 1 + && t_mdl.images[0].image.size() > 0); + tinygltf::Image t_img = t_mdl.images[0]; + + // TODO: re-alloc array when out of space + assert(textures->count < textures->max && arena != nullptr); + util_image* dtex = &textures->images[textures->count]; + textures->count++; + + 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 = (u8*) arenaAllocateBlock(arena, dtex->data_len); + // FIXME: overwriting memory here... also fix in libTangerine + std::strncpy(dtex->file_path, t_img.uri.c_str(), t_img.uri.size()); + 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) +{ + tinygltf::Model t_mdl; + tinygltf::TinyGLTF gltf_ctx; + std::string err; + std::string warn; + + LOG(Info) << "Loading model: " << filename << "\n"; + + if (!gltf_ctx.LoadASCIIFromFile(&t_mdl, &err, &warn, filename)) { + LOG(Error) << "Error loading file: " << filename + << " , msg: " << err << "\n"; + return nullptr; + } +#if 0 + dumpNodes(t_mdl); +#endif + // NOTE: assume we're working with a single buffer + assert(t_mdl.buffers.size() == 1); + model* mdl = initModel(assets, arena, t_mdl, filename); +#if 0 + // FIXME: probably a bug here with overwriting arena memory + mdl->diffuse_texture = copyDiffuseTexture(textures, arena, t_mdl); + if (mdl->diffuse_texture == nullptr) { + LOG(Error) << "Error Loading diffuse texture\n"; + // TODO: reclaim arena memory + return nullptr; + } +#endif + + 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)) + { + LOG(Error) << "Error parsing node\n"; + return nullptr; + } + } + } + + return mdl; +} + +model* +assetGetCached(model_assets* assets, uint64_t path_hash) +{ + for (uint i = 0; i < assets->count; i++) { + if (assets->models[i].filepath_hash == path_hash) + return &assets->models[i]; + } + + LOG(Debug) << "asset not cached: " << path_hash << "\n"; + return nullptr; +} + + +// internal + +model* +initModel(model_assets* assets, + memory_arena* arena, + tinygltf::Model t_mdl, + const char* filename) +{ + // 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); + + return mdl; +} + +bool +copyBuffer(uint8_t*& buffer_ref, + memory_arena* arena, + int acc_idx, + const tinygltf::Model& t_mdl) +{ + const tinygltf::Accessor& acc = t_mdl.accessors[acc_idx]; + const tinygltf::BufferView& bv = t_mdl.bufferViews[acc.bufferView]; + const tinygltf::Buffer& t_buf = t_mdl.buffers[bv.buffer]; + + // TODO: clean up validation + if (bv.target == TINYGLTF_TARGET_ARRAY_BUFFER) { + assert(acc.componentType == TINYGLTF_COMPONENT_TYPE_FLOAT + && (acc.type == TINYGLTF_TYPE_VEC3 + || acc.type == TINYGLTF_TYPE_VEC2)); + } else if (bv.target == TINYGLTF_TARGET_ELEMENT_ARRAY_BUFFER) { + assert(acc.type == TINYGLTF_TYPE_SCALAR + && acc.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT); + } else { + LOG(Error) << "unknown target\n"; + return false; + } + + assert(bv.byteStride == 0); + buffer_ref = (uint8_t*) arenaAllocateBlock(arena, bv.byteLength); + std::memcpy(buffer_ref, &t_buf.data[bv.byteOffset], bv.byteLength); + + return buffer_ref != nullptr; +} + +// FIXME: need to implement tree structure for blender models to work properly +glm::mat4* +parseNodeTransform(memory_arena* 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)); + *xform = glm::mat4(1.0f); + *xform = glm::rotate(*xform, (float) node->rotation[3], + glm::vec3((float) node->rotation[0], + (float) node->rotation[1], + (float) node->rotation[2]) + ); + *xform = glm::scale(*xform, + glm::vec3((float) node->scale[0], + (float) node->scale[0], + (float) node->scale[0]) + ); + *xform = glm::translate(*xform, + glm::vec3((float) node->translation[0], + (float) node->translation[0], + (float) node->translation[0]) + ); + + return xform; + } + + LOG(Error) << "Unknown transform\n"; + return nullptr; +} + +bool +parseMeshNode(mesh* m, + texture_assets* textures, + memory_arena* arena, + const tinygltf::Node& node, + const tinygltf::Model& t_mdl) +{ + tinygltf::Mesh t_mesh = t_mdl.meshes[node.mesh]; + // NOTE: assume only 1 primitive object per mesh + assert(t_mdl.meshes[node.mesh].primitives.size() == 1); + tinygltf::Primitive prim = t_mesh.primitives[0]; + + // NOTE: verify assumptions about input + assert(prim.attributes.find("POSITION") != prim.attributes.end() + && prim.attributes.find("NORMAL") != prim.attributes.end() + && prim.attributes.find("TEXCOORD_0") != prim.attributes.end() + && prim.indices >= 0); + + const tinygltf::Accessor& vert_acc = + t_mdl.accessors[prim.attributes["POSITION"]]; + const tinygltf::Accessor& index_acc = t_mdl.accessors[prim.indices]; + m->num_vertices = vert_acc.count; + m->num_indices = index_acc.count; + 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 = glm::mat4(1.0f); +#else + m->xform = parseNodeTransform(arena, &node); +#endif + + if (m->xform != nullptr + && copyBuffer((uint8_t*&) m->vertices, arena, + prim.attributes["POSITION"], t_mdl) + && copyBuffer((uint8_t*&) m->normals, arena, + prim.attributes["NORMAL"], t_mdl) + && copyBuffer((uint8_t*&) m->uvs, arena, + prim.attributes["TEXCOORD_0"], t_mdl) + && copyBuffer((uint8_t*&) m->indices, arena, + prim.indices, t_mdl)) + { + return true; + } + + return false; +} + +const char* +getTargetStr(int target) +{ + switch (target) { + case 34962: return "GL_ARRAY_BUFFER"; + case 34963: return "GL_ELEMENT_ARRAY_BUFFER"; + default: return "UNKNOWN"; + } +} + +const char* +getElementType(int type) +{ + switch (type) { + case 2: return "TINYGLTF_TYPE_VEC2"; + case 3: return "TINYGLTF_TYPE_VEC3"; + case 4: return "TINYGLTF_TYPE_VEC4"; + case 65: return "TINYGLTF_TYPE_SCALAR"; + default: return "UNKNOWN"; + } +} + +const char* +getComponentType(int componentType) +{ + switch (componentType) { + case 5123: return "TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT"; + case 5124: return "TINYGLTF_COMPONENT_TYPE_INT"; + case 5125: return "TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT"; + case 5126: return "TINYGLTF_COMPONENT_TYPE_FLOAT"; + case 5130: return "TINYGLTF_COMPONENT_TYPE_DOUBLE"; + default: return "UNKOWN"; } +} + +const char* +getDrawMode(int drawMode) +{ + switch (drawMode) { + case 0: return "TINYGLTF_MODE_POINTS"; + case 1: return "TINYGLTF_MODE_LINE"; + case 2: return "TINYGLTF_MODE_LINE_LOOP"; + case 3: return "TINYGLTF_MODE_LINE_STRIP"; + case 4: return "TINYGLTF_MODE_TRIANGLES"; + case 5: return "TINYGLTF_MODE_TRIANGLE_STRIP"; + case 6: return "TINYGLTF_MODE_TRIANGLE_FAN"; + default: return "UNKOWN MODE"; + } +} + +void +dumpBuffer(tinygltf::Model model, 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]; + + LOG(Debug) << "-----------------------\n"; + LOG(Debug) << "buf idx: " << bv_idx << "\n"; + LOG(Debug) << "buf target: " << getTargetStr(bv.target) << "\n"; + LOG(Debug) << "buf len: " << bv.byteLength << "\n"; + LOG(Debug) << "buf offset: " << bv.byteOffset << "\n"; + LOG(Debug) << "buf stride: " << bv.byteStride << "\n"; + LOG(Debug) << "acc component type: " + << getComponentType(acc.componentType) << "\n"; + LOG(Debug) << "acc element type: " + << getElementType(acc.type) << "\n"; + +} + +void +dumpNodes(tinygltf::Model t_mdl) +{ + for (tinygltf::Node node : t_mdl.nodes) { + LOG(Debug) << "##################\n"; + LOG(Debug) << "node name: " << node.name << "\n"; + LOG(Debug) << "node mesh idx: " << node.mesh << "\n"; + + if (node.mesh >= 0) { + tinygltf::Mesh t_mesh = t_mdl.meshes[node.mesh]; + LOG(Debug) << "node mesh name: " << t_mesh.name << "\n"; + // NOTE: assume only 1 primitive object per mesh + assert(t_mdl.meshes[node.mesh].primitives.size() == 1); + tinygltf::Primitive prim = t_mesh.primitives[0]; + LOG(Debug) << "node draw mode: " << getDrawMode(prim.mode) << "\n"; + + for (auto& att : prim.attributes) { + LOG(Debug) << "dumping buffer: " << att.first << "\n"; + dumpBuffer(t_mdl, t_mdl.accessors[att.second]); + } + + LOG(Debug) << "dumping index buffer\n"; + dumpBuffer(t_mdl, t_mdl.accessors[prim.indices]); + } else { + LOG(Debug) << "Not a mesh node\n"; + } + } +} + diff --git a/src/asset.h b/src/asset.h new file mode 100644 index 0000000..6bcfa0b --- /dev/null +++ b/src/asset.h @@ -0,0 +1,113 @@ + +#pragma once + +#include +#include + + +typedef uint8_t u8; +typedef int32_t i32; +typedef uint64_t u64; + +//----------------- +// Hashing + +// NOTE: FNV1a hashing algorithm http://www.isthe.com/chongo/tech/comp/fnv/ +#define FNV1_64_INIT ((u64) 0xcbf29ce484222325ULL) +#define FNV_64_PRIME ((u64) 0x100000001b3ULL) +u64 +utilFNV64a_str(const char *str, u64 hval = FNV1_64_INIT); + +//----------------- +// Memory allocation + +struct memory_arena +{ + size_t max_size; + void* head; + void* next_free; +}; + +#define DEFAULT_ARENA_SIZE 10 * 1024 * 1024 // 10MB +memory_arena* arenaInit(size_t initial_size = DEFAULT_ARENA_SIZE); + +void arenaFree(memory_arena*& arena); + +uint arenaGetFreeSize(memory_arena* arena); + +void* arenaAllocateBlock(memory_arena* arena, size_t block_size); + +// NOTE: wrapper for stb_image +struct util_image +{ + i32 w; + i32 h; + i32 bits_per_channel; + i32 num_channels; + uint data_len; + u8* pixels; + u64 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]; +}; + + +// NOTE: wrapper for tinygltf https://github.com/syoyo/tinygltf +// https://github.com/KhronosGroup/glTF + +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; + glm::vec3* vertices; + glm::vec3* normals; + glm::vec2* uvs; + glm::vec3* colors; + uint* indices; + glm::mat4* xform; +}; + +#define MAX_PATH_SIZE 256 +struct model +{ + char* filepath; + u64 filepath_hash; + uint num_meshes; + mesh* meshes; + util_image* diffuse_texture; +}; + +struct model_assets +{ + model* models; + uint count; + uint max; +}; + +struct texture_assets +{ + util_image* images; + uint count; + uint max; +}; + + +// 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* +assetGetCached(model_assets* assets, u64 path_hash); + diff --git a/src/dumbLog.cpp b/src/dumbLog.cpp new file mode 100644 index 0000000..13b7f14 --- /dev/null +++ b/src/dumbLog.cpp @@ -0,0 +1,38 @@ + +#include +#include + +#include "dumbLog.h" + +void dumbLog::setOutputStream(std::ostream* out) +{ + this->OUT = out; +} + +const char* +dumbLog::logLevelToString(log_level level) +{ + switch (level) { + case log_level::Error: return "Error"; + case log_level::Warning: return "Warning"; + case log_level::Info: return "Info"; + default: return "Potato"; + } +}; + +std::tm* +dumbLog::getCurrentTime() +{ + auto now = std::chrono::system_clock::now(); + auto t_c = std::chrono::system_clock::to_time_t(now); + return std::localtime(&t_c); +} + +int +dumbLog::getCurrentMS() +{ + auto now = std::chrono::system_clock::now(); + long long total_ms = std::chrono::duration_cast(now.time_since_epoch()).count(); + return int(total_ms % 1000); +} + diff --git a/src/dumbLog.h b/src/dumbLog.h new file mode 100644 index 0000000..769cfa4 --- /dev/null +++ b/src/dumbLog.h @@ -0,0 +1,30 @@ + +#pragma once + +#include +#include + + +enum log_level { + Error, + Warning, + Info, + Debug +}; + +struct dumbLog +{ + std::ostream* OUT = &std::cout; + void setOutputStream(std::ostream* out); + const char* logLevelToString(log_level level); + std::tm* getCurrentTime(); + int getCurrentMS(); +}; + +static dumbLog logger; + +#define LOG(level) *logger.OUT \ + << std::put_time(logger.getCurrentTime(), "%F %T.") << logger.getCurrentMS() << " " \ + << "[" << logger.logLevelToString(level) << "] " \ + << "(" << __FUNCTION__ << ") " + diff --git a/dumpShader.inl b/src/dumpShader.inl similarity index 86% rename from dumpShader.inl rename to src/dumpShader.inl index ed44f56..a84d0b0 100644 --- a/dumpShader.inl +++ b/src/dumpShader.inl @@ -35,15 +35,15 @@ dumpShader(GLuint prog_id) for (int i = 0; i < active_uniforms; i++) { glGetActiveUniform(prog_id, i, sizeof(uni_name), &length, &size, &type, uni_name); - printf("uniform idx: %d, len: %d, type: %s, name: %s \n", - i, length, gl_enum_to_string(type), uni_name); + printf("uniform idx: %d, type: %s, name: %s \n", + i, gl_enum_to_string(type), uni_name); } for (int i = 0; i < active_attribs; i++) { glGetActiveAttrib(prog_id, i, sizeof(uni_name), &length, &size, &type, uni_name); - printf("attribute idx: %d, len: %d, type: %s, name: %s \n", - i, length, gl_enum_to_string(type), uni_name); + printf("attribute idx: %d, type: %s, name: %s \n", + i, gl_enum_to_string(type), uni_name); } } @@ -54,6 +54,7 @@ gl_enum_to_string(GLenum e) switch (e) { case 0x8b5c: return "GL_FLOAT_MAT4"; case 0x8b51: return "GL_FLOAT_VEC3"; + case 0x8B52: return "GL_FLOAT_VEC4"; default: return "???"; } diff --git a/src/json.hpp b/src/json.hpp new file mode 100644 index 0000000..c9af0be --- /dev/null +++ b/src/json.hpp @@ -0,0 +1,20406 @@ +/* + __ _____ _____ _____ + __| | __| | | | JSON for Modern C++ +| | |__ | | | | | | version 3.5.0 +|_____|_____|_____|_|___| https://github.com/nlohmann/json + +Licensed under the MIT License . +SPDX-License-Identifier: MIT +Copyright (c) 2013-2018 Niels Lohmann . + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#ifndef NLOHMANN_JSON_HPP +#define NLOHMANN_JSON_HPP + +#define NLOHMANN_JSON_VERSION_MAJOR 3 +#define NLOHMANN_JSON_VERSION_MINOR 5 +#define NLOHMANN_JSON_VERSION_PATCH 0 + +#include // all_of, find, for_each +#include // assert +#include // and, not, or +#include // nullptr_t, ptrdiff_t, size_t +#include // hash, less +#include // initializer_list +#include // istream, ostream +#include // random_access_iterator_tag +#include // accumulate +#include // string, stoi, to_string +#include // declval, forward, move, pair, swap + +// #include +#ifndef NLOHMANN_JSON_FWD_HPP +#define NLOHMANN_JSON_FWD_HPP + +#include // int64_t, uint64_t +#include // map +#include // allocator +#include // string +#include // vector + +/*! +@brief namespace for Niels Lohmann +@see https://github.com/nlohmann +@since version 1.0.0 +*/ +namespace nlohmann +{ +/*! +@brief default JSONSerializer template argument + +This serializer ignores the template arguments and uses ADL +([argument-dependent lookup](https://en.cppreference.com/w/cpp/language/adl)) +for serialization. +*/ +template +struct adl_serializer; + +template class ObjectType = + std::map, + template class ArrayType = std::vector, + class StringType = std::string, class BooleanType = bool, + class NumberIntegerType = std::int64_t, + class NumberUnsignedType = std::uint64_t, + class NumberFloatType = double, + template class AllocatorType = std::allocator, + template class JSONSerializer = + adl_serializer> +class basic_json; + +/*! +@brief JSON Pointer + +A JSON pointer defines a string syntax for identifying a specific value +within a JSON document. It can be used with functions `at` and +`operator[]`. Furthermore, JSON pointers are the base for JSON patches. + +@sa [RFC 6901](https://tools.ietf.org/html/rfc6901) + +@since version 2.0.0 +*/ +template +class json_pointer; + +/*! +@brief default JSON class + +This type is the default specialization of the @ref basic_json class which +uses the standard template types. + +@since version 1.0.0 +*/ +using json = basic_json<>; +} // namespace nlohmann + +#endif + +// #include + + +// This file contains all internal macro definitions +// You MUST include macro_unscope.hpp at the end of json.hpp to undef all of them + +// exclude unsupported compilers +#if !defined(JSON_SKIP_UNSUPPORTED_COMPILER_CHECK) + #if defined(__clang__) + #if (__clang_major__ * 10000 + __clang_minor__ * 100 + __clang_patchlevel__) < 30400 + #error "unsupported Clang version - see https://github.com/nlohmann/json#supported-compilers" + #endif + #elif defined(__GNUC__) && !(defined(__ICC) || defined(__INTEL_COMPILER)) + #if (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) < 40800 + #error "unsupported GCC version - see https://github.com/nlohmann/json#supported-compilers" + #endif + #endif +#endif + +// disable float-equal warnings on GCC/clang +#if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wfloat-equal" +#endif + +// disable documentation warnings on clang +#if defined(__clang__) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wdocumentation" +#endif + +// allow for portable deprecation warnings +#if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) + #define JSON_DEPRECATED __attribute__((deprecated)) +#elif defined(_MSC_VER) + #define JSON_DEPRECATED __declspec(deprecated) +#else + #define JSON_DEPRECATED +#endif + +// allow to disable exceptions +#if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) && !defined(JSON_NOEXCEPTION) + #define JSON_THROW(exception) throw exception + #define JSON_TRY try + #define JSON_CATCH(exception) catch(exception) + #define JSON_INTERNAL_CATCH(exception) catch(exception) +#else + #define JSON_THROW(exception) std::abort() + #define JSON_TRY if(true) + #define JSON_CATCH(exception) if(false) + #define JSON_INTERNAL_CATCH(exception) if(false) +#endif + +// override exception macros +#if defined(JSON_THROW_USER) + #undef JSON_THROW + #define JSON_THROW JSON_THROW_USER +#endif +#if defined(JSON_TRY_USER) + #undef JSON_TRY + #define JSON_TRY JSON_TRY_USER +#endif +#if defined(JSON_CATCH_USER) + #undef JSON_CATCH + #define JSON_CATCH JSON_CATCH_USER + #undef JSON_INTERNAL_CATCH + #define JSON_INTERNAL_CATCH JSON_CATCH_USER +#endif +#if defined(JSON_INTERNAL_CATCH_USER) + #undef JSON_INTERNAL_CATCH + #define JSON_INTERNAL_CATCH JSON_INTERNAL_CATCH_USER +#endif + +// manual branch prediction +#if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) + #define JSON_LIKELY(x) __builtin_expect(!!(x), 1) + #define JSON_UNLIKELY(x) __builtin_expect(!!(x), 0) +#else + #define JSON_LIKELY(x) x + #define JSON_UNLIKELY(x) x +#endif + +// C++ language standard detection +#if (defined(__cplusplus) && __cplusplus >= 201703L) || (defined(_HAS_CXX17) && _HAS_CXX17 == 1) // fix for issue #464 + #define JSON_HAS_CPP_17 + #define JSON_HAS_CPP_14 +#elif (defined(__cplusplus) && __cplusplus >= 201402L) || (defined(_HAS_CXX14) && _HAS_CXX14 == 1) + #define JSON_HAS_CPP_14 +#endif + +/*! +@brief macro to briefly define a mapping between an enum and JSON +@def NLOHMANN_JSON_SERIALIZE_ENUM +@since version 3.4.0 +*/ +#define NLOHMANN_JSON_SERIALIZE_ENUM(ENUM_TYPE, ...) \ + template \ + inline void to_json(BasicJsonType& j, const ENUM_TYPE& e) \ + { \ + static_assert(std::is_enum::value, #ENUM_TYPE " must be an enum!"); \ + static const std::pair m[] = __VA_ARGS__; \ + auto it = std::find_if(std::begin(m), std::end(m), \ + [e](const std::pair& ej_pair) -> bool \ + { \ + return ej_pair.first == e; \ + }); \ + j = ((it != std::end(m)) ? it : std::begin(m))->second; \ + } \ + template \ + inline void from_json(const BasicJsonType& j, ENUM_TYPE& e) \ + { \ + static_assert(std::is_enum::value, #ENUM_TYPE " must be an enum!"); \ + static const std::pair m[] = __VA_ARGS__; \ + auto it = std::find_if(std::begin(m), std::end(m), \ + [j](const std::pair& ej_pair) -> bool \ + { \ + return ej_pair.second == j; \ + }); \ + e = ((it != std::end(m)) ? it : std::begin(m))->first; \ + } + +// Ugly macros to avoid uglier copy-paste when specializing basic_json. They +// may be removed in the future once the class is split. + +#define NLOHMANN_BASIC_JSON_TPL_DECLARATION \ + template class ObjectType, \ + template class ArrayType, \ + class StringType, class BooleanType, class NumberIntegerType, \ + class NumberUnsignedType, class NumberFloatType, \ + template class AllocatorType, \ + template class JSONSerializer> + +#define NLOHMANN_BASIC_JSON_TPL \ + basic_json + +// #include + + +#include // not +#include // size_t +#include // conditional, enable_if, false_type, integral_constant, is_constructible, is_integral, is_same, remove_cv, remove_reference, true_type + +namespace nlohmann +{ +namespace detail +{ +// alias templates to reduce boilerplate +template +using enable_if_t = typename std::enable_if::type; + +template +using uncvref_t = typename std::remove_cv::type>::type; + +// implementation of C++14 index_sequence and affiliates +// source: https://stackoverflow.com/a/32223343 +template +struct index_sequence +{ + using type = index_sequence; + using value_type = std::size_t; + static constexpr std::size_t size() noexcept + { + return sizeof...(Ints); + } +}; + +template +struct merge_and_renumber; + +template +struct merge_and_renumber, index_sequence> + : index_sequence < I1..., (sizeof...(I1) + I2)... > {}; + +template +struct make_index_sequence + : merge_and_renumber < typename make_index_sequence < N / 2 >::type, + typename make_index_sequence < N - N / 2 >::type > {}; + +template<> struct make_index_sequence<0> : index_sequence<> {}; +template<> struct make_index_sequence<1> : index_sequence<0> {}; + +template +using index_sequence_for = make_index_sequence; + +// dispatch utility (taken from ranges-v3) +template struct priority_tag : priority_tag < N - 1 > {}; +template<> struct priority_tag<0> {}; + +// taken from ranges-v3 +template +struct static_const +{ + static constexpr T value{}; +}; + +template +constexpr T static_const::value; +} // namespace detail +} // namespace nlohmann + +// #include + + +#include // not +#include // numeric_limits +#include // false_type, is_constructible, is_integral, is_same, true_type +#include // declval + +// #include + +// #include + + +#include // random_access_iterator_tag + +// #include + + +namespace nlohmann +{ +namespace detail +{ +template struct make_void +{ + using type = void; +}; +template using void_t = typename make_void::type; +} // namespace detail +} // namespace nlohmann + +// #include + + +namespace nlohmann +{ +namespace detail +{ +template +struct iterator_types {}; + +template +struct iterator_types < + It, + void_t> +{ + using difference_type = typename It::difference_type; + using value_type = typename It::value_type; + using pointer = typename It::pointer; + using reference = typename It::reference; + using iterator_category = typename It::iterator_category; +}; + +// This is required as some compilers implement std::iterator_traits in a way that +// doesn't work with SFINAE. See https://github.com/nlohmann/json/issues/1341. +template +struct iterator_traits +{ +}; + +template +struct iterator_traits < T, enable_if_t < !std::is_pointer::value >> + : iterator_types +{ +}; + +template +struct iterator_traits::value>> +{ + using iterator_category = std::random_access_iterator_tag; + using value_type = T; + using difference_type = ptrdiff_t; + using pointer = T*; + using reference = T&; +}; +} +} + +// #include + +// #include + + +#include + +// #include + + +// http://en.cppreference.com/w/cpp/experimental/is_detected +namespace nlohmann +{ +namespace detail +{ +struct nonesuch +{ + nonesuch() = delete; + ~nonesuch() = delete; + nonesuch(nonesuch const&) = delete; + void operator=(nonesuch const&) = delete; +}; + +template class Op, + class... Args> +struct detector +{ + using value_t = std::false_type; + using type = Default; +}; + +template class Op, class... Args> +struct detector>, Op, Args...> +{ + using value_t = std::true_type; + using type = Op; +}; + +template