diff options
author | rtk0c <[email protected]> | 2022-04-17 20:08:57 -0700 |
---|---|---|
committer | rtk0c <[email protected]> | 2022-04-17 20:08:57 -0700 |
commit | 5424a1d5434e3ddd911a504719918c2df027e2fd (patch) | |
tree | 6275aab13140d81dcc46c8290e73ac9a8bbb5605 | |
parent | afcac59c7d04f4337d6b04ebed8cac7e871ccc50 (diff) |
Changeset: 8 Initial work on sprites and texture system
49 files changed, 2629 insertions, 553 deletions
diff --git a/3rdparty/imgui/ocurnut-imgui.LICENSE.txt b/3rdparty/imgui/LICENSE.txt index 49425fd..49425fd 100644 --- a/3rdparty/imgui/ocurnut-imgui.LICENSE.txt +++ b/3rdparty/imgui/LICENSE.txt diff --git a/3rdparty/imgui/README.md b/3rdparty/imgui/README.md new file mode 100644 index 0000000..803ac03 --- /dev/null +++ b/3rdparty/imgui/README.md @@ -0,0 +1,5 @@ +## Source +Version: v1.87 +https://github.com/ocornut/imgui/commit/c71a50deb5ddf1ea386b91e60fa2e4a26d080074 ++ Only relevant sources are here ++ Applied patch to expose more internal APIs (see each file) diff --git a/3rdparty/imgui/ocurnut-imgui.stamp b/3rdparty/imgui/ocurnut-imgui.stamp deleted file mode 100644 index e1752d7..0000000 --- a/3rdparty/imgui/ocurnut-imgui.stamp +++ /dev/null @@ -1,3 +0,0 @@ -# Source obtained from https://github.com/ocornut/imgui -version: v1.86 -commit: c71a50deb5ddf1ea386b91e60fa2e4a26d080074 diff --git a/3rdparty/imgui/source/backends/imgui_impl_opengl2.cpp b/3rdparty/imgui/source/backends/imgui_impl_opengl2.cpp new file mode 100644 index 0000000..17a6fae --- /dev/null +++ b/3rdparty/imgui/source/backends/imgui_impl_opengl2.cpp @@ -0,0 +1,285 @@ +// dear imgui: Renderer Backend for OpenGL2 (legacy OpenGL, fixed pipeline) +// This needs to be used along with a Platform Backend (e.g. GLFW, SDL, Win32, custom..) + +// Implemented features: +// [X] Renderer: User texture binding. Use 'GLuint' OpenGL texture identifier as void*/ImTextureID. Read the FAQ about ImTextureID! + +// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. +// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. +// If you are new to Dear ImGui, read documentation from the docs/ folder + read the top of imgui.cpp. +// Read online: https://github.com/ocornut/imgui/tree/master/docs + +// **DO NOT USE THIS CODE IF YOUR CODE/ENGINE IS USING MODERN OPENGL (SHADERS, VBO, VAO, etc.)** +// **Prefer using the code in imgui_impl_opengl3.cpp** +// This code is mostly provided as a reference to learn how ImGui integration works, because it is shorter to read. +// If your code is using GL3+ context or any semi modern OpenGL calls, using this is likely to make everything more +// complicated, will require your code to reset every single OpenGL attributes to their initial state, and might +// confuse your GPU driver. +// The GL2 code is unable to reset attributes or even call e.g. "glUseProgram(0)" because they don't exist in that API. + +// CHANGELOG +// (minor and older changes stripped away, please see git history for details) +// 2021-12-08: OpenGL: Fixed mishandling of the the ImDrawCmd::IdxOffset field! This is an old bug but it never had an effect until some internal rendering changes in 1.86. +// 2021-06-29: Reorganized backend to pull data from a single structure to facilitate usage with multiple-contexts (all g_XXXX access changed to bd->XXXX). +// 2021-05-19: OpenGL: Replaced direct access to ImDrawCmd::TextureId with a call to ImDrawCmd::GetTexID(). (will become a requirement) +// 2021-01-03: OpenGL: Backup, setup and restore GL_SHADE_MODEL state, disable GL_STENCIL_TEST and disable GL_NORMAL_ARRAY client state to increase compatibility with legacy OpenGL applications. +// 2020-01-23: OpenGL: Backup, setup and restore GL_TEXTURE_ENV to increase compatibility with legacy OpenGL applications. +// 2019-04-30: OpenGL: Added support for special ImDrawCallback_ResetRenderState callback to reset render state. +// 2019-02-11: OpenGL: Projecting clipping rectangles correctly using draw_data->FramebufferScale to allow multi-viewports for retina display. +// 2018-11-30: Misc: Setting up io.BackendRendererName so it can be displayed in the About Window. +// 2018-08-03: OpenGL: Disabling/restoring GL_LIGHTING and GL_COLOR_MATERIAL to increase compatibility with legacy OpenGL applications. +// 2018-06-08: Misc: Extracted imgui_impl_opengl2.cpp/.h away from the old combined GLFW/SDL+OpenGL2 examples. +// 2018-06-08: OpenGL: Use draw_data->DisplayPos and draw_data->DisplaySize to setup projection matrix and clipping rectangle. +// 2018-02-16: Misc: Obsoleted the io.RenderDrawListsFn callback and exposed ImGui_ImplOpenGL2_RenderDrawData() in the .h file so you can call it yourself. +// 2017-09-01: OpenGL: Save and restore current polygon mode. +// 2016-09-10: OpenGL: Uploading font texture as RGBA32 to increase compatibility with users shaders (not ideal). +// 2016-09-05: OpenGL: Fixed save and restore of current scissor rectangle. + +#include "imgui.h" +#include "imgui_impl_opengl2.h" +#if defined(_MSC_VER) && _MSC_VER <= 1500 // MSVC 2008 or earlier +#include <stddef.h> // intptr_t +#else +#include <stdint.h> // intptr_t +#endif + +// Include OpenGL header (without an OpenGL loader) requires a bit of fiddling +#if defined(_WIN32) && !defined(APIENTRY) +#define APIENTRY __stdcall // It is customary to use APIENTRY for OpenGL function pointer declarations on all platforms. Additionally, the Windows OpenGL header needs APIENTRY. +#endif +#if defined(_WIN32) && !defined(WINGDIAPI) +#define WINGDIAPI __declspec(dllimport) // Some Windows OpenGL headers need this +#endif +#if defined(__APPLE__) +#define GL_SILENCE_DEPRECATION +#include <OpenGL/gl.h> +#else +#include <GL/gl.h> +#endif + +struct ImGui_ImplOpenGL2_Data +{ + GLuint FontTexture; + + ImGui_ImplOpenGL2_Data() { memset(this, 0, sizeof(*this)); } +}; + +// Backend data stored in io.BackendRendererUserData to allow support for multiple Dear ImGui contexts +// It is STRONGLY preferred that you use docking branch with multi-viewports (== single Dear ImGui context + multiple windows) instead of multiple Dear ImGui contexts. +static ImGui_ImplOpenGL2_Data* ImGui_ImplOpenGL2_GetBackendData() +{ + return ImGui::GetCurrentContext() ? (ImGui_ImplOpenGL2_Data*)ImGui::GetIO().BackendRendererUserData : NULL; +} + +// Functions +bool ImGui_ImplOpenGL2_Init() +{ + ImGuiIO& io = ImGui::GetIO(); + IM_ASSERT(io.BackendRendererUserData == NULL && "Already initialized a renderer backend!"); + + // Setup backend capabilities flags + ImGui_ImplOpenGL2_Data* bd = IM_NEW(ImGui_ImplOpenGL2_Data)(); + io.BackendRendererUserData = (void*)bd; + io.BackendRendererName = "imgui_impl_opengl2"; + + return true; +} + +void ImGui_ImplOpenGL2_Shutdown() +{ + ImGui_ImplOpenGL2_Data* bd = ImGui_ImplOpenGL2_GetBackendData(); + IM_ASSERT(bd != NULL && "No renderer backend to shutdown, or already shutdown?"); + ImGuiIO& io = ImGui::GetIO(); + + ImGui_ImplOpenGL2_DestroyDeviceObjects(); + io.BackendRendererName = NULL; + io.BackendRendererUserData = NULL; + IM_DELETE(bd); +} + +void ImGui_ImplOpenGL2_NewFrame() +{ + ImGui_ImplOpenGL2_Data* bd = ImGui_ImplOpenGL2_GetBackendData(); + IM_ASSERT(bd != NULL && "Did you call ImGui_ImplOpenGL2_Init()?"); + + if (!bd->FontTexture) + ImGui_ImplOpenGL2_CreateDeviceObjects(); +} + +static void ImGui_ImplOpenGL2_SetupRenderState(ImDrawData* draw_data, int fb_width, int fb_height) +{ + // Setup render state: alpha-blending enabled, no face culling, no depth testing, scissor enabled, vertex/texcoord/color pointers, polygon fill. + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + //glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA); // In order to composite our output buffer we need to preserve alpha + glDisable(GL_CULL_FACE); + glDisable(GL_DEPTH_TEST); + glDisable(GL_STENCIL_TEST); + glDisable(GL_LIGHTING); + glDisable(GL_COLOR_MATERIAL); + glEnable(GL_SCISSOR_TEST); + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + glEnableClientState(GL_COLOR_ARRAY); + glDisableClientState(GL_NORMAL_ARRAY); + glEnable(GL_TEXTURE_2D); + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + glShadeModel(GL_SMOOTH); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + + // If you are using this code with non-legacy OpenGL header/contexts (which you should not, prefer using imgui_impl_opengl3.cpp!!), + // you may need to backup/reset/restore other state, e.g. for current shader using the commented lines below. + // (DO NOT MODIFY THIS FILE! Add the code in your calling function) + // GLint last_program; + // glGetIntegerv(GL_CURRENT_PROGRAM, &last_program); + // glUseProgram(0); + // ImGui_ImplOpenGL2_RenderDrawData(...); + // glUseProgram(last_program) + // There are potentially many more states you could need to clear/setup that we can't access from default headers. + // e.g. glBindBuffer(GL_ARRAY_BUFFER, 0), glDisable(GL_TEXTURE_CUBE_MAP). + + // Setup viewport, orthographic projection matrix + // Our visible imgui space lies from draw_data->DisplayPos (top left) to draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayPos is (0,0) for single viewport apps. + glViewport(0, 0, (GLsizei)fb_width, (GLsizei)fb_height); + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + glLoadIdentity(); + glOrtho(draw_data->DisplayPos.x, draw_data->DisplayPos.x + draw_data->DisplaySize.x, draw_data->DisplayPos.y + draw_data->DisplaySize.y, draw_data->DisplayPos.y, -1.0f, +1.0f); + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + glLoadIdentity(); +} + +// OpenGL2 Render function. +// Note that this implementation is little overcomplicated because we are saving/setting up/restoring every OpenGL state explicitly. +// This is in order to be able to run within an OpenGL engine that doesn't do so. +void ImGui_ImplOpenGL2_RenderDrawData(ImDrawData* draw_data) +{ + // Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates) + int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x); + int fb_height = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y); + if (fb_width == 0 || fb_height == 0) + return; + + // Backup GL state + GLint last_texture; glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture); + GLint last_polygon_mode[2]; glGetIntegerv(GL_POLYGON_MODE, last_polygon_mode); + GLint last_viewport[4]; glGetIntegerv(GL_VIEWPORT, last_viewport); + GLint last_scissor_box[4]; glGetIntegerv(GL_SCISSOR_BOX, last_scissor_box); + GLint last_shade_model; glGetIntegerv(GL_SHADE_MODEL, &last_shade_model); + GLint last_tex_env_mode; glGetTexEnviv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, &last_tex_env_mode); + glPushAttrib(GL_ENABLE_BIT | GL_COLOR_BUFFER_BIT | GL_TRANSFORM_BIT); + + // Setup desired GL state + ImGui_ImplOpenGL2_SetupRenderState(draw_data, fb_width, fb_height); + + // Will project scissor/clipping rectangles into framebuffer space + ImVec2 clip_off = draw_data->DisplayPos; // (0,0) unless using multi-viewports + ImVec2 clip_scale = draw_data->FramebufferScale; // (1,1) unless using retina display which are often (2,2) + + // Render command lists + for (int n = 0; n < draw_data->CmdListsCount; n++) + { + const ImDrawList* cmd_list = draw_data->CmdLists[n]; + const ImDrawVert* vtx_buffer = cmd_list->VtxBuffer.Data; + const ImDrawIdx* idx_buffer = cmd_list->IdxBuffer.Data; + glVertexPointer(2, GL_FLOAT, sizeof(ImDrawVert), (const GLvoid*)((const char*)vtx_buffer + IM_OFFSETOF(ImDrawVert, pos))); + glTexCoordPointer(2, GL_FLOAT, sizeof(ImDrawVert), (const GLvoid*)((const char*)vtx_buffer + IM_OFFSETOF(ImDrawVert, uv))); + glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(ImDrawVert), (const GLvoid*)((const char*)vtx_buffer + IM_OFFSETOF(ImDrawVert, col))); + + for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++) + { + const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i]; + if (pcmd->UserCallback) + { + // User callback, registered via ImDrawList::AddCallback() + // (ImDrawCallback_ResetRenderState is a special callback value used by the user to request the renderer to reset render state.) + if (pcmd->UserCallback == ImDrawCallback_ResetRenderState) + ImGui_ImplOpenGL2_SetupRenderState(draw_data, fb_width, fb_height); + else + pcmd->UserCallback(cmd_list, pcmd); + } + else + { + // Project scissor/clipping rectangles into framebuffer space + ImVec2 clip_min((pcmd->ClipRect.x - clip_off.x) * clip_scale.x, (pcmd->ClipRect.y - clip_off.y) * clip_scale.y); + ImVec2 clip_max((pcmd->ClipRect.z - clip_off.x) * clip_scale.x, (pcmd->ClipRect.w - clip_off.y) * clip_scale.y); + if (clip_max.x <= clip_min.x || clip_max.y <= clip_min.y) + continue; + + // Apply scissor/clipping rectangle (Y is inverted in OpenGL) + glScissor((int)clip_min.x, (int)(fb_height - clip_max.y), (int)(clip_max.x - clip_min.x), (int)(clip_max.y - clip_min.y)); + + // Bind texture, Draw + glBindTexture(GL_TEXTURE_2D, (GLuint)(intptr_t)pcmd->GetTexID()); + glDrawElements(GL_TRIANGLES, (GLsizei)pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, idx_buffer + pcmd->IdxOffset); + } + } + } + + // Restore modified GL state + glDisableClientState(GL_COLOR_ARRAY); + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + glDisableClientState(GL_VERTEX_ARRAY); + glBindTexture(GL_TEXTURE_2D, (GLuint)last_texture); + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + glPopAttrib(); + glPolygonMode(GL_FRONT, (GLenum)last_polygon_mode[0]); glPolygonMode(GL_BACK, (GLenum)last_polygon_mode[1]); + glViewport(last_viewport[0], last_viewport[1], (GLsizei)last_viewport[2], (GLsizei)last_viewport[3]); + glScissor(last_scissor_box[0], last_scissor_box[1], (GLsizei)last_scissor_box[2], (GLsizei)last_scissor_box[3]); + glShadeModel(last_shade_model); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, last_tex_env_mode); +} + +bool ImGui_ImplOpenGL2_CreateFontsTexture() +{ + // Build texture atlas + ImGuiIO& io = ImGui::GetIO(); + ImGui_ImplOpenGL2_Data* bd = ImGui_ImplOpenGL2_GetBackendData(); + unsigned char* pixels; + int width, height; + io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); // Load as RGBA 32-bit (75% of the memory is wasted, but default font is so small) because it is more likely to be compatible with user's existing shaders. If your ImTextureId represent a higher-level concept than just a GL texture id, consider calling GetTexDataAsAlpha8() instead to save on GPU memory. + + // Upload texture to graphics system + GLint last_texture; + glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture); + glGenTextures(1, &bd->FontTexture); + glBindTexture(GL_TEXTURE_2D, bd->FontTexture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels); + + // Store our identifier + io.Fonts->SetTexID((ImTextureID)(intptr_t)bd->FontTexture); + + // Restore state + glBindTexture(GL_TEXTURE_2D, last_texture); + + return true; +} + +void ImGui_ImplOpenGL2_DestroyFontsTexture() +{ + ImGuiIO& io = ImGui::GetIO(); + ImGui_ImplOpenGL2_Data* bd = ImGui_ImplOpenGL2_GetBackendData(); + if (bd->FontTexture) + { + glDeleteTextures(1, &bd->FontTexture); + io.Fonts->SetTexID(0); + bd->FontTexture = 0; + } +} + +bool ImGui_ImplOpenGL2_CreateDeviceObjects() +{ + return ImGui_ImplOpenGL2_CreateFontsTexture(); +} + +void ImGui_ImplOpenGL2_DestroyDeviceObjects() +{ + ImGui_ImplOpenGL2_DestroyFontsTexture(); +} diff --git a/3rdparty/imgui/source/backends/imgui_impl_opengl2.h b/3rdparty/imgui/source/backends/imgui_impl_opengl2.h new file mode 100644 index 0000000..d00d27f --- /dev/null +++ b/3rdparty/imgui/source/backends/imgui_impl_opengl2.h @@ -0,0 +1,32 @@ +// dear imgui: Renderer Backend for OpenGL2 (legacy OpenGL, fixed pipeline) +// This needs to be used along with a Platform Backend (e.g. GLFW, SDL, Win32, custom..) + +// Implemented features: +// [X] Renderer: User texture binding. Use 'GLuint' OpenGL texture identifier as void*/ImTextureID. Read the FAQ about ImTextureID! + +// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. +// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. +// If you are new to Dear ImGui, read documentation from the docs/ folder + read the top of imgui.cpp. +// Read online: https://github.com/ocornut/imgui/tree/master/docs + +// **DO NOT USE THIS CODE IF YOUR CODE/ENGINE IS USING MODERN OPENGL (SHADERS, VBO, VAO, etc.)** +// **Prefer using the code in imgui_impl_opengl3.cpp** +// This code is mostly provided as a reference to learn how ImGui integration works, because it is shorter to read. +// If your code is using GL3+ context or any semi modern OpenGL calls, using this is likely to make everything more +// complicated, will require your code to reset every single OpenGL attributes to their initial state, and might +// confuse your GPU driver. +// The GL2 code is unable to reset attributes or even call e.g. "glUseProgram(0)" because they don't exist in that API. + +#pragma once +#include "imgui.h" // IMGUI_IMPL_API + +IMGUI_IMPL_API bool ImGui_ImplOpenGL2_Init(); +IMGUI_IMPL_API void ImGui_ImplOpenGL2_Shutdown(); +IMGUI_IMPL_API void ImGui_ImplOpenGL2_NewFrame(); +IMGUI_IMPL_API void ImGui_ImplOpenGL2_RenderDrawData(ImDrawData* draw_data); + +// Called by Init/NewFrame/Shutdown +IMGUI_IMPL_API bool ImGui_ImplOpenGL2_CreateFontsTexture(); +IMGUI_IMPL_API void ImGui_ImplOpenGL2_DestroyFontsTexture(); +IMGUI_IMPL_API bool ImGui_ImplOpenGL2_CreateDeviceObjects(); +IMGUI_IMPL_API void ImGui_ImplOpenGL2_DestroyDeviceObjects(); diff --git a/CMakeLists.txt b/CMakeLists.txt index aafd9e1..ea2b3f4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -28,6 +28,7 @@ set_target_properties(${PROJECT_NAME} PROPERTIES target_compile_definitions(${PROJECT_NAME} PRIVATE RAPIDJSON_HAS_STDSTRING=1 + IMGUI_DISABLE_OBSOLETE_FUNCTIONS ) target_include_directories(${PROJECT_NAME} PRIVATE diff --git a/assets/Materials/M_BasicWalls.json b/assets/Materials/M_BasicWalls.json new file mode 100644 index 0000000..209dcf6 --- /dev/null +++ b/assets/Materials/M_BasicWalls.json @@ -0,0 +1 @@ +{"Name":"M_BasicWalls","ShaderName":"S_Default","Fields":[{"Name":"taint","Type":"Vector","Value":[0.37144365906715393,0.5479920506477356,0.5784313678741455,1.0]}]}
\ No newline at end of file diff --git a/assets/Materials/M_Default.json b/assets/Materials/M_Default.json index 87a467f..ad09830 100644 --- a/assets/Materials/M_Default.json +++ b/assets/Materials/M_Default.json @@ -1 +1 @@ -{"Name":"M_Default","ShaderName":"Default","Fields":[]}
\ No newline at end of file +{"Name":"M_Default","ShaderName":"S_Default","Fields":[{"Name":"taint","Type":"Vector","Value":[1.0,1.0,1.0,1.0]}]}
\ No newline at end of file diff --git a/assets/Materials/M_Player.json b/assets/Materials/M_Player.json new file mode 100644 index 0000000..d2b126d --- /dev/null +++ b/assets/Materials/M_Player.json @@ -0,0 +1 @@ +{"Name":"M_Player","ShaderName":"S_Default","Fields":[{"Name":"taint","Type":"Vector","Value":[1.0,1.0,1.0,0.0]}]}
\ No newline at end of file diff --git a/assets/Shaders/Default.json b/assets/Shaders/Default.json deleted file mode 100644 index d294372..0000000 --- a/assets/Shaders/Default.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "Inputs": [ - { - "Semantic": "Position", - "Name": "pos", - "ScalarType": "float", - "Width": 1, - "Height": 3, - "ArrayLength": 1, - "OpenGLLocation": 0 - }, - { - "Semantic": "Color1", - "Name": "color", - "ScalarType": "float", - "Width": 1, - "Height": 4, - "ArrayLength": 1, - "OpenGLLocation": 1 - } - ], - "Outputs": [ - { - "Name": "fragColor", - "ScalarType": "float", - "Width": 1, - "Height": 4, - "ArrayLength": 1, - "OpenGLLocation": 0 - } - ] -}
\ No newline at end of file diff --git a/assets/Shaders/Default.glsl b/assets/Shaders/S_Default.glsl index d103102..1d93471 100644 --- a/assets/Shaders/Default.glsl +++ b/assets/Shaders/S_Default.glsl @@ -3,17 +3,20 @@ layout(location = 0) in vec3 pos; layout(location = 1) in vec4 color; +layout(location = 2) in vec2 texcoord; out Vertex2Fragmnet { vec4 color; + vec2 texcoord; } v2f; -// Standard PainterHost uniform +// Autofill uniforms uniform mat4 transformation; void main() { gl_Position = transformation * vec4(pos, 1.0); v2f.color = color; + v2f.texcoord = texcoord; } #type fragment @@ -21,10 +24,15 @@ void main() { in Vertex2Fragmnet { vec4 color; + vec2 texcoord; } v2f; out vec4 fragColor; +// Material uniforms +uniform sampler2D textureAtlas; +uniform vec4 taint; + void main() { - fragColor = v2f.color; + fragColor = texture(textureAtlas, v2f.texcoord) * v2f.color * taint; } diff --git a/assets/Shaders/S_Default.json b/assets/Shaders/S_Default.json new file mode 100644 index 0000000..df37792 --- /dev/null +++ b/assets/Shaders/S_Default.json @@ -0,0 +1,65 @@ +{ + "Inputs": [ + { + "Semantic": "Position", + "Name": "pos", + "ScalarType": "float", + "Width": 1, + "Height": 3, + "OpenGLLocation": 0 + }, + { + "Semantic": "TexCoords1", + "Name": "texcoord", + "ScalarType": "float", + "Width": 1, + "Height": 2, + "OpenGLLocation": 1 + }, + { + "Semantic": "Color1", + "Name": "color", + "ScalarType": "float", + "Width": 1, + "Height": 4, + "OpenGLLocation": 2 + } + ], + "Outputs": [ + { + "Name": "fragColor", + "ScalarType": "float", + "Width": 1, + "Height": 4, + "OpenGLLocation": 0 + } + ], + "Uniforms": [ + { + "Type": "Math", + "AutoFill": true, + "Value": { + "Name": "transformation", + "ScalarType": "float", + "Width": 4, + "Height": 4 + } + }, + { + "Type": "Math", + "Value": { + "Name": "taint", + "Semantic": "Color1", + "ScalarType": "float", + "Width": 1, + "Height": 4 + } + }, + { + "Type": "Sampler", + "Value": { + "Name": "textureAtlas" + } + } + ] +}
\ No newline at end of file diff --git a/conanfile.txt b/conanfile.txt index 4c6b0f7..f80978c 100644 --- a/conanfile.txt +++ b/conanfile.txt @@ -4,7 +4,7 @@ glad/0.1.34 assimp/5.2.2 rapidjson/cci.20211112 stb/cci.20210910 -abseil/20211102.0 +robin-hood-hashing/3.11.5 [generators] cmake diff --git a/source/App.cpp b/source/App.cpp index ff0a5a5..ac5b319 100644 --- a/source/App.cpp +++ b/source/App.cpp @@ -30,13 +30,18 @@ void App::Shutdown() { } void App::Show() { - mCurrentWorld->Draw(); - if (mEditorShown) { mEditor->Show(); } } +void App::Update() { +} + +void App::Draw() { + mCurrentWorld->Draw(); +} + void App::HandleMouse(int button, int action) { } diff --git a/source/App.hpp b/source/App.hpp index 731ab06..fbdbd43 100644 --- a/source/App.hpp +++ b/source/App.hpp @@ -28,7 +28,12 @@ public: void Init(); void Shutdown(); + // Do ImGui calls void Show(); + // Do regular calls + void Update(); + void Draw(); + void HandleMouse(int button, int action); void HandleMouseMotion(double xOff, double yOff); void HandleKey(GLFWkeyboard* keyboard, int key, int action); diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index f851bcb..bc4bfdb 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -10,13 +10,17 @@ PRIVATE EditorUtils.cpp GameObject.cpp GraphicsTags.cpp + Image.cpp + Ires.cpp Level.cpp Material.cpp Mesh.cpp Player.cpp + Renderer.cpp SceneThings.cpp Shader.cpp SmallVector.cpp + Sprite.cpp Texture.cpp World.cpp ) diff --git a/source/CpuMesh.cpp b/source/CpuMesh.cpp index 8e65395..15b0f54 100644 --- a/source/CpuMesh.cpp +++ b/source/CpuMesh.cpp @@ -1,58 +1,54 @@ #include "CpuMesh.hpp" -bool CpuMesh::IsEmpty() const { - return !mVertexFormat->elements.empty(); -} +#include <cstring> -std::byte* CpuMesh::GetVertices() const { - return mVertexData.get(); +StandardCpuMesh::StandardCpuMesh() + : mGpuMesh(new GpuMesh()) { + mGpuMesh->vertFormat = gVformatStandard; + mGpuMesh->vertBufBindings.SetBinding(0, new GpuVertexBuffer()); + mGpuMesh->vertBufBindings.SetBinding(1, new GpuVertexBuffer()); + mGpuMesh->indexBuf.Attach(new GpuIndexBuffer()); } -int CpuMesh::GetVertexNumBytes() const { - return mVertexByteCount; +StandardCpuMesh::~StandardCpuMesh() { + delete mData; } -std::byte* CpuMesh::GetIndices() const { - return mIndexData.get(); +void StandardCpuMesh::CreateCpuData() { + if (!mData) { + mData = new StandardCpuMeshData(); + } } -int CpuMesh::GetIndexNumBytes() const { - return mIndexCount * Tags::SizeOf(mIndexType); +GpuVertexBuffer* StandardCpuMesh::GetPosBuffer() const { + return mGpuMesh->vertBufBindings.bindings[0].Get(); } -GpuMesh* CpuMesh::SyncToGpuCreate() const { - if (IsEmpty()) return nullptr; - - auto vertexBuffer = new GpuVertexBuffer(); - vertexBuffer->Upload(mVertexData.get(), GetVertexNumBytes()); +GpuVertexBuffer* StandardCpuMesh::GetExtraBuffer() const { + return mGpuMesh->vertBufBindings.bindings[1].Get(); +} - auto bindings = new BufferBindings(); - for (auto& elm : mVertexFormat->elements) { - bindings->SetBinding(elm.bindingIndex, vertexBuffer); +bool StandardCpuMesh::UpdatePositions(glm::vec3* pos, size_t count, size_t startVertIndex) { + if (mData) { + std::memcpy(&mData->vertPositions[startVertIndex], pos, count * sizeof(glm::vec3)); } - - auto indexBuffer = new GpuIndexBuffer(); - indexBuffer->Upload(mIndexData.get(), mIndexType, mIndexCount); - - return new GpuMesh(mVertexFormat.Get(), bindings, indexBuffer); + auto posBuf = GetPosBuffer(); + glBindBuffer(GL_ARRAY_BUFFER, posBuf->handle); + glBufferSubData(GL_ARRAY_BUFFER, startVertIndex * mGpuMesh->vertFormat->vertexSize, count * sizeof(glm::vec3), pos); + return true; } -void CpuMesh::SyncToGpu(GpuMesh& mesh) const { - if (IsEmpty()) return; - - auto& oldFormat = mesh.vertFormat; - auto& newFormat = this->mVertexFormat; - if (oldFormat != newFormat) { - auto buffer = new GpuVertexBuffer(); - buffer->Upload(mVertexData.get(), GetVertexNumBytes()); - - mesh.vertBufBindings->Clear(); - for (auto& elm : newFormat->elements) { - mesh.vertBufBindings->SetBinding(elm.bindingIndex, buffer); - } +bool StandardCpuMesh::UpdateColors(RgbaColor* color, size_t count, size_t starVertIndex) { + if (!mData) return false; + // TODO +} - oldFormat = newFormat; - } +bool StandardCpuMesh::UpdateNormals(glm::vec2* normals, size_t count, size_t startVertIndex) { + if (!mData) return false; + // TODO +} - mesh.indexBuf->Upload(mIndexData.get(), mIndexType, mIndexCount); +bool StandardCpuMesh::UpdateIndices(uint32_t* indices, size_t count, size_t startVertIndex) { + if (!mData) return false; + // TODO } diff --git a/source/CpuMesh.hpp b/source/CpuMesh.hpp index 7c6e2c8..9fcb00c 100644 --- a/source/CpuMesh.hpp +++ b/source/CpuMesh.hpp @@ -1,28 +1,51 @@ #pragma once +#include "Color.hpp" #include "Mesh.hpp" +#include "PodVector.hpp" #include "RcPtr.hpp" #include <cstddef> +#include <cstdint> +#include <glm/glm.hpp> #include <memory> -class CpuMesh : public RefCounted { +struct StandardVertex { + float x, y, z; + float u, v; + uint8_t r, g, b, a; +}; + +struct StandardVertexExtra { + float u, v; + uint8_t r, g, b, a; +}; + +class StandardCpuMeshData { +public: + PodVector<glm::vec3> vertPositions; + PodVector<StandardVertexExtra> vertExtra; + PodVector<uint32_t> index; + size_t vertexCount; + size_t triangleCount; +}; + +class StandardCpuMesh { private: - std::unique_ptr<std::byte[]> mVertexData; - std::unique_ptr<std::byte[]> mIndexData; - RcPtr<GpuMesh> mGpuMesh; - RcPtr<VertexFormat> mVertexFormat; - Tags::IndexType mIndexType; - int mVertexByteCount; - int mIndexCount; + StandardCpuMeshData* mData = nullptr; + RcPtr<GpuMesh> mGpuMesh; public: - bool IsEmpty() const; - std::byte* GetVertices() const; - int GetVertexNumBytes() const; - std::byte* GetIndices() const; - int GetIndexNumBytes() const; + StandardCpuMesh(); + ~StandardCpuMesh(); + + GpuVertexBuffer* GetPosBuffer() const; + GpuVertexBuffer* GetExtraBuffer() const; + GpuMesh* GetGpuMesh() const { return mGpuMesh.Get(); } - GpuMesh* SyncToGpuCreate() const; - void SyncToGpu(GpuMesh& mesh) const; + void CreateCpuData(); + bool UpdatePositions(glm::vec3* pos, size_t count, size_t startVertIndex); + bool UpdateColors(RgbaColor* color, size_t count, size_t starVertIndex); + bool UpdateNormals(glm::vec2* normals, size_t count, size_t startVertIndex); + bool UpdateIndices(uint32_t* indices, size_t count, size_t startVertIndex); }; diff --git a/source/EditorAttachmentImpl.hpp b/source/EditorAttachmentImpl.hpp index cb96348..258e987 100644 --- a/source/EditorAttachmentImpl.hpp +++ b/source/EditorAttachmentImpl.hpp @@ -19,6 +19,12 @@ class EaLevelWrapper : public EditorAttachment { public: }; +class EaIresObject : public EditorAttachment { +public: + std::string nameEditingScratch; + bool isEditingName = false; +}; + class EaShader : public EditorAttachment { public: Shader* shader; diff --git a/source/EditorCore.cpp b/source/EditorCore.cpp index 1846962..0c2e6a2 100644 --- a/source/EditorCore.cpp +++ b/source/EditorCore.cpp @@ -97,6 +97,7 @@ void EditorInstance::Show() { case ITT_GameObject: ShowInspector(static_cast<GameObject*>(mSelectedItPtr)); break; case ITT_Shader: ShowInspector(static_cast<Shader*>(mSelectedItPtr)); break; case ITT_Material: ShowInspector(static_cast<Material*>(mSelectedItPtr)); break; + case ITT_Ires: ShowInspector("", static_cast<IresObject*>(mSelectedItPtr)); break; // TODO case ITT_None: break; } ImGui::End(); @@ -104,6 +105,8 @@ void EditorInstance::Show() { if (mEdContentBrowserVisible) { mEdContentBrowser.Show(&mEdContentBrowserVisible); } + + ShowSpriteViewer(); } void EditorInstance::SelectIt(void* ptr, InspectorTargetType itt) { @@ -129,50 +132,37 @@ void EditorInstance::ShowInspector(Shader* shader) { shader->SetEditorAttachment(attachment); } - auto info = shader->GetInfo(); - if (!info) { - ImGui::TextUnformatted("No info present for this shader."); - if (ImGui::Button("Create empty info")) { - shader->CreateEmptyInfoIfAbsent(); - } - if (ImGui::Button("Gather info")) { - shader->GatherInfoIfAbsent(); - } - return; - } - + auto& info = shader->GetInfo(); auto& name = shader->GetName(); bool isAnnoymous = name.empty(); ShowShaderName(shader); - if (ImGui::Button("Reimport metadata", isAnnoymous)) { - info->LoadFromFile(shader->GetDesignatedMetadataPath()); + if (ImGui::Button("Reload metadata", isAnnoymous)) { + shader->LoadMetadataFromFile(shader->GetDesignatedMetadataPath()); + } + ImGui::SameLine(); + if (ImGui::Button("Save metadata", isAnnoymous)) { + shader->SaveMetadataToFile(shader->GetDesignatedMetadataPath()); } ImGui::SameLine(); - if (ImGui::Button("Export metadata", isAnnoymous)) { - info->SaveToFile(shader->GetDesignatedMetadataPath()); + if (ImGui::Button("Gather info")) { + shader->GatherInfoShaderIntrospection(); } - auto ShowThing = [&](const std::vector<ShaderInfo::InputOutputThing>& things) { - for (auto& thing : things) { - ImGui::BulletText("Location %d\nName: %s\nSemantic: %s\nType: %s %dx%d", - thing.variable.location, - thing.variable.name.c_str(), - Tags::NameOf(thing.semantic).data(), - Tags::NameOfGLType(thing.variable.scalarType).data(), - thing.variable.width, - thing.variable.height); - } - }; if (ImGui::CollapsingHeader("Inputs")) { - ShowThing(info->inputs); + for (auto& input : info.inputs) { + input.ShowInfo(); + } } if (ImGui::CollapsingHeader("Outputs")) { - ShowThing(info->outputs); + for (auto& output : info.outputs) { + output.ShowInfo(); + } } if (ImGui::CollapsingHeader("Uniforms")) { - } - if (ImGui::CollapsingHeader("Uniform blocks")) { + for (auto& uniform : info.uniforms) { + uniform->ShowInfo(); + } } } @@ -190,8 +180,6 @@ void EditorInstance::ShowInspector(Material* material) { auto& name = material->GetName(); bool isAnnoymous = name.empty(); - auto shader = material->GetShader(); - if (isAnnoymous) { ImGui::Text("<Annoymous Material at %p>", (void*)(&material)); } else { @@ -210,8 +198,6 @@ void EditorInstance::ShowInspector(Material* material) { if (ImGui::Button("Cancel")) { attachment->isEditingName = false; } - - ImGui::Text("%s", attachment->editingScratch.c_str()); } else { // NOTE: ReadOnly shouldn't write any data into the buffer ImGui::InputText("##", material->mName.data(), name.size() + 1, ImGuiInputTextFlags_ReadOnly); @@ -223,6 +209,7 @@ void EditorInstance::ShowInspector(Material* material) { } } + auto shader = material->GetShader(); ShowShaderName(shader); if (ImGui::BeginDragDropTarget()) { if (auto payload = ImGui::AcceptDragDropPayload(BRUSSEL_DRAG_DROP_SHADER)) { @@ -237,6 +224,9 @@ void EditorInstance::ShowInspector(Material* material) { mSelectedItPtr = shader; } + if (!shader) return; + auto& info = shader->GetInfo(); + if (ImGui::Button("Reload", isAnnoymous)) { material->LoadFromFile(material->GetDesignatedPath()); } @@ -246,19 +236,112 @@ void EditorInstance::ShowInspector(Material* material) { } for (auto& field : material->mBoundScalars) { - // TODO + auto& decl = static_cast<ShaderMathVariable&>(*info.uniforms[field.infoUniformIndex]); + decl.ShowInfo(); + + ImGui::Indent(); + switch (decl.scalarType) { + case GL_FLOAT: ImGui::InputFloat("##", &field.floatValue); break; + case GL_INT: ImGui::InputInt("##", &field.intValue); break; + // TODO proper uint edit? + case GL_UNSIGNED_INT: ImGui::InputInt("##", (int32_t*)(&field.uintValue), 0, std::numeric_limits<int32_t>::max()); break; + default: ImGui::TextUnformatted("Unsupported scalar type"); break; + } + ImGui::Unindent(); } for (auto& field : material->mBoundVectors) { - // TODO + auto& decl = static_cast<ShaderMathVariable&>(*info.uniforms[field.infoUniformIndex]); + decl.ShowInfo(); + + ImGui::Indent(); + switch (decl.semantic) { + case VES_Color1: + case VES_Color2: { + ImGui::ColorEdit4("##", field.value); + } break; + + default: { + ImGui::InputFloat4("##", field.value); + } break; + } + ImGui::Unindent(); } for (auto& field : material->mBoundMatrices) { + auto& decl = static_cast<ShaderMathVariable&>(*info.uniforms[field.infoUniformIndex]); + decl.ShowInfo(); + // TODO } for (auto& field : material->mBoundTextures) { + auto& decl = static_cast<ShaderSamplerVariable&>(*info.uniforms[field.infoUniformIndex]); + decl.ShowInfo(); + // TODO } } +void EditorInstance::ShowInspector(const std::string& path, IresObject* genericIres) { + ImGui::Text("%s", path.c_str()); + + switch (genericIres->GetKind()) { + case IresObject::KD_Texture: { + ImGui::TextUnformatted("Texture"); + ImGui::Separator(); + + // TODO + ImGui::TextUnformatted("Unimplemented"); + } break; + + case IresObject::KD_SpriteFiles: { + ImGui::TextUnformatted("Sprite Files"); + ImGui::Separator(); + + // TODO + ImGui::TextUnformatted("Unimplemented"); + } break; + + case IresObject::KD_Spritesheet: { + ImGui::TextUnformatted("Spritesheet"); + ImGui::Separator(); + + auto& ires = *static_cast<IresSpritesheet*>(genericIres); + auto instance = ires.GetInstance(); // NOTE: may be null + + if (ImGui::Button("View Sprite", instance == nullptr)) { + OpenSpriteViewer(instance); + } + + bool doInvalidateInstance = false; + if (ImGui::InputText("Spritesheet", &ires.spritesheetFile)) { + doInvalidateInstance = true; + } + if (ImGui::InputInt("Horizontal Split", &ires.sheetWSplit)) { + if (instance) IresSpritesheet::ResplitSpritesheet(instance, ires.sheetWSplit, ires.sheetHSplit); + } + if (ImGui::InputInt("Vertical Split", &ires.sheetHSplit)) { + if (instance) IresSpritesheet::ResplitSpritesheet(instance, ires.sheetWSplit, ires.sheetHSplit); + } + + if (instance) { + auto atlas = instance->GetAtlas(); + auto aspectRatio = (float)atlas->GetInfo().size.y / atlas->GetInfo().size.x; + ImVec2 size; + size.x = ImGui::GetContentRegionAvail().x; + size.y = aspectRatio * size.x; + ImGui::Image((ImTextureID)(uintptr_t)atlas->GetHandle(), size); + } else { + ImGui::TextUnformatted("Sprite configuration invalid"); + } + + if (doInvalidateInstance) { + ires.InvalidateInstance(); + } + } break; + + case IresObject::KD_COUNT: break; + } +} + void EditorInstance::ShowInspector(GameObject* object) { using namespace Tags; using namespace ProjectBrussel_UNITY_ID; @@ -367,3 +450,15 @@ void EditorInstance::ShowGameObjectInTree(GameObject* object) { ImGui::TreePop(); } } + +void EditorInstance::OpenSpriteViewer(Sprite* sprite) { + mSpriteView_Instance.Attach(sprite); + ImGui::OpenPopup("Sprite Viewer"); +} + +void EditorInstance::ShowSpriteViewer() { + if (ImGui::BeginPopup("Sprite Viewer")) { + // TODO + ImGui::EndPopup(); + } +} diff --git a/source/EditorCore.hpp b/source/EditorCore.hpp index 3d91fa6..c604f36 100644 --- a/source/EditorCore.hpp +++ b/source/EditorCore.hpp @@ -3,6 +3,9 @@ #include "EditorAttachment.hpp" #include "EditorResources.hpp" #include "GameObject.hpp" +#include "Ires.hpp" +#include "RcPtr.hpp" +#include "Sprite.hpp" #include "World.hpp" #include <memory> @@ -15,16 +18,19 @@ public: ITT_GameObject, ITT_Shader, ITT_Material, + ITT_Ires, ITT_None, }; private: App* mApp; GameWorld* mWorld; + // TODO store more fields for ITT void* mSelectedItPtr = nullptr; + RcPtr<Sprite> mSpriteView_Instance; EditorContentBrowser mEdContentBrowser; InspectorTargetType mSelectedItt = ITT_None; - bool mEdContentBrowserVisible=false; + bool mEdContentBrowserVisible = false; public: EditorInstance(App* app, GameWorld* world); @@ -41,8 +47,12 @@ private: void ShowInspector(Shader* shader); void ShowInspector(Material* material); + void ShowInspector(const std::string& path, IresObject* ires); void ShowInspector(GameObject* object); void ShowGameObjecetFields(GameObject* object); void ShowGameObjectInTree(GameObject* object); + + void OpenSpriteViewer(Sprite* sprite); + void ShowSpriteViewer(); }; diff --git a/source/EditorResources.cpp b/source/EditorResources.cpp index be7cefb..4f1a7c4 100644 --- a/source/EditorResources.cpp +++ b/source/EditorResources.cpp @@ -25,20 +25,20 @@ EditorContentBrowser::~EditorContentBrowser() { void EditorContentBrowser::Show(bool* open) { ImGuiWindowFlags windowFlags; - if (docked) { + if (mDocked) { // Center window horizontally, align bottom vertically auto& viewportSize = ImGui::GetMainViewport()->Size; ImGui::SetNextWindowPos(ImVec2(viewportSize.x / 2, viewportSize.y), ImGuiCond_Always, ImVec2(0.5f, 1.0f)); - ImGui::SetNextWindowSizeRelScreen(0.8f, 0.5f); + ImGui::SetNextWindowSizeRelScreen(0.8f, mBrowserHeight); windowFlags = ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse; } else { windowFlags = 0; } ImGui::Begin("Content Browser", open, windowFlags); - ImGui::Splitter(true, kSplitterThickness, &splitterLeft, &splitterRight, kLeftPaneMinWidth, kRightPaneMinWidth); + ImGui::Splitter(true, kSplitterThickness, &mSplitterLeft, &mSplitterRight, kLeftPaneMinWidth, kRightPaneMinWidth); - ImGui::BeginChild("LeftPane", ImVec2(splitterLeft - kPadding, 0.0f)); + ImGui::BeginChild("LeftPane", ImVec2(mSplitterLeft - kPadding, 0.0f)); { if (ImGui::Selectable("Settings", mPane == P_Settings)) { mPane = P_Settings; @@ -49,6 +49,9 @@ void EditorContentBrowser::Show(bool* open) { if (ImGui::Selectable("Materials", mPane == P_Material)) { mPane = P_Material; } + if (ImGui::Selectable("Ires", mPane == P_Ires)) { + mPane = P_Ires; + } } ImGui::EndChild(); @@ -57,7 +60,8 @@ void EditorContentBrowser::Show(bool* open) { { switch (mPane) { case P_Settings: { - ImGui::Checkbox("Docked", &docked); + ImGui::Checkbox("Docked", &mDocked); + ImGui::SliderFloat("Height", &mBrowserHeight, 0.1f, 1.0f); } break; case P_Shader: { @@ -68,10 +72,7 @@ void EditorContentBrowser::Show(bool* open) { if (ImGui::Button("Save all")) { auto& shaders = ShaderManager::instance->GetShaders(); for (auto&& [DISCARD, shader] : shaders) { - auto info = shader->GetInfo(); - if (info) { - info->SaveToFile(shader->GetDesignatedMetadataPath()); - } + shader->SaveMetadataToFile(shader->GetDesignatedMetadataPath()); } } @@ -80,9 +81,6 @@ void EditorContentBrowser::Show(bool* open) { auto shader = it->second.Get(); auto& name = shader->GetName(); - shader->GatherInfoIfAbsent(); - auto details = shader->GetInfo(); - bool selected = mEditor->GetSelectedItPtr() == shader; if (ImGui::Selectable(name.c_str(), selected)) { mEditor->SelectIt(shader, EditorInstance::ITT_Shader); @@ -100,7 +98,7 @@ void EditorContentBrowser::Show(bool* open) { case P_Material: { if (ImGui::Button("New")) { int n = std::rand(); - auto mat = new Material(nullptr, "Unnamed Material " + std::to_string(n)); + auto mat = new Material("Unnamed Material " + std::to_string(n)); auto guard = GuardDeletion(mat); auto [DISCARD, inserted] = MaterialManager::instance->SaveMaterial(mat); if (inserted) { @@ -139,6 +137,36 @@ void EditorContentBrowser::Show(bool* open) { } } } break; + + case P_Ires: { + if (ImGui::Button("New")) { + ImGui::OpenPopup("NewIresMenu"); + } + if (ImGui::BeginPopup("NewIresMenu")) { + for (int i = 0; i < IresObject::KD_COUNT; ++i) { + auto kind = static_cast<IresObject::Kind>(i); + if (ImGui::MenuItem(IresObject::ToString(kind).data())) { + auto ires = IresObject::Create(kind); + auto [DISCARD, success] = IresManager::instance->Add(ires.get()); + if (success) { + (void)ires.release(); + } + } + } + ImGui::EndPopup(); + } + + auto& objects = IresManager::instance->GetObjects(); + for (auto it = objects.begin(); it != objects.end(); ++it) { + auto ires = it->second.Get(); + auto& name = ires->GetName(); + + bool selected = mEditor->GetSelectedItPtr() == ires; + if (ImGui::Selectable(name.c_str(), selected)) { + mEditor->SelectIt(ires, EditorInstance::ITT_Ires); + } + } + } break; } } ImGui::EndChild(); diff --git a/source/EditorResources.hpp b/source/EditorResources.hpp index 81145a3..65969f0 100644 --- a/source/EditorResources.hpp +++ b/source/EditorResources.hpp @@ -11,6 +11,7 @@ private: P_Settings, P_Shader, P_Material, + P_Ires, }; static constexpr float kSplitterThickness = 3.0f; @@ -22,9 +23,10 @@ private: EditorInstance* mEditor; Pane mPane = P_Settings; - float splitterLeft = kLeftPaneMinWidth; - float splitterRight = 0.0f; - bool docked = true; + float mBrowserHeight = 0.5f; + float mSplitterLeft = kLeftPaneMinWidth; + float mSplitterRight = 0.0f; + bool mDocked = true; public: EditorContentBrowser(EditorInstance* editor); diff --git a/source/Enum.hpp b/source/Enum.hpp new file mode 100644 index 0000000..5e106fe --- /dev/null +++ b/source/Enum.hpp @@ -0,0 +1,103 @@ +#pragma once + +#include <initializer_list> +#include <type_traits> + +template <class TEnum> +class EnumFlags { +public: + using Enum = TEnum; + using Underlying = std::underlying_type_t<TEnum>; + +private: + Underlying mValue; + +public: + EnumFlags() + : mValue{ 0 } { + } + + EnumFlags(TEnum e) + : mValue{ 1 << static_cast<Underlying>(e) } { + } + + bool IsSet(EnumFlags mask) const { + return (mValue & mask.mValue) == mask.mValue; + } + + bool IsSet(std::initializer_list<TEnum> enums) { + EnumFlags flags; + for (auto& e : enums) { + flags.mValue |= static_cast<Underlying>(e); + } + return IsSet(flags); + } + + bool IsSetExclusive(EnumFlags mask) const { + return mValue == mask.mValue; + } + + bool IsSetExclusive(std::initializer_list<TEnum> enums) { + EnumFlags flags; + for (auto& e : enums) { + flags.mValue |= static_cast<Underlying>(e); + } + return IsSetExclusive(flags); + } + + void SetOn(EnumFlags mask) { + mValue |= mask.mValue; + } + + void SetOff(EnumFlags mask) { + mValue &= ~mask.mValue; + } + + void Set(EnumFlags mask, bool enabled) { + if (enabled) { + SetOn(mask); + } else { + SetOff(mask); + } + } + + EnumFlags& operator|=(EnumFlags that) const { + mValue |= that.mValue; + return *this; + } + + EnumFlags& operator&=(EnumFlags that) const { + mValue &= that.mValue; + return *this; + } + + EnumFlags& operator^=(EnumFlags that) const { + mValue ^= that.mValue; + return *this; + } + + EnumFlags& operator|=(TEnum e) const { + mValue |= 1 << static_cast<Underlying>(e); + return *this; + } + + EnumFlags& operator&=(TEnum e) const { + mValue &= 1 << static_cast<Underlying>(e); + return *this; + } + + EnumFlags& operator^=(TEnum e) const { + mValue ^= 1 << static_cast<Underlying>(e); + return *this; + } + + EnumFlags operator|(EnumFlags that) const { return EnumFlags(mValue | that.mValue); } + EnumFlags operator&(EnumFlags that) const { return EnumFlags(mValue & that.mValue); } + EnumFlags operator^(EnumFlags that) const { return EnumFlags(mValue ^ that.mValue); } + + EnumFlags operator|(TEnum e) const { return EnumFlags(mValue | 1 << static_cast<Underlying>(e)); } + EnumFlags operator&(TEnum e) const { return EnumFlags(mValue & 1 << static_cast<Underlying>(e)); } + EnumFlags operator^(TEnum e) const { return EnumFlags(mValue ^ 1 << static_cast<Underlying>(e)); } + + EnumFlags operator~() const { return EnumFlags(~mValue); } +}; diff --git a/source/GraphicsTags.cpp b/source/GraphicsTags.cpp index d2da091..b389acf 100644 --- a/source/GraphicsTags.cpp +++ b/source/GraphicsTags.cpp @@ -1,6 +1,6 @@ #include "GraphicsTags.hpp" -#include <absl/container/flat_hash_map.h> +#include <robin_hood.h> #include <cstddef> #include <cstdint> @@ -14,7 +14,10 @@ std::string_view Tags::NameOf(VertexElementSemantic semantic) { case VES_Normal: return "Normal"sv; case VES_Color1: return "Color1"sv; case VES_Color2: return "Color2"sv; - case VES_Texture_coordinates: return "Texture_coordinates"sv; + case VES_Color3: return "Color3"sv; + case VES_TexCoords1: return "TexCoords1"sv; + case VES_TexCoords2: return "TexCoords2"sv; + case VES_TexCoords3: return "TexCoords3"sv; case VES_Binormal: return "Binormal"sv; case VES_Tangent: return "Tangent"sv; case VES_Generic: return "Generic"sv; @@ -30,7 +33,10 @@ Tags::VertexElementSemantic Tags::FindVertexElementSemantic(std::string_view nam if (name == "Normal"sv) return VES_Normal; if (name == "Color1"sv) return VES_Color1; if (name == "Color2"sv) return VES_Color2; - if (name == "Texture_coordinates"sv) return VES_Texture_coordinates; + if (name == "Color3"sv) return VES_Color3; + if (name == "TexCoords1"sv) return VES_TexCoords1; + if (name == "TexCoords2"sv) return VES_TexCoords2; + if (name == "TexCoords3"sv) return VES_TexCoords3; if (name == "Binormal"sv) return VES_Binormal; if (name == "Tangent"sv) return VES_Tangent; if (name == "Generic"sv) return VES_Generic; @@ -86,6 +92,86 @@ int Tags::SizeOf(VertexElementType type) { return 0; } +int Tags::VectorLenOf(VertexElementType type) { + switch (type) { + case VET_Float1: + case VET_Double1: + case VET_Int1: + case VET_Uint1: + return 1; + case VET_Float2: + case VET_Double2: + case VET_Short2: + case VET_Short2Norm: + case VET_Ushort2: + case VET_Ushort2Norm: + case VET_Int2: + case VET_Uint2: + return 2; + case VET_Float3: + case VET_Double3: + case VET_Int3: + case VET_Uint3: + return 3; + case VET_Float4: + case VET_Double4: + case VET_Short4: + case VET_Short4Norm: + case VET_Ushort4: + case VET_Ushort4Norm: + case VET_Int4: + case VET_Uint4: + case VET_Byte4: + case VET_Byte4Norm: + case VET_Ubyte4: + case VET_Ubyte4Norm: + return 4; + } + return 0; +} + +GLenum Tags::FindGLType(VertexElementType type) { + switch (type) { + case VET_Float1: + case VET_Float2: + case VET_Float3: + case VET_Float4: + return GL_FLOAT; + case VET_Double1: + case VET_Double2: + case VET_Double3: + case VET_Double4: + return GL_DOUBLE; + case VET_Short2: + case VET_Short2Norm: + case VET_Short4: + case VET_Short4Norm: + return GL_SHORT; + case VET_Ushort2: + case VET_Ushort2Norm: + case VET_Ushort4: + case VET_Ushort4Norm: + return GL_UNSIGNED_SHORT; + case VET_Int1: + case VET_Int2: + case VET_Int3: + case VET_Int4: + return GL_INT; + case VET_Uint1: + case VET_Uint2: + case VET_Uint3: + case VET_Uint4: + return GL_UNSIGNED_INT; + case VET_Byte4: + case VET_Byte4Norm: + return GL_BYTE; + case VET_Ubyte4: + case VET_Ubyte4Norm: + return GL_UNSIGNED_BYTE; + } + return 0; +} + bool Tags::IsNormalized(VertexElementType type) { return type >= VET_NORM_BEGIN && type <= VET_NORM_END; } @@ -98,11 +184,18 @@ int Tags::SizeOf(IndexType type) { return 0; } -namespace ProjectBrussel_UNITY_ID { +GLenum Tags::FindGLType(IndexType type) { + switch (type) { + case IT_16Bit: return GL_UNSIGNED_SHORT; + case IT_32Bit: return GL_UNSIGNED_BYTE; + } + return GL_NONE; +} +namespace ProjectBrussel_UNITY_ID { struct GLTypeInfo { - absl::flat_hash_map<GLenum, std::string_view> enum2Name; - absl::flat_hash_map<std::string_view, GLenum> name2Enum; + robin_hood::unordered_flat_map<GLenum, std::string_view> enum2Name; + robin_hood::unordered_flat_map<std::string_view, GLenum> name2Enum; GLTypeInfo() { InsertEntry("float"sv, GL_FLOAT); diff --git a/source/GraphicsTags.hpp b/source/GraphicsTags.hpp index 465c57d..34c0885 100644 --- a/source/GraphicsTags.hpp +++ b/source/GraphicsTags.hpp @@ -1,6 +1,7 @@ #pragma once #include <glad/glad.h> +#include <limits> #include <string> #include <string_view> @@ -17,10 +18,12 @@ enum VertexElementSemantic { VES_Normal, /// Colour, typically VET_Ubyte4 VES_Color1, - /// Secondary colour. Generally free for custom data. Means specular with OpenGL FFP. VES_Color2, + VES_Color3, /// Texture coordinates, typically VET_Float2 - VES_Texture_coordinates, + VES_TexCoords1, + VES_TexCoords2, + VES_TexCoords3, /// Binormal (Y axis if normal is Z) VES_Binormal, /// Tangent (X axis if normal is Z) @@ -71,6 +74,8 @@ enum VertexElementType { }; int SizeOf(VertexElementType type); +int VectorLenOf(VertexElementType type); +GLenum FindGLType(VertexElementType type); bool IsNormalized(VertexElementType type); enum IndexType { @@ -79,7 +84,15 @@ enum IndexType { }; int SizeOf(IndexType type); +GLenum FindGLType(IndexType type); + +enum TexFilter { + TF_Linear, + TF_Nearest, +}; std::string_view NameOfGLType(GLenum); GLenum FindGLType(std::string_view name); + +constexpr GLuint kInvalidLocation = std::numeric_limits<GLuint>::max(); } // namespace Tags diff --git a/source/Image.cpp b/source/Image.cpp new file mode 100644 index 0000000..3673acc --- /dev/null +++ b/source/Image.cpp @@ -0,0 +1,101 @@ +#include "Image.hpp" + +#include <stb_image.h> +#include <stb_rect_pack.h> +#include <cstring> + +Image::Image() + : mSize{} + , mChannels{ 0 } { +} + +bool Image::InitFromImageFile(const char* filePath, int desiredChannels) { + // Dimensions of the image in + int width, height; + // Number of channels that the image has, we'll get `desiredChannels` channels in our output (if it's non-0, which is the default argument) + int channels; + + // NOTE: don't free, the data is passed to std::unique_ptr + auto result = (uint8_t*)stbi_load(filePath, &width, &height, &channels, desiredChannels); + if (!result) { + return false; + } + + mData.reset(result); + mSize = { width, height }; + mChannels = desiredChannels == 0 ? channels : desiredChannels; + return true; +} + +bool Image::InitFromImageData(std::span<uint8_t> data, int desiredChannels) { + int width, height; + int channels; + + // NOTE: don't free, the data is passed to std::unique_ptr + auto result = (uint8_t*)stbi_load_from_memory(data.data(), data.size(), &width, &height, &channels, desiredChannels); + if (!result) { + return false; + } + + mData.reset(result); + mSize = { width, height }; + mChannels = desiredChannels == 0 ? channels : desiredChannels; + return true; +} + +bool Image::InitFromPixels(std::span<uint8_t> pixels, glm::ivec2 dimensions, int channels) { + mData = std::make_unique<uint8_t[]>(pixels.size()); + std::memcpy(mData.get(), pixels.data(), pixels.size()); + mSize = dimensions; + mChannels = channels; + return true; +} + +bool Image::InitFromPixels(std::unique_ptr<uint8_t[]> pixels, glm::ivec2 dimensions, int channels) { + mData = std::move(pixels); + mSize = dimensions; + mChannels = channels; + return true; +} + +RgbaColor Image::GetPixel(int x, int y) const { + size_t offset = (y * mSize.x + x) * mChannels; + RgbaColor color; + color.r = mData.get()[offset + 0]; + color.g = mData.get()[offset + 1]; + color.b = mData.get()[offset + 2]; + color.a = mData.get()[offset + 3]; + return color; +} + +void Image::SetPixel(int x, int y, RgbaColor color) { + size_t offset = (y * mSize.x + x) * mChannels; + mData.get()[offset + 0] = color.r; + mData.get()[offset + 1] = color.g; + mData.get()[offset + 2] = color.b; + mData.get()[offset + 3] = color.a; +} + +uint8_t* Image::GetDataPtr() const { + return mData.get(); +} + +size_t Image::GetDataLength() const { + return mSize.x * mSize.y * mChannels * sizeof(uint8_t); +} + +std::span<uint8_t> Image::GetData() const { + return { mData.get(), GetDataLength() }; +} + +glm::ivec2 Image::GetSize() const { + return mSize; +} + +int Image::GetChannels() const { + return mChannels; +} + +bool Image::IsEmpty() const { + return mSize.x == 0 || mSize.y == 0; +} diff --git a/source/Image.hpp b/source/Image.hpp new file mode 100644 index 0000000..c577c24 --- /dev/null +++ b/source/Image.hpp @@ -0,0 +1,38 @@ +#pragma once + +#include "Color.hpp" +#include "RcPtr.hpp" + +#include <cstdint> +#include <glm/glm.hpp> +#include <memory> +#include <span> + +/// Image is a 2d array of pixels, stored as a continuous array in memory, with the first pixel +/// being the top-left pixel. If a vertically flipped image data is needed, load using stb_image +/// yourself, or flip the data here. +class Image : public RefCounted { +private: + std::unique_ptr<uint8_t[]> mData; + glm::ivec2 mSize; + int mChannels; + +public: + Image(); + + bool InitFromImageFile(const char* filePath, int desiredChannels = 0); + bool InitFromImageData(std::span<uint8_t> data, int desiredChannels = 0); + bool InitFromPixels(std::span<uint8_t> pixels, glm::ivec2 dimensions, int channels); + bool InitFromPixels(std::unique_ptr<uint8_t[]> pixels, glm::ivec2 dimensions, int channels); + + /// Get the pixel at the given location. + RgbaColor GetPixel(int x, int y) const; + void SetPixel(int x, int y, RgbaColor color); + + uint8_t* GetDataPtr() const; + size_t GetDataLength() const; + std::span<uint8_t> GetData() const; + glm::ivec2 GetSize() const; + int GetChannels() const; + bool IsEmpty() const; +}; diff --git a/source/Ires.cpp b/source/Ires.cpp new file mode 100644 index 0000000..f700ed6 --- /dev/null +++ b/source/Ires.cpp @@ -0,0 +1,176 @@ +#include "Ires.hpp" + +#include "AppConfig.hpp" +#include "RapidJsonHelper.hpp" +#include "ScopeGuard.hpp" +#include "Sprite.hpp" +#include "Texture.hpp" +#include "Utils.hpp" + +#include <rapidjson/document.h> +#include <rapidjson/filereadstream.h> +#include <rapidjson/filewritestream.h> +#include <rapidjson/writer.h> +#include <algorithm> + +namespace fs = std::filesystem; +using namespace std::literals; + +IresObject::IresObject(Kind kind) + : mKind{ kind } { +} + +std::string_view IresObject::ToString(Kind kind) { + switch (kind) { + case KD_Texture: return "Texture"sv; + case KD_SpriteFiles: return "SpriteFiles"sv; + case KD_Spritesheet: return "Spritesheet"sv; + case KD_COUNT: break; + } + return std::string_view(); +} + +IresObject::Kind IresObject::FromString(std::string_view name) { + if (name == "Texture"sv) return KD_Texture; + if (name == "SpriteFiles"sv) return KD_SpriteFiles; + if (name == "Spritesheet"sv) return KD_Spritesheet; + return KD_COUNT; +} + +std::unique_ptr<IresObject> IresObject::Create(Kind kind) { + switch (kind) { + case KD_Texture: return std::make_unique<IresTexture>(); + case KD_SpriteFiles: return std::make_unique<IresSpriteFiles>(); + case KD_Spritesheet: return std::make_unique<IresSpritesheet>(); + case KD_COUNT: break; + } + return nullptr; +} + +bool IresObject::IsAnnoymous() const { + return mName.empty(); +} + +rapidjson::Value IresObject::WriteFull(const IresObject& ires, rapidjson::Document& root) { + rapidjson::Value rvIres; + ires.Write(rvIres, root); + + rapidjson::Value result; + result.AddMember("Type", rapidjson::StringRef(ToString(ires.GetKind())), root.GetAllocator()); + result.AddMember("Value", rvIres, root.GetAllocator()); + return result; +} + +std::unique_ptr<IresObject> IresObject::ReadFull(const rapidjson::Value& value) { + auto rvType = rapidjson::GetProperty(value, rapidjson::kStringType, "Type"sv); + if (!rvType) return nullptr; + auto kind = FromString(rapidjson::AsStringView(*rvType)); + auto ires = Create(kind); + if (!ires) return nullptr; + + auto rvValue = rapidjson::GetProperty(value, "Value"sv); + if (!rvValue) return nullptr; + ires->Read(*rvValue); + + return ires; +} + +void IresManager::DiscoverFilesDesignatedLocation() { + auto path = AppConfig::assetDirPath / "Ires"; + DiscoverFiles(path); +} + +void IresManager::DiscoverFiles(const fs::path& dir) { + // NOTE: by default does not follow symlinks + // for (auto& item : fs::recursive_directory_iterator(dir)) { + for (auto& item : fs::directory_iterator(dir)) { + if (!item.is_regular_file()) { + continue; + } + if (item.path().extension() != ".json") { + continue; + } + + auto file = Utils::OpenCstdioFile(item.path(), Utils::Read); + if (!file) continue; + DEFER { fclose(file); }; + + char readerBuffer[65536]; + rapidjson::FileReadStream stream(file, readerBuffer, sizeof(readerBuffer)); + + rapidjson::Document root; + root.ParseStream(stream); + + auto ires = IresObject::ReadFull(root); + if (!ires) { + continue; + } + + auto iden = fs::path(item.path()).replace_extension().lexically_relative(dir).string(); + std::replace(iden.begin(), iden.end(), '\\', '/'); + +#if 0 + std::string_view idenView(iden); + + // Trim heading slashes + while (idenView.front() == '/') { + idenView = std::string_view(idenView.data() + 1, idenView.size()); + } + // Trim trailing slashes + while (idenView.back() == '/') { + idenView = std::string_view(idenView.data(), idenView.size() - 1); + } +#endif + mObjects.try_emplace(std::move(iden), ires.release()); + } +} + +std::pair<IresObject*, bool> IresManager::Add(IresObject* ires) { + auto& name = ires->mName; + if (name.empty()) { + int n = std::rand(); + // NOTE: does not include null-terminator + int size = snprintf(nullptr, 0, "Unnamed %s #%d", IresObject::ToString(ires->GetKind()).data(), n); + name.resize(size); // std::string::resize handles storage for null-terminator alreaedy + snprintf(name.data(), size, "Unnamed %s #%d", IresObject::ToString(ires->GetKind()).data(), n); + } + + auto [iter, inserted] = mObjects.try_emplace(name, ires); + if (inserted) { + ires->mMan = this; + return { ires, true }; + } else { + return { iter->second.Get(), false }; + } +} + +void IresManager::Delete(IresObject* ires) { + // TODO +} + +bool IresManager::Rename(IresObject* ires, std::string newName) { + if (mObjects.contains(newName)) { + return false; + } + + // Keep the material from being deleted, in case the old entry in map is the only one existing + RcPtr rc(ires); + + // Remove old entry (must do before replacing Material::mName, because the std::string_view in the map is a reference to it) + mObjects.erase(ires->GetName()); + + // Add new entry + ires->mName = std::move(newName); + mObjects.try_emplace(ires->GetName(), ires); + + return true; +} + +IresObject* IresManager::FindIres(std::string_view path) { + auto iter = mObjects.find(path); + if (iter != mObjects.end()) { + return iter->second.Get(); + } else { + return nullptr; + } +} diff --git a/source/Ires.hpp b/source/Ires.hpp new file mode 100644 index 0000000..66a931e --- /dev/null +++ b/source/Ires.hpp @@ -0,0 +1,73 @@ +#pragma once + +#include "EditorAttachment.hpp" +#include "RcPtr.hpp" +#include "Utils.hpp" + +#include <rapidjson/fwd.h> +#include <robin_hood.h> +#include <filesystem> +#include <memory> +#include <string_view> + +// Forward declarations +class IresObject; +class IresManager; + +class IresObject : public RefCounted { + friend class IresManager; + +public: + enum Kind { + KD_Texture, + KD_SpriteFiles, + KD_Spritesheet, + KD_COUNT, + }; + +private: + std::string mName; + std::unique_ptr<EditorAttachment> mEditorAttachment; + IresManager* mMan = nullptr; + Kind mKind; + +public: + IresObject(Kind kind); + virtual ~IresObject() = default; + + static std::string_view ToString(Kind kind); + static Kind FromString(std::string_view name); + static std::unique_ptr<IresObject> Create(Kind kind); + Kind GetKind() const { return mKind; } + + IresManager* GetAssociatedManager() const { return mMan; } + bool IsAnnoymous() const; + const std::string& GetName() const { return mName; } + + EditorAttachment* GetEditorAttachment() const { return mEditorAttachment.get(); } + void SetEditorAttachment(EditorAttachment* attachment) { mEditorAttachment.reset(attachment); } + + static rapidjson::Value WriteFull(const IresObject& ires, rapidjson::Document& root); + static std::unique_ptr<IresObject> ReadFull(const rapidjson::Value& value); + virtual void Write(rapidjson::Value& value, rapidjson::Document& root) const = 0; + virtual void Read(const rapidjson::Value& value) = 0; +}; + +class IresManager { +public: + static inline IresManager* instance = nullptr; + +private: + robin_hood::unordered_map<std::string_view, RcPtr<IresObject>, StringHash, StringEqual> mObjects; + +public: + void DiscoverFilesDesignatedLocation(); + void DiscoverFiles(const std::filesystem::path& dir); + + std::pair<IresObject*, bool> Add(IresObject* mat); + void Delete(IresObject* ires); + bool Rename(IresObject* ires, std::string newName); + + const auto& GetObjects() const { return mObjects; } + IresObject* FindIres(std::string_view path); +}; diff --git a/source/Material.cpp b/source/Material.cpp index db76b21..5e42b96 100644 --- a/source/Material.cpp +++ b/source/Material.cpp @@ -16,18 +16,18 @@ namespace fs = std::filesystem; using namespace std::literals; -Material::Material(Shader* shader, std::string name) - : mShader(shader) - , mName(std::move(name)) { +Material::Material() { +} + +Material::Material(std::string name) + : mName(std::move(name)) { } namespace ProjectBrussel_UNITY_ID { bool TryFindShaderId(Shader* shader, std::string_view name, int& out) { - auto info = shader->GetInfo(); - if (!info) return false; - - auto iter = info->things.find(name); - if (iter == info->things.end()) return false; + auto& info = shader->GetInfo(); + auto iter = info.things.find(name); + if (iter == info.things.end()) return false; auto& id = iter->second; if (id.kind != ShaderThingId::KD_Uniform) return false; @@ -111,7 +111,7 @@ Material::MatrixUniform ReadMatrixFromjson(const rapidjson::Value& rv) { assert(row.Size() == w); for (int x = 0; x < w; ++x) { auto& val = row[x]; - assert(val.IsFloat()); + assert(val.IsNumber()); result.value[x * h + y] = val.GetFloat(); } } @@ -224,8 +224,48 @@ Shader* Material::GetShader() const { } void Material::SetShader(Shader* shader) { - // TODO validate uniforms? mShader.Attach(shader); + auto& info = shader->GetInfo(); + + mBoundScalars.clear(); + mBoundVectors.clear(); + mBoundMatrices.clear(); + mBoundTextures.clear(); + for (int i = 0; i < info.uniforms.size(); ++i) { + auto& decl = info.uniforms[i]; + switch (decl->kind) { + case ShaderVariable::KD_Math: { + auto& mathDecl = static_cast<ShaderMathVariable&>(*decl); + if (mathDecl.width == 1) { + if (mathDecl.height == 1) { + // Scalar + auto& scalar = mBoundScalars.emplace_back(); + scalar.location = decl->location; + scalar.infoUniformIndex = i; + } else { + // Vector + auto& vec = mBoundVectors.emplace_back(); + vec.location = decl->location; + vec.infoUniformIndex = i; + vec.actualLength = mathDecl.height; + } + } else { + // Matrix + auto& mat = mBoundMatrices.emplace_back(); + mat.location = decl->location; + mat.infoUniformIndex = i; + mat.actualWidth = mathDecl.width; + mat.actualHeight = mathDecl.height; + } + } break; + + case ShaderVariable::KD_Sampler: { + auto& uniform = mBoundTextures.emplace_back(); + uniform.location = decl->location; + uniform.infoUniformIndex = i; + } break; + } + } } const std::string& Material::GetName() const { @@ -306,7 +346,7 @@ bool Material::SaveToFile(const fs::path& filePath) const { if (IsAnnoymous()) return false; if (!IsValid()) return false; - auto info = mShader->GetInfo(); + auto& info = mShader->GetInfo(); rapidjson::Document root(rapidjson::kObjectType); @@ -316,7 +356,7 @@ bool Material::SaveToFile(const fs::path& filePath) const { rapidjson::Value fields(rapidjson::kArrayType); for (auto& scalar : mBoundScalars) { rapidjson::Value rvField(rapidjson::kObjectType); - if (info) rvField.AddMember("Name", info->uniforms[scalar.infoUniformIndex]->name, root.GetAllocator()); + rvField.AddMember("Name", info.uniforms[scalar.infoUniformIndex]->name, root.GetAllocator()); rvField.AddMember("Type", "Scalar", root.GetAllocator()); switch (scalar.actualType) { case GL_FLOAT: rvField.AddMember("Value", scalar.floatValue, root.GetAllocator()); break; @@ -327,14 +367,14 @@ bool Material::SaveToFile(const fs::path& filePath) const { } for (auto& vector : mBoundVectors) { rapidjson::Value rvField(rapidjson::kObjectType); - if (info) rvField.AddMember("Name", info->uniforms[vector.infoUniformIndex]->name, root.GetAllocator()); + rvField.AddMember("Name", info.uniforms[vector.infoUniformIndex]->name, root.GetAllocator()); rvField.AddMember("Type", "Vector", root.GetAllocator()); rvField.AddMember("Value", MakeVectorJson(vector, root).Move(), root.GetAllocator()); fields.PushBack(rvField, root.GetAllocator()); } for (auto& matrix : mBoundMatrices) { rapidjson::Value rvField(rapidjson::kObjectType); - if (info) rvField.AddMember("Name", info->uniforms[matrix.infoUniformIndex]->name, root.GetAllocator()); + rvField.AddMember("Name", info.uniforms[matrix.infoUniformIndex]->name, root.GetAllocator()); rvField.AddMember("Type", "Matrix", root.GetAllocator()); rvField.AddMember("Value", MakeMatrixJson(matrix, root).Move(), root.GetAllocator()); fields.PushBack(rvField, root.GetAllocator()); @@ -388,6 +428,7 @@ bool Material::LoadFromFile(const fs::path& filePath) { mShader.Attach(shader); } + auto& shaderInfo = mShader->GetInfo(); auto fields = rapidjson::GetProperty(root, rapidjson::kArrayType, "Fields"sv); if (!fields) return false; @@ -407,6 +448,7 @@ bool Material::LoadFromFile(const fs::path& filePath) { ScalarUniform uniform; if (rvName) { TryFindShaderId(mShader.Get(), rapidjson::AsStringView(*rvName), uniform.infoUniformIndex); + uniform.location = shaderInfo.uniforms[uniform.infoUniformIndex]->location; } if (rvValue->IsFloat()) { uniform.actualType = GL_FLOAT; @@ -423,12 +465,14 @@ bool Material::LoadFromFile(const fs::path& filePath) { auto uniform = ReadVectorFromJson(*rvValue); if (rvName) { TryFindShaderId(mShader.Get(), rapidjson::AsStringView(*rvName), uniform.infoUniformIndex); + uniform.location = shaderInfo.uniforms[uniform.infoUniformIndex]->location; } mBoundVectors.push_back(std::move(uniform)); } else if (type == "Matrix"sv) { auto uniform = ReadMatrixFromjson(*rvValue); if (rvName) { TryFindShaderId(mShader.Get(), rapidjson::AsStringView(*rvName), uniform.infoUniformIndex); + uniform.location = shaderInfo.uniforms[uniform.infoUniformIndex]->location; } mBoundMatrices.push_back(uniform); } else if (type == "Texture"sv) { diff --git a/source/Material.hpp b/source/Material.hpp index 6b431be..bf1c988 100644 --- a/source/Material.hpp +++ b/source/Material.hpp @@ -6,6 +6,7 @@ #include "Texture.hpp" #include <glad/glad.h> +#include <robin_hood.h> #include <cstddef> #include <cstdint> #include <filesystem> @@ -15,11 +16,23 @@ #include <string_view> #include <vector> +// TODO migrate material editor to Ires class MaterialManager; class Material : public RefCounted { public: // NOTE: public for internal helpers and editor // NOTE: specialize between scalar vs matrix vs vector to save memory + enum UniformType : uint16_t { + UT_Scalar, + UT_Vector, + UT_Matrix, + }; + + struct UniformIndex { + UniformType type; + uint16_t index; + }; + struct ScalarUniform { union { float floatValue; @@ -27,14 +40,14 @@ public: // NOTE: public for internal helpers and editor uint32_t uintValue; }; GLenum actualType; - /* Saves 'name' */ int infoUniformIndex; + /* Transient */ int infoUniformIndex; /* Transient */ GLint location; }; struct VectorUniform { float value[4]; int actualLength; - /* Saves 'name' */ int infoUniformIndex; + /* Transient */ int infoUniformIndex; /* Transient */ GLint location; }; @@ -42,13 +55,13 @@ public: // NOTE: public for internal helpers and editor float value[16]; int actualWidth; int actualHeight; - /* Saves 'name' */ int infoUniformIndex; + /* Transient */ int infoUniformIndex; /* Transient */ GLint location; }; struct TextureUniform { RcPtr<Texture> value; - /* Saves 'name' */ int infoUniformIndex; + /* Transient */ int infoUniformIndex; /* Transient */ GLint location; }; @@ -61,9 +74,8 @@ public: // NOTE: public for internal helpers and editor std::vector<TextureUniform> mBoundTextures; public: - // NOTE: constructs invalid object - Material() = default; - Material(Shader* shader, std::string name = ""); + Material(); + Material(std::string name); void SetFloat(const char* name, float value); void SetInt(const char* name, int32_t value); @@ -106,7 +118,7 @@ public: static inline MaterialManager* instance = nullptr; private: - absl::flat_hash_map<std::string_view, RcPtr<Material>> mMaterials; + robin_hood::unordered_map<std::string_view, RcPtr<Material>> mMaterials; public: void DiscoverMaterials(); diff --git a/source/Mesh.cpp b/source/Mesh.cpp index 3622d42..5b4f708 100644 --- a/source/Mesh.cpp +++ b/source/Mesh.cpp @@ -69,6 +69,15 @@ int VertexElementFormat::GetStride() const { void VertexFormat::AddElement(VertexElementFormat element) { vertexSize += element.GetStride(); + + int lastIdx = (int)elements.size() - 1; + if (lastIdx >= 0) { + auto& last = elements[lastIdx]; + element.offset = last.offset + last.GetStride(); + } else { + element.offset = 0; + } + elements.push_back(std::move(element)); } @@ -78,56 +87,12 @@ void VertexFormat::RemoveElement(int index) { elements.erase(elements.begin() + index); } -void VertexFormat::Sort() { - std::sort(elements.begin(), elements.end()); -} - -void VertexFormat::CompactBindingIndex() { - if (elements.empty()) { - return; - } - - Sort(); - - int targetIdx = 0; - int lastIdx = elements[0].bindingIndex; - int c = 0; - for (auto& elm : elements) { - if (lastIdx != elm.bindingIndex) { - targetIdx++; - lastIdx = elm.bindingIndex; - } - if (targetIdx != elm.bindingIndex) { - elements[c] = elm; - elements[c].bindingIndex = targetIdx; - } - ++c; - } +GpuMesh::GpuMesh() { } -GpuMesh::GpuMesh(VertexFormat* vertexFormat, BufferBindings* bindings, GpuIndexBuffer* indexBuffer) - : vertFormat(vertexFormat) - , vertBufBindings(bindings) - , indexBuf(indexBuffer) { +GpuMesh::~GpuMesh() { } bool GpuMesh::IsEmpty() const { - return vertFormat != nullptr; -} - -void GpuMesh::SetVertex(VertexFormat* vertexFormat, BufferBindings* bindings) { - vertFormat.Attach(vertexFormat); - vertBufBindings.Attach(bindings); -} - -VertexFormat* GpuMesh::GetVertexFormat() const { - return vertFormat.Get(); -} - -BufferBindings* GpuMesh::GetVertexBufferBindings() const { - return vertBufBindings.Get(); -} - -GpuIndexBuffer* GpuMesh::GetIndexBuffer() const { - return indexBuf.Get(); + return vertFormat == nullptr || indexBuf == nullptr; } diff --git a/source/Mesh.hpp b/source/Mesh.hpp index a1c0984..bc755a3 100644 --- a/source/Mesh.hpp +++ b/source/Mesh.hpp @@ -31,7 +31,7 @@ struct GpuIndexBuffer : public RefCounted { void Upload(const std::byte* data, Tags::IndexType type, size_t count); }; -struct BufferBindings : public RefCounted { +struct BufferBindings { SmallVector<RcPtr<GpuVertexBuffer>, 4> bindings; int GetMaxBindingIndex() const; @@ -44,14 +44,15 @@ struct BufferBindings : public RefCounted { }; struct VertexElementFormat { - int offset; - int bindingIndex; - Tags::VertexElementType type; - Tags::VertexElementSemantic semantic; + /// NOTE: + /// "Automatic" means it will be set inside VertexFormat::AddElement() + /// "Parameter" means it must be set by the user + /* Automatic */ int offset; + /* Parameter */ int bindingIndex; + /* Parameter */ Tags::VertexElementType type; + /* Parameter */ Tags::VertexElementSemantic semantic; int GetStride() const; - - auto operator<=>(const VertexElementFormat&) const = default; }; struct VertexFormat : public RefCounted { @@ -61,24 +62,22 @@ struct VertexFormat : public RefCounted { const std::vector<VertexElementFormat>& GetElements() { return elements; } void AddElement(VertexElementFormat element); void RemoveElement(int index); - - void Sort(); - void CompactBindingIndex(); }; +// Initialized in main() +inline RcPtr<VertexFormat> gVformatStandard{}; +inline RcPtr<VertexFormat> gVformatStandardPacked{}; + +// TODO handle immutability class GpuMesh : public RefCounted { public: RcPtr<VertexFormat> vertFormat; - RcPtr<BufferBindings> vertBufBindings; + BufferBindings vertBufBindings; RcPtr<GpuIndexBuffer> indexBuf; public: - GpuMesh(VertexFormat* vertexFormat, BufferBindings* bindings, GpuIndexBuffer* indexBuffer); + GpuMesh(); + ~GpuMesh(); bool IsEmpty() const; - void SetVertex(VertexFormat* vertexFormat, BufferBindings* bindings); - VertexFormat* GetVertexFormat() const; - BufferBindings* GetVertexBufferBindings() const; - void SetIndex(GpuIndexBuffer* buffer); - GpuIndexBuffer* GetIndexBuffer() const; }; diff --git a/source/PodVector.hpp b/source/PodVector.hpp index 8f0281b..74e99d6 100644 --- a/source/PodVector.hpp +++ b/source/PodVector.hpp @@ -95,6 +95,8 @@ public: T* end() { return mData + mSize; } const T* end() const { return mData + mSize; } + T* data() { return mData; } + T& front() { assert(mSize > 0); return mData[0]; diff --git a/source/RapidJsonHelper.hpp b/source/RapidJsonHelper.hpp index 9dc0701..75cd93a 100644 --- a/source/RapidJsonHelper.hpp +++ b/source/RapidJsonHelper.hpp @@ -75,4 +75,36 @@ inline GenericStringRef<char> StringRef(std::string_view str) { str.size()); } +template <class TIter, class TSentienl> +rapidjson::Value WriteVectorPrimitives(rapidjson::Document& root, TIter begin, TSentienl end) { + using TElement = typename TIter::value_type; + + rapidjson::Value list; + while (begin != end) { + if constexpr (std::is_same_v<TElement, std::string>) { + auto& elm = *begin; + list.PushBack(rapidjson::Value(elm.c_str(), elm.size()), root.GetAllocator()); + } else { + list.PushBack(*begin, root.GetAllocator()); + } + ++begin; + } + return list; +} + +template <class TContainer> +bool ReadVectorPrimitives(const rapidjson::Value& value, TContainer& list) { + using TElement = typename TContainer::value_type; + + if (!value.IsArray()) return false; + + list.reserve(value.Size()); + for (auto& elm : value.GetArray()) { + if (!elm.Is<TElement>()) return {}; + list.push_back(elm.Get<TElement>()); + } + + return true; +} + } // namespace rapidjson diff --git a/source/RcPtr.hpp b/source/RcPtr.hpp index 5958db4..130b2b2 100644 --- a/source/RcPtr.hpp +++ b/source/RcPtr.hpp @@ -4,13 +4,15 @@ #include "TypeTraits.hpp" #include <cstddef> +#include <cstdint> #include <optional> #include <type_traits> class RefCounted { public: // DO NOT MODIFY this field, unless explicitly documented the use - size_t refCount = 0; + uint32_t refCount = 0; + uint32_t weakCount = 0; // TODO implement }; template <class T, class TDeleter = DefaultDeleter<T>> @@ -27,7 +29,7 @@ public: explicit RcPtr(T* ptr) : mPtr{ ptr } { if (ptr) { - ++ptr->refCount; + ++ptr->RefCounted::refCount; } } @@ -39,7 +41,7 @@ public: CleanUp(); mPtr = ptr; if (ptr) { - ++ptr->refCount; + ++ptr->RefCounted::refCount; } } @@ -51,7 +53,7 @@ public: RcPtr(const RcPtr& that) : mPtr{ that.mPtr } { if (mPtr) { - ++mPtr->refCount; + ++mPtr->RefCounted::refCount; } } @@ -59,7 +61,7 @@ public: CleanUp(); mPtr = that.mPtr; if (mPtr) { - ++mPtr->refCount; + ++mPtr->RefCounted::refCount; } return *this; } @@ -109,8 +111,8 @@ public: private: void CleanUp() { if (mPtr) { - --mPtr->refCount; - if (mPtr->refCount == 0) { + --mPtr->RefCounted::refCount; + if (mPtr->RefCounted::refCount == 0) { TDeleter::operator()(mPtr); } } diff --git a/source/Rect.hpp b/source/Rect.hpp new file mode 100644 index 0000000..89d9b01 --- /dev/null +++ b/source/Rect.hpp @@ -0,0 +1,164 @@ +#pragma once + +#include <glm/glm.hpp> + +/// Rect is a rectangle representation based on a point and a dimensions, in television coordinate space +/// (x increases from left to right, y increases from top to bottom). +template <class T> +class Rect { +public: + using ScalarType = T; + using VectorType = glm::vec<2, T>; + +public: + T x; + T y; + T width; + T height; + +public: + Rect() + : x{ 0 }, y{ 0 }, width{ 0 }, height{ 0 } { + } + + Rect(T x, T y, T width, T height) + : x{ x }, y{ y }, width{ width }, height{ height } { + } + + Rect(VectorType pos, VectorType size) + : x{ pos.x } + , y{ pos.y } + , width{ size.x } + , height{ size.y } { + } + + T x0() const { return x; } + T y0() const { return y; } + T x1() const { return x + width; } + T y1() const { return y + height; } + + VectorType TopLeft() const { + return VectorType{ x, y }; + } + + VectorType TopRight() const { + return VectorType{ x + width, y }; + } + + VectorType BottomLeft() const { + return VectorType{ x, y + height }; + } + + VectorType BottomRight() const { + return VectorType{ x + width, y + height }; + } + + VectorType Center() const { + return TopLeft() + VectorType{ width / 2, height / 2 }; + } + + VectorType Dimensions() const { + return VectorType{ width, height }; + } + + VectorType Extents() const { + return VectorType{ width / 2, height / 2 }; + } + + /// Assumes `bySize * 2` is smaller than both `width` and `height` (does not produce a negative-dimension rectangle). + Rect Shrink(T bySize) const { + T two = bySize * 2; + return Rect{ x + bySize, y + bySize, width - two, height - two }; + } + + Rect Shrink(T left, T top, T right, T bottom) const { + return Rect{ + x + left, + y + top, + width - left - right, + height - top - bottom, + }; + } + + Rect Expand(T bySize) const { + T two = bySize * 2; + return Rect{ x - bySize, y - bySize, width + two, height + two }; + } + + Rect Expand(T left, T top, T right, T bottom) const { + return Rect{ + x - left, + y - top, + width + left + right, + height + top + bottom, + }; + } + + bool Contains(VectorType point) const { + return point.x >= x && + point.y >= y && + point.x < x + width && + point.y < y + height; + } + + bool Intersects(const Rect& that) const { + bool xBetween = x > that.x0() && x < that.x1(); + bool yBetween = y > that.y0() && y < that.y1(); + return xBetween && yBetween; + } + + // Write min()/max() tenary by hand so that we don't have to include <algorithm> + // This file is practically going to be included in every file in this project + + static Rect Intersection(const Rect& a, const Rect& b) { + auto x0 = a.x0() > b.x0() ? a.x0() : b.x0(); // Max + auto y0 = a.y0() > b.y0() ? a.y0() : b.y0(); // Max + auto x1 = a.x1() < b.x1() ? a.x1() : b.x1(); // Min + auto y1 = a.y1() < b.y1() ? a.y1() : b.y1(); // Min + auto width = x1 - x0; + auto height = y1 - x0; + return Rect{ x0, y0, width, height }; + } + + static Rect Union(const Rect& a, const Rect& b) { + auto x0 = a.x0() < b.x0() ? a.x0() : b.x0(); // Min + auto y0 = a.y0() < b.y0() ? a.y0() : b.y0(); // Min + auto x1 = a.x1() > b.x1() ? a.x1() : b.x1(); // Max + auto y1 = a.y1() > b.y1() ? a.y1() : b.y1(); // Max + auto width = x1 - x0; + auto height = y1 - x0; + return Rect{ x0, y0, width, height }; + } + + friend bool operator==(const Rect<T>&, const Rect<T>&) = default; + + Rect operator+(glm::vec<2, T> offset) const { + return { x + offset.x, y + offset.y, width, height }; + } + + Rect operator-(glm::vec<2, T> offset) const { + return { x - offset.x, y - offset.y, width, height }; + } + + Rect& operator+=(glm::vec<2, T> offset) { + x += offset.x; + y += offset.y; + return *this; + } + + Rect& operator-=(glm::vec<2, T> offset) { + x -= offset.x; + y -= offset.y; + return *this; + } + + template <class TTarget> + Rect<TTarget> Cast() const { + return { + static_cast<TTarget>(x), + static_cast<TTarget>(y), + static_cast<TTarget>(width), + static_cast<TTarget>(height), + }; + } +}; diff --git a/source/Renderer.cpp b/source/Renderer.cpp new file mode 100644 index 0000000..dec24ea --- /dev/null +++ b/source/Renderer.cpp @@ -0,0 +1,43 @@ +#include "Renderer.hpp" + +RenderObject::RenderObject(GpuMesh* mesh, Material* material) { + glGenVertexArrays(1, &mVao); + glBindVertexArray(mVao); + + auto& vf = mesh->vertFormat; + auto& vBindings = mesh->vertBufBindings.bindings; + auto& shaderInfo = material->GetShader()->GetInfo(); + + // Setup vertex buffers + for (auto& elm : vf->elements) { + assert(elm.bindingIndex < vBindings.size()); + auto& buffer = vBindings[elm.bindingIndex]; + + int index = material->GetShader()->GetInfo().FindInputLocation(elm.semantic); + glBindBuffer(GL_ARRAY_BUFFER, buffer->handle); + glEnableVertexAttribArray(index); + glVertexAttribPointer( + index, + Tags::VectorLenOf(elm.type), + Tags::FindGLType(elm.type), + Tags::IsNormalized(elm.type), + vf->vertexSize, + (void*)(uintptr_t)elm.offset); + } + + // Setup index buffer + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mesh->indexBuf->handle); + + glBindVertexArray(GL_NONE); +} + +RenderObject::~RenderObject() { + glDeleteVertexArrays(1, &mVao); +} + +void Renderer::Draw(RenderObject& object) { + auto indexType = object.GetMesh()->indexBuf->indexType; + + glBindVertexArray(object.GetGLVao()); + glDrawElements(GL_TRIANGLES, 0, Tags::FindGLType(indexType), 0); +} diff --git a/source/Renderer.hpp b/source/Renderer.hpp new file mode 100644 index 0000000..18a1e6e --- /dev/null +++ b/source/Renderer.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include "Material.hpp" +#include "Mesh.hpp" +#include "RcPtr.hpp" + +#include <glad/glad.h> +#include <glm/glm.hpp> + +class RenderObject { +public: + glm::mat4 worldMatrix; + +private: + RcPtr<Material> mMaterial; + RcPtr<GpuMesh> mMesh; + GLuint mVao; + +public: + RenderObject(GpuMesh* mesh, Material* material); + ~RenderObject(); + + GLuint GetGLVao() const { return mVao; } + Material* GetMaterial() const { return mMaterial.Get(); } + GpuMesh* GetMesh() const { return mMesh.Get(); } +}; + +class Camera { +public: + glm::mat4 viewMatrix; + glm::mat4 projectionMatrix; + +public: + void Move(glm::vec3 pos); + void LookAt(glm::vec3 pos); +}; + +class Renderer { +private: + Camera* mCam; + +public: + void BeginFrame(); + void EndFrame(); + void Draw(RenderObject& object); +}; diff --git a/source/Shader.cpp b/source/Shader.cpp index 4a576d0..61e80a1 100644 --- a/source/Shader.cpp +++ b/source/Shader.cpp @@ -18,114 +18,56 @@ namespace fs = std::filesystem; using namespace std::literals; -bool ShaderThingId::IsValid() const { - return kind == KD_Invalid; +void ShaderMathVariable::ShowInfo() const { + ImGui::BulletText("Location: %d\nName: %s\nSemantic: %s\nType: %s %dx%d", + location, + name.c_str(), + Tags::NameOf(semantic).data(), + Tags::NameOfGLType(scalarType).data(), + width, + height); } -ShaderVariable* ShaderInfo::FindVariable(const ShaderThingId& thing) { - switch (thing.kind) { - case ShaderThingId::KD_Input: return &inputs[thing.index].variable; - case ShaderThingId::KD_Output: return &outputs[thing.index].variable; - case ShaderThingId::KD_Uniform: return uniforms[thing.index].get(); - case ShaderThingId::KD_Invalid: break; - } - return nullptr; +void ShaderSamplerVariable::ShowInfo() const { + ImGui::BulletText("Location: %d\nName: %s\nSemantic: %s\nType: Sampler", + location, + name.c_str(), + Tags::NameOf(semantic).data()); } -bool ShaderInfo::SaveToFile(const fs::path& filePath) const { - rapidjson::Document root(rapidjson::kObjectType); - - auto SaveInputOutputThings = [&](const char* name, const std::vector<InputOutputThing>& things) { - rapidjson::Value rvThings(rapidjson::kArrayType); - for (auto& thing : things) { - rapidjson::Value rvThing(rapidjson::kObjectType); - rvThing.AddMember("Name", thing.variable.name, root.GetAllocator()); - rvThing.AddMember("Semantic", rapidjson::StringRef(Tags::NameOf(thing.semantic)), root.GetAllocator()); - rvThing.AddMember("ScalarType", rapidjson::StringRef(Tags::NameOfGLType(thing.variable.scalarType)), root.GetAllocator()); - rvThing.AddMember("Width", thing.variable.width, root.GetAllocator()); - rvThing.AddMember("Height", thing.variable.height, root.GetAllocator()); - rvThing.AddMember("ArrayLength", thing.variable.arrayLength, root.GetAllocator()); - rvThing.AddMember("OpenGLLocation", thing.variable.location, root.GetAllocator()); - - rvThings.PushBack(rvThing, root.GetAllocator()); - } - root.AddMember(rapidjson::StringRef(name), rvThings, root.GetAllocator()); - }; - SaveInputOutputThings("Inputs", inputs); - SaveInputOutputThings("Outputs", outputs); - - // TODO uniforms - - auto file = Utils::OpenCstdioFile(filePath, Utils::WriteTruncate); - if (!file) return false; - DEFER { fclose(file); }; - - char writerBuffer[65536]; - rapidjson::FileWriteStream stream(file, writerBuffer, sizeof(writerBuffer)); - rapidjson::Writer<rapidjson::FileWriteStream> writer(stream); - root.Accept(writer); - - return true; +bool ShaderThingId::IsValid() const { + return kind == KD_Invalid; } -bool ShaderInfo::LoadFromFile(const fs::path& filePath) { - auto file = Utils::OpenCstdioFile(filePath, Utils::Read); - if (!file) return false; - DEFER { fclose(file); }; - - char readerBuffer[65536]; - rapidjson::FileReadStream stream(file, readerBuffer, sizeof(readerBuffer)); - - rapidjson::Document root; - root.ParseStream(stream); - - auto LoadInputOutputThings = [&](std::string_view name, std::vector<InputOutputThing>& things) { - auto rvThings = rapidjson::GetProperty(root, rapidjson::kObjectType, name); - if (!rvThings) return; - if (!rvThings->IsArray()) return; - - for (auto& elm : rvThings->GetArray()) { - if (!elm.IsObject()) continue; - InputOutputThing thing; - - BRUSSEL_JSON_GET(elm, "Name", std::string, thing.variable.name, continue); - { // Semantic - auto value = rapidjson::GetProperty(elm, rapidjson::kStringType, "Semantic"sv); - if (!value) { - thing.semantic = Tags::VES_Generic; - } else { - thing.semantic = Tags::FindVertexElementSemantic(rapidjson::AsStringView(*value)); - } - } - { // Scalar type - auto value = rapidjson::GetProperty(elm, rapidjson::kStringType, "ScalarType"sv); - if (!value) continue; - thing.variable.scalarType = Tags::FindGLType(rapidjson::AsStringView(*value)); - } - BRUSSEL_JSON_GET(elm, "Width", int, thing.variable.width, continue); - BRUSSEL_JSON_GET(elm, "Height", int, thing.variable.height, continue); - BRUSSEL_JSON_GET(elm, "OpenGLLocation", int, thing.variable.location, continue); - BRUSSEL_JSON_GET_DEFAULT(elm, "ArrayLength", int, thing.variable.arrayLength, 1); - - things.push_back(std::move(thing)); +namespace ProjectBrussel_UNITY_ID { +GLuint FindLocation(const std::vector<ShaderMathVariable>& vars, Tags::VertexElementSemantic semantic) { + for (auto& var : vars) { + if (var.semantic == semantic) { + return var.location; } - }; - LoadInputOutputThings("Inputs"sv, inputs); - LoadInputOutputThings("Outputs"sv, outputs); - - // TODO uniforms + } + return Tags::kInvalidLocation; +} +} // namespace ProjectBrussel_UNITY_ID - return true; +GLuint ShaderInfo::FindInputLocation(Tags::VertexElementSemantic semantic) { + using namespace ProjectBrussel_UNITY_ID; + return FindLocation(inputs, semantic); } -void ShaderInfo::LoadLocations(const Shader& ownerShader) { - GLuint program = ownerShader.GetProgram(); +GLuint ShaderInfo::FindOutputLocation(Tags::VertexElementSemantic semantic) { + using namespace ProjectBrussel_UNITY_ID; + return FindLocation(outputs, semantic); +} - // TODO inputs - // TODO outputs - for (auto& uniform : uniforms) { - uniform->location = glGetUniformLocation(ownerShader.GetProgram(), uniform->name.c_str()); +ShaderVariable* ShaderInfo::FindVariable(const ShaderThingId& thing) { + switch (thing.kind) { + case ShaderThingId::KD_Input: return &inputs[thing.index]; + case ShaderThingId::KD_Output: return &outputs[thing.index]; + case ShaderThingId::KD_Uniform: return uniforms[thing.index].get(); + case ShaderThingId::KD_Invalid: break; } + return nullptr; } Shader::Shader(std::string name) @@ -133,7 +75,7 @@ Shader::Shader(std::string name) } Shader::~Shader() { - glDeleteProgram(mHandle); + glDeleteProgram(mProgram); } namespace ProjectBrussel_UNITY_ID { @@ -155,10 +97,10 @@ Shader::ErrorCode CreateShader(GLuint& out, const char* src, int beginIdx, int e std::string log(len, '\0'); glGetShaderInfoLog(out, len, nullptr, log.data()); - return Shader ::CompilationFailed; + return Shader ::EC_CompilationFailed; } - return Shader::Success; + return Shader::EC_Success; } Shader::ErrorCode CreateShader(GLuint& out, std::string_view str, GLenum type) { @@ -176,17 +118,17 @@ Shader::ErrorCode LinkShaderProgram(GLuint program) { std::string log(len, '\0'); glGetProgramInfoLog(program, len, nullptr, log.data()); - return Shader::LinkingFailed; + return Shader::EC_LinkingFailed; } - return Shader::Success; + return Shader::EC_Success; } } // namespace ProjectBrussel_UNITY_ID -#define CATCH_ERROR_IMPL(x, name) \ - auto name = x; \ - if (name != Shader::Success) { \ - return name; \ +#define CATCH_ERROR_IMPL(x, name) \ + auto name = x; \ + if (name != Shader::EC_Success) { \ + return name; \ } #define CATCH_ERROR(x) CATCH_ERROR_IMPL(x, UNIQUE_NAME(result)) @@ -194,7 +136,7 @@ Shader::ErrorCode Shader::InitFromSources(const ShaderSources& sources) { using namespace ProjectBrussel_UNITY_ID; if (IsValid()) { - return ShaderAlreadyCreated; + return EC_AlreadyInitialized; } GLuint program = glCreateProgram(); @@ -238,9 +180,9 @@ Shader::ErrorCode Shader::InitFromSources(const ShaderSources& sources) { CATCH_ERROR(LinkShaderProgram(program)); sg.Dismiss(); - mHandle = program; + mProgram = program; - return Success; + return EC_Success; } Shader::ErrorCode Shader::InitFromSource(std::string_view source) { @@ -268,7 +210,7 @@ Shader::ErrorCode Shader::InitFromSource(std::string_view source) { auto CommitSection = [&]() -> ErrorCode { if (prevBegin == -1 || prevEnd == -1) { // Not actually "succeeding" here, but we just want to skip this call and continue - return Success; + return EC_Success; } if (prevShaderVariant == "vertex" && !vertex) { @@ -282,14 +224,14 @@ Shader::ErrorCode Shader::InitFromSource(std::string_view source) { } else if (prevShaderVariant == "fragment" && !fragment) { CATCH_ERROR(CreateShader(fragment, source.data(), prevBegin, prevEnd, GL_FRAGMENT_SHADER)); } else { - return InvalidShaderVariant; + return EC_InvalidShaderVariant; } prevBegin = -1; prevEnd = -1; prevShaderVariant.clear(); - return Success; + return EC_Success; }; constexpr const char* kMarker = "#type "; @@ -355,9 +297,9 @@ Shader::ErrorCode Shader::InitFromSource(std::string_view source) { CATCH_ERROR(LinkShaderProgram(program)); sg.Dismiss(); - mHandle = program; + mProgram = program; - return Success; + return EC_Success; } #undef CATCH_ERROR @@ -498,23 +440,10 @@ std::unique_ptr<ShaderVariable> CreateVariable(GLenum type, GLuint loc) { } } // namespace ProjectBrussel_UNITY_ID -bool Shader::CreateEmptyInfoIfAbsent() { - if (mInfo || !IsValid()) { - return false; - } - - mInfo = std::make_unique<ShaderInfo>(); - return true; -} - -bool Shader::GatherInfoIfAbsent() { +bool Shader::GatherInfoShaderIntrospection() { using namespace ProjectBrussel_UNITY_ID; - if (mInfo || !IsValid()) { - return false; - } - - mInfo = std::make_unique<ShaderInfo>(); + mInfo = {}; // TODO handle differnt types of variables with the same name @@ -522,45 +451,44 @@ bool Shader::GatherInfoIfAbsent() { return true; int inputCount; - glGetProgramInterfaceiv(mHandle, GL_PROGRAM_INPUT, GL_ACTIVE_RESOURCES, &inputCount); + glGetProgramInterfaceiv(mProgram, GL_PROGRAM_INPUT, GL_ACTIVE_RESOURCES, &inputCount); int outputCount; - glGetProgramInterfaceiv(mHandle, GL_PROGRAM_OUTPUT, GL_ACTIVE_RESOURCES, &outputCount); + glGetProgramInterfaceiv(mProgram, GL_PROGRAM_OUTPUT, GL_ACTIVE_RESOURCES, &outputCount); int uniformBlockCount; - glGetProgramInterfaceiv(mHandle, GL_UNIFORM_BLOCK, GL_ACTIVE_RESOURCES, &uniformBlockCount); + glGetProgramInterfaceiv(mProgram, GL_UNIFORM_BLOCK, GL_ACTIVE_RESOURCES, &uniformBlockCount); int uniformCount; - glGetProgramInterfaceiv(mHandle, GL_UNIFORM, GL_ACTIVE_RESOURCES, &uniformCount); + glGetProgramInterfaceiv(mProgram, GL_UNIFORM, GL_ACTIVE_RESOURCES, &uniformCount); // Gather inputs - auto GatherMathVars = [&](int count, GLenum resourceType, ShaderThingId::Kind resourceKind, std::vector<ShaderInfo::InputOutputThing>& list) { + auto GatherMathVars = [&](int count, GLenum resourceType, ShaderThingId::Kind resourceKind, std::vector<ShaderMathVariable>& list) { for (int i = 0; i < count; ++i) { const GLenum query[] = { GL_NAME_LENGTH, GL_TYPE, GL_LOCATION, GL_TOP_LEVEL_ARRAY_SIZE }; GLint props[std::size(query)]; - glGetProgramResourceiv(mHandle, resourceType, i, std::size(query), query, std::size(props), nullptr, props); + glGetProgramResourceiv(mProgram, resourceType, i, std::size(query), query, std::size(props), nullptr, props); auto& nameLength = props[0]; auto& type = props[1]; auto& loc = props[2]; auto& arrayLength = props[3]; std::string fieldName(nameLength - 1, '\0'); - glGetProgramResourceName(mHandle, GL_UNIFORM, i, nameLength, nullptr, fieldName.data()); + glGetProgramResourceName(mProgram, GL_UNIFORM, i, nameLength, nullptr, fieldName.data()); - mInfo->things.try_emplace(fieldName, ShaderThingId{ resourceKind, i }); + mInfo.things.try_emplace(fieldName, ShaderThingId{ resourceKind, i }); - auto& thing = list.emplace_back(ShaderInfo::InputOutputThing{}); - auto& var = thing.variable; - var.name = std::move(fieldName); - var.arrayLength = arrayLength; - QueryMathInfo(type, var.scalarType, var.width, var.height); + auto& thing = list.emplace_back(); + thing.name = std::move(fieldName); + thing.arrayLength = arrayLength; + QueryMathInfo(type, thing.scalarType, thing.width, thing.height); } }; - GatherMathVars(inputCount, GL_PROGRAM_INPUT, ShaderThingId::KD_Input, mInfo->inputs); - GatherMathVars(outputCount, GL_PROGRAM_OUTPUT, ShaderThingId::KD_Output, mInfo->outputs); + GatherMathVars(inputCount, GL_PROGRAM_INPUT, ShaderThingId::KD_Input, mInfo.inputs); + GatherMathVars(outputCount, GL_PROGRAM_OUTPUT, ShaderThingId::KD_Output, mInfo.outputs); // Gather uniform variables for (int i = 0; i < uniformCount; ++i) { const GLenum query[] = { GL_BLOCK_INDEX, GL_NAME_LENGTH, GL_TYPE, GL_LOCATION, GL_TOP_LEVEL_ARRAY_SIZE }; GLint props[std::size(query)]; - glGetProgramResourceiv(mHandle, GL_UNIFORM, i, std::size(query), query, std::size(props), nullptr, props); + glGetProgramResourceiv(mProgram, GL_UNIFORM, i, std::size(query), query, std::size(props), nullptr, props); auto& blockIndex = props[0]; // Index in interface block if (blockIndex != -1) { // If this is an interface block uniform, skip because it will be handled by our uniform blocks inspector continue; @@ -571,29 +499,182 @@ bool Shader::GatherInfoIfAbsent() { auto& arrayLength = props[4]; std::string fieldName(nameLength - 1, '\0'); - glGetProgramResourceName(mHandle, GL_UNIFORM, i, nameLength, nullptr, fieldName.data()); + glGetProgramResourceName(mProgram, GL_UNIFORM, i, nameLength, nullptr, fieldName.data()); - mInfo->things.try_emplace(fieldName, ShaderThingId{ ShaderThingId::KD_Uniform, i }); - mInfo->uniforms.push_back(CreateVariable(type, loc)); + mInfo.things.try_emplace(fieldName, ShaderThingId{ ShaderThingId::KD_Uniform, i }); + mInfo.uniforms.push_back(CreateVariable(type, loc)); } return true; } -ShaderInfo* Shader::GetInfo() const { - return mInfo.get(); +bool Shader::IsAnnoymous() const { + return mName.empty(); } -const std::string& Shader::GetName() const { - return mName; +bool Shader::IsValid() const { + return mProgram != 0; } -GLuint Shader::GetProgram() const { - return mHandle; +namespace ProjectBrussel_UNITY_ID { +void WriteShaderVariable(rapidjson::Value& value, rapidjson::Document& root, const ShaderVariable& var) { + value.AddMember("Name", var.name, root.GetAllocator()); + value.AddMember("Semantic", rapidjson::StringRef(Tags::NameOf(var.semantic)), root.GetAllocator()); + value.AddMember("OpenGLLocation", var.location, root.GetAllocator()); } -bool Shader::IsValid() const { - return mHandle != 0; +bool ReadShaderVariable(const rapidjson::Value& value, ShaderVariable& var) { + BRUSSEL_JSON_GET(value, "Name", std::string, var.name, return false); + { // Semantic + auto rvSemantic = rapidjson::GetProperty(value, rapidjson::kStringType, "Semantic"sv); + if (!rvSemantic) { + var.semantic = Tags::VES_Generic; + } else { + var.semantic = Tags::FindVertexElementSemantic(rapidjson::AsStringView(*rvSemantic)); + } + } + BRUSSEL_JSON_GET_DEFAULT(value, "OpenGLLocation", int, var.location, 0); + return true; +} + +void WriteShaderMathVariable(rapidjson::Value& value, rapidjson::Document& root, const ShaderMathVariable& var) { + WriteShaderVariable(value, root, var); + value.AddMember("ScalarType", rapidjson::StringRef(Tags::NameOfGLType(var.scalarType)), root.GetAllocator()); + value.AddMember("Width", var.width, root.GetAllocator()); + value.AddMember("Height", var.height, root.GetAllocator()); + value.AddMember("ArrayLength", var.arrayLength, root.GetAllocator()); +} + +bool ReadShaderMathVariable(const rapidjson::Value& value, ShaderMathVariable& var) { + if (!ReadShaderVariable(value, var)) return false; + { + auto rvScalar = rapidjson::GetProperty(value, rapidjson::kStringType, "ScalarType"sv); + if (!rvScalar) return false; + var.scalarType = Tags::FindGLType(rapidjson::AsStringView(*rvScalar)); + } + BRUSSEL_JSON_GET(value, "Width", int, var.width, return false); + BRUSSEL_JSON_GET(value, "Height", int, var.height, return false); + BRUSSEL_JSON_GET_DEFAULT(value, "ArrayLength", int, var.arrayLength, 1); + return true; +} + +void WriteShaderSamplerVariable(rapidjson::Value& value, rapidjson::Document& root, const ShaderSamplerVariable& var) { + WriteShaderVariable(value, root, var); + // TODO +} + +bool ReadShaderSamplerVariable(const rapidjson::Value& value, ShaderSamplerVariable& var) { + if (!ReadShaderVariable(value, var)) return false; + BRUSSEL_JSON_GET_DEFAULT(value, "ArrayLength", int, var.arrayLength, 1); + return true; +} +} // namespace ProjectBrussel_UNITY_ID + +bool Shader::SaveMetadataToFile(const fs::path& filePath) const { + using namespace ProjectBrussel_UNITY_ID; + + rapidjson::Document root(rapidjson::kObjectType); + + auto SaveMathVars = [&](const char* name, const std::vector<ShaderMathVariable>& vars) { + rapidjson::Value rvThings(rapidjson::kArrayType); + for (auto& thing : vars) { + rapidjson::Value rvThing(rapidjson::kObjectType); + WriteShaderMathVariable(rvThing, root, thing); + + rvThings.PushBack(rvThing, root.GetAllocator()); + } + root.AddMember(rapidjson::StringRef(name), rvThings, root.GetAllocator()); + }; + SaveMathVars("Inputs", mInfo.inputs); + SaveMathVars("Outputs", mInfo.outputs); + + // TODO uniforms + + auto file = Utils::OpenCstdioFile(filePath, Utils::WriteTruncate); + if (!file) return false; + DEFER { fclose(file); }; + + char writerBuffer[65536]; + rapidjson::FileWriteStream stream(file, writerBuffer, sizeof(writerBuffer)); + rapidjson::Writer<rapidjson::FileWriteStream> writer(stream); + root.Accept(writer); + + return true; +} + +bool Shader::LoadMetadataFromFile(const fs::path& filePath) { + using namespace ProjectBrussel_UNITY_ID; + + mInfo = {}; + + auto file = Utils::OpenCstdioFile(filePath, Utils::Read); + if (!file) return false; + DEFER { fclose(file); }; + + char readerBuffer[65536]; + rapidjson::FileReadStream stream(file, readerBuffer, sizeof(readerBuffer)); + + rapidjson::Document root; + root.ParseStream(stream); + + auto LoadMathVars = [&](std::string_view name, ShaderThingId::Kind kind, std::vector<ShaderMathVariable>& vars) { + auto rvThings = rapidjson::GetProperty(root, rapidjson::kArrayType, name); + if (!rvThings) return; + + for (auto& rv : rvThings->GetArray()) { + if (!rv.IsObject()) continue; + ShaderMathVariable thing; + ReadShaderMathVariable(rv, thing); + + mInfo.things.try_emplace(thing.name, ShaderThingId{ kind, (int)vars.size() }); + vars.push_back(std::move(thing)); + } + }; + LoadMathVars("Inputs"sv, ShaderThingId::KD_Input, mInfo.inputs); + LoadMathVars("Outputs"sv, ShaderThingId::KD_Output, mInfo.outputs); + + auto rvUniforms = rapidjson::GetProperty(root, rapidjson::kArrayType, "Uniforms"sv); + if (!rvUniforms) return false; + for (auto& rvUniform : rvUniforms->GetArray()) { + if (!rvUniform.IsObject()) continue; + + auto rvType = rapidjson::GetProperty(rvUniform, rapidjson::kStringType, "Type"sv); + if (!rvType) continue; + auto type = rapidjson::AsStringView(*rvType); + + auto rvValue = rapidjson::GetProperty(rvUniform, rapidjson::kObjectType, "Value"sv); + if (!rvValue) continue; + + bool autoFill; // TODO store autofill uniforms somewhere else + BRUSSEL_JSON_GET_DEFAULT(rvUniform, "AutoFill", bool, autoFill, false); + if (autoFill) continue; + + auto uniform = [&]() -> std::unique_ptr<ShaderVariable> { + if (type == "Math"sv) { + auto uniform = std::make_unique<ShaderMathVariable>(); + ReadShaderMathVariable(*rvValue, *uniform); + + return uniform; + } else if (type == "Sampler"sv) { + auto uniform = std::make_unique<ShaderSamplerVariable>(); + ReadShaderSamplerVariable(*rvValue, *uniform); + + return uniform; + } + + return nullptr; + }(); + if (uniform) { + mInfo.things.try_emplace(uniform->name, ShaderThingId{ ShaderThingId::KD_Uniform, (int)mInfo.uniforms.size() }); + mInfo.uniforms.emplace_back(std::move(uniform)); + } + } + + for (auto& uniform : mInfo.uniforms) { + uniform->location = glGetUniformLocation(mProgram, uniform->name.c_str()); + } + + return true; } void ShaderManager::DiscoverShaders() { @@ -625,7 +706,7 @@ void ShaderManager::DiscoverShaders() { // Load shader auto err = shader->InitFromSource(source); - if (err != Shader::Success) { + if (err != Shader::EC_Success) { continue; } @@ -633,8 +714,7 @@ void ShaderManager::DiscoverShaders() { fs::path infoPath(item.path()); infoPath.replace_extension(".json"); if (fs::exists(infoPath)) { - shader->CreateEmptyInfoIfAbsent(); - shader->GetInfo()->LoadFromFile(infoPath); + shader->LoadMetadataFromFile(infoPath); } mShaders.try_emplace(shaderName, std::move(shader)); diff --git a/source/Shader.hpp b/source/Shader.hpp index 79262a6..7ad2bbb 100644 --- a/source/Shader.hpp +++ b/source/Shader.hpp @@ -3,15 +3,17 @@ #include "EditorAttachment.hpp" #include "GraphicsTags.hpp" #include "RcPtr.hpp" +#include "Utils.hpp" -#include <absl/container/flat_hash_map.h> #include <glad/glad.h> +#include <robin_hood.h> #include <filesystem> #include <memory> #include <string_view> #include <vector> // TODO move to variable after pattern matching is in the language +// TODO migrate shader editor to Ires // Forward declarations class Shader; @@ -25,6 +27,9 @@ struct ShaderVariable { std::string name; Kind kind; GLuint location; + Tags::VertexElementSemantic semantic = Tags::VES_Generic; + + virtual void ShowInfo() const = 0; protected: ShaderVariable(Kind kind) @@ -39,6 +44,8 @@ struct ShaderMathVariable : public ShaderVariable { ShaderMathVariable() : ShaderVariable(KD_Math) {} + + virtual void ShowInfo() const override; }; struct ShaderSamplerVariable : public ShaderVariable { @@ -47,6 +54,8 @@ struct ShaderSamplerVariable : public ShaderVariable { ShaderSamplerVariable() : ShaderVariable(KD_Sampler) {} + + virtual void ShowInfo() const override; }; struct ShaderThingId { @@ -64,29 +73,27 @@ struct ShaderThingId { }; struct ShaderInfo { - struct InputOutputThing { - ShaderMathVariable variable; - Tags::VertexElementSemantic semantic = Tags::VES_Generic; - }; - - absl::flat_hash_map<std::string, ShaderThingId> things; - std::vector<InputOutputThing> inputs; - std::vector<InputOutputThing> outputs; + robin_hood::unordered_map<std::string, ShaderThingId, StringHash, StringEqual> things; + std::vector<ShaderMathVariable> inputs; + std::vector<ShaderMathVariable> outputs; std::vector<std::unique_ptr<ShaderVariable>> uniforms; + GLuint FindInputLocation(Tags::VertexElementSemantic semantic); + GLuint FindOutputLocation(Tags::VertexElementSemantic semantic); ShaderVariable* FindVariable(const ShaderThingId& thing); - - bool SaveToFile(const std::filesystem::path& filePath) const; - bool LoadFromFile(const std::filesystem::path& filePath); - void LoadLocations(const Shader& ownerShader); }; class Shader : public RefCounted { private: std::string mName; - std::unique_ptr<ShaderInfo> mInfo; + ShaderInfo mInfo; std::unique_ptr<EditorAttachment> mEditorAttachment; - GLuint mHandle = 0; + GLuint mProgram = 0; + +public: + GLuint autofillLoc_Transform = Tags::kInvalidLocation; + GLuint autofillLoc_Time = Tags::kInvalidLocation; + GLuint autofillLoc_DeltaTime = Tags::kInvalidLocation; public: Shader(std::string name = ""); @@ -97,14 +104,14 @@ public: Shader& operator=(Shader&&) = default; enum ErrorCode { - Success, + EC_Success, /// Generated when Init*() functions are called on an already initialized Shader object. - ShaderAlreadyCreated, + EC_AlreadyInitialized, /// Generated when the one-source-file text contains invalid or duplicate shader variants. - InvalidShaderVariant, - FileIOFailed, - CompilationFailed, - LinkingFailed, + EC_InvalidShaderVariant, + EC_FileIoFailed, + EC_CompilationFailed, + EC_LinkingFailed, }; struct ShaderSources { @@ -138,14 +145,20 @@ public: void GetDesignatedMetadataPath(char* buffer, int bufferSize); std::filesystem::path GetDesignatedMetadataPath(); - bool CreateEmptyInfoIfAbsent(); - bool GatherInfoIfAbsent(); - ShaderInfo* GetInfo() const; + /// Rebuild info object using OpenGL shader introspection API. Requires OpenGL 4.3 or above. Overrides existing info object. + bool GatherInfoShaderIntrospection(); + const ShaderInfo& GetInfo() const { return mInfo; } + ShaderInfo& GetInfo() { return mInfo; } /// If not empty, this name must not duplicate with any other shader object in the process. - const std::string& GetName() const; - GLuint GetProgram() const; + const std::string& GetName() const { return mName; } + GLuint GetProgram() const { return mProgram; } + bool IsAnnoymous() const; bool IsValid() const; + + bool SaveMetadataToFile(const std::filesystem::path& filePath) const; + /// Overrides existing info object. + bool LoadMetadataFromFile(const std::filesystem::path& filePath); }; class ShaderManager { @@ -153,7 +166,7 @@ public: static inline ShaderManager* instance = nullptr; private: - absl::flat_hash_map<std::string_view, RcPtr<Shader>> mShaders; + robin_hood::unordered_map<std::string_view, RcPtr<Shader>> mShaders; public: void DiscoverShaders(); diff --git a/source/Sprite.cpp b/source/Sprite.cpp new file mode 100644 index 0000000..cb8d327 --- /dev/null +++ b/source/Sprite.cpp @@ -0,0 +1,163 @@ +#include "Sprite.hpp" + +#include "Image.hpp" +#include "RapidJsonHelper.hpp" + +#include <rapidjson/document.h> +#include <memory> + +using namespace std::literals; + +bool Sprite::IsValid() const { + return mAtlas != nullptr; +} + +bool IresSpriteFiles::IsValid() const { + return !spriteFiles.empty(); +} + +Sprite* IresSpriteFiles::CreateInstance() const { + if (IsValid()) { + return nullptr; + } + + std::vector<Texture::AtlasSource> sources; + sources.resize(spriteFiles.size()); + for (auto& file : spriteFiles) { + } + + Texture::AtlasOutput atlasOut; + Texture::AtlasInput atlasIn{ + .sources = sources, + .packingMode = Texture::PM_KeepSquare, + }; + atlasIn.sources = sources; + auto atlas = std::make_unique<Texture>(); + if (atlas->InitAtlas(atlasIn, &atlasOut) != Texture::EC_Success) { + return nullptr; + } + + auto sprite = std::make_unique<Sprite>(); + sprite->mAtlas.Attach(atlas.release()); + sprite->mBoundingBox = atlasOut.elements[0].subregionSize; + sprite->mFrames.reserve(atlasOut.elements.size()); + for (auto& elm : atlasOut.elements) { + // Validate bounding box + if (sprite->mBoundingBox != elm.subregionSize) { + return nullptr; + } + + // Copy frame subregion + sprite->mFrames.push_back(elm.subregion); + } + return sprite.release(); +} + +Sprite* IresSpriteFiles::GetInstance() { + if (mInstance == nullptr) { + mInstance.Attach(CreateInstance()); + } + return mInstance.Get(); +} + +void IresSpriteFiles::InvalidateInstance() { + mInstance.Attach(nullptr); +} + +void IresSpriteFiles::Write(rapidjson::Value& value, rapidjson::Document& root) const { + value.AddMember("Sprites", rapidjson::WriteVectorPrimitives(root, spriteFiles.begin(), spriteFiles.end()), root.GetAllocator()); +} + +void IresSpriteFiles::Read(const rapidjson::Value& value) { + auto rvFileList = rapidjson::GetProperty(value, rapidjson::kArrayType, "Sprites"sv); + if (!rvFileList) return; + spriteFiles.clear(); + rapidjson::ReadVectorPrimitives<decltype(spriteFiles)>(*rvFileList, spriteFiles); +} + +bool IresSpritesheet::IsValid() const { + return !spritesheetFile.empty() && + sheetWSplit != 0 && + sheetHSplit != 0; +} + +void IresSpritesheet::ResplitSpritesheet(Sprite* sprite, int wSplit, int hSplit) { + auto atlas = sprite->GetAtlas(); + auto size = atlas->GetInfo().size; + int frameWidth = size.x / wSplit; + int frameHeight = size.y / hSplit; + + sprite->mBoundingBox = { frameWidth, frameHeight }; + sprite->mFrames.clear(); + sprite->mFrames.reserve(wSplit * hSplit); + + // Width and height in UV coordinates for each frame + float deltaU = 1.0f / wSplit; + float deltaV = 1.0f / hSplit; + for (int y = 0; y < hSplit; ++y) { + for (int x = 0; x < wSplit; ++x) { + auto& subregion = sprite->mFrames.emplace_back(); + // Top left + subregion.u0 = deltaU * x; + subregion.v0 = deltaV * y; + // Bottom right + subregion.u1 = subregion.u0 + deltaU; + subregion.u1 = subregion.v0 + deltaV; + } + } +} + +Sprite* IresSpritesheet::CreateInstance() const { + if (!IsValid()) { + return nullptr; + } + + auto atlas = std::make_unique<Texture>(); + if (!atlas->InitFromFile(spritesheetFile.c_str())) { + return nullptr; + } + + auto sprite = std::make_unique<Sprite>(); + sprite->mAtlas.Attach(atlas.release()); + ResplitSpritesheet(sprite.get(), sheetWSplit, sheetHSplit); + return sprite.release(); +} + +Sprite* IresSpritesheet::GetInstance() { + if (mInstance == nullptr) { + mInstance.Attach(CreateInstance()); + } + return mInstance.Get(); +} + +void IresSpritesheet::InvalidateInstance() { + mInstance.Attach(nullptr); +} + +void IresSpritesheet::Write(rapidjson::Value& value, rapidjson::Document& root) const { + value.AddMember("SpriteSheet", spritesheetFile, root.GetAllocator()); + value.AddMember("WSplit", sheetWSplit, root.GetAllocator()); + value.AddMember("HSplit", sheetHSplit, root.GetAllocator()); +} + +void IresSpritesheet::Read(const rapidjson::Value& value) { + BRUSSEL_JSON_GET(value, "SpriteSheet", std::string, spritesheetFile, return ); + BRUSSEL_JSON_GET(value, "WSplit", int, sheetWSplit, return ); + BRUSSEL_JSON_GET(value, "HSplit", int, sheetHSplit, return ); +} + +SpriteMesh::SpriteMesh(Sprite* sprite) + : mSprite(sprite) { +} + +void SpriteMesh::SetFrame(int frame) { + // TODO +} + +void SpriteMesh::PlayFrame() { + // TODO +} + +void SpriteMesh::SetPlaybackSpeed(int speed) { + // TODO +} diff --git a/source/Sprite.hpp b/source/Sprite.hpp new file mode 100644 index 0000000..ec25fbd --- /dev/null +++ b/source/Sprite.hpp @@ -0,0 +1,98 @@ +#pragma once + +#include "CpuMesh.hpp" +#include "Ires.hpp" +#include "Mesh.hpp" +#include "PodVector.hpp" +#include "RcPtr.hpp" +#include "Texture.hpp" + +#include <rapidjson/fwd.h> +#include <glm/glm.hpp> +#include <string> +#include <string_view> +#include <vector> + +class Sprite : public RefCounted { + friend class IresSpriteFiles; + friend class IresSpritesheet; + +private: + RcPtr<Texture> mAtlas; + glm::ivec2 mBoundingBox; + std::vector<Subregion> mFrames; + +public: + bool IsValid() const; + Texture* GetAtlas() const { return mAtlas.Get(); } + glm::ivec2 GetBoundingBox() const { return mBoundingBox; } + const decltype(mFrames)& GetFrames() const { return mFrames; } +}; + +class IresSpriteFiles : public IresObject { +public: + RcPtr<Sprite> mInstance; + std::vector<std::string> spriteFiles; + +public: + IresSpriteFiles() + : IresObject(KD_SpriteFiles) {} + + // NOTE: does not check whether all specified files have the same dimensions + bool IsValid() const; + + Sprite* CreateInstance() const; + Sprite* GetInstance(); + void InvalidateInstance(); + + void Write(rapidjson::Value& value, rapidjson::Document& root) const override; + void Read(const rapidjson::Value& value) override; +}; + +class IresSpritesheet : public IresObject { +public: + RcPtr<Sprite> mInstance; + std::string spritesheetFile; + int sheetWSplit; + int sheetHSplit; + +public: + IresSpritesheet() + : IresObject(KD_Spritesheet) {} + + bool IsValid() const; + + static void ResplitSpritesheet(Sprite* sprite, int wSplit, int hSplit); + + Sprite* CreateInstance() const; + Sprite* GetInstance(); + void InvalidateInstance(); + + void Write(rapidjson::Value& value, rapidjson::Document& root) const override; + void Read(const rapidjson::Value& value) override; +}; + +class SpriteMesh { +private: + RcPtr<GpuMesh> mMesh; + RcPtr<Sprite> mSprite; + PodVector<StandardVertex> mVertices; + PodVector<uint16_t> mIndices; + int mCurrentFrame = 0; + // # of frames per second + int mPlaybackSpeed = 5; + +public: + SpriteMesh(Sprite* sprite); + + Sprite* GetSprite() const { return mSprite.Get(); } + GpuMesh* GetGpuMesh() const { return mMesh.Get(); } + + int GetFrame() const { return mCurrentFrame; } + void SetFrame(int frame); + // Update as if a render frame has passed + void PlayFrame(); + + int GetPlaybackSpeed() const { return mPlaybackSpeed; } + void SetPlaybackSpeed(int speed); +}; diff --git a/source/Texture.cpp b/source/Texture.cpp index 08641e1..8fd74ac 100644 --- a/source/Texture.cpp +++ b/source/Texture.cpp @@ -1,90 +1,220 @@ #include "Texture.hpp" +#include "Macros.hpp" #include "PodVector.hpp" +#include "ScopeGuard.hpp" #include <stb_image.h> #include <stb_rect_pack.h> -#include <cstdint> +#include <bit> #include <cstring> -#include <deque> -#include <filesystem> -#include <fstream> -#include <stdexcept> #include <utility> Texture::~Texture() { glDeleteTextures(1, &mHandle); } -static GLenum MapTextureFilteringToGL(Texture::Filtering option) { +static GLenum MapTextureFilteringToGL(Tags::TexFilter option) { + using namespace Tags; switch (option) { - case Texture::LinearFilter: return GL_LINEAR; - case Texture::NearestFilter: return GL_NEAREST; + case TF_Linear: return GL_LINEAR; + case TF_Nearest: return GL_NEAREST; } return 0; } -bool Texture::InitFromFile(const char* filePath, const TextureProperties& props, bool flipVertically) { +Texture::ErrorCode Texture::InitFromFile(const char* filePath) { + if (IsValid()) { + return EC_AlreadyInitialized; + } + int width, height; int channels; - stbi_set_flip_vertically_on_load(flipVertically); auto result = (uint8_t*)stbi_load(filePath, &width, &height, &channels, 4); if (!result) { - return false; + return EC_FileIoFailed; } + DEFER { stbi_image_free(result); }; - glDeleteTextures(1, &mHandle); // In case the caller gave us glGenTextures(1, &mHandle); glBindTexture(GL_TEXTURE_2D, mHandle); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, ::MapTextureFilteringToGL(props.minifyingFilter)); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, ::MapTextureFilteringToGL(props.magnifyingFilter)); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, result); mInfo.size = { width, height }; - mInfo.isAtlas = false; - return true; + + return EC_Success; } -// bool Texture::InitFromImage(const Image& image, const TextureProperties& props, bool flipVertically) { -// GLenum sourceFormat; -// switch (image.GetChannels()) { -// case 1: sourceFormat = GL_RED; break; -// case 2: sourceFormat = GL_RG; break; -// case 3: sourceFormat = GL_RGB; break; -// case 4: sourceFormat = GL_RGBA; break; -// default: return false; -// } +Texture::ErrorCode Texture::InitFromImage(const Image& image) { + if (IsValid()) { + return EC_AlreadyInitialized; + } + + GLenum sourceFormat; + switch (image.GetChannels()) { + case 1: sourceFormat = GL_RED; break; + case 2: sourceFormat = GL_RG; break; + case 3: sourceFormat = GL_RGB; break; + case 4: sourceFormat = GL_RGBA; break; + default: return EC_InvalidImage; + } + + auto size = image.GetSize(); + uint8_t* dataPtr = image.GetDataPtr(); + + glGenTextures(1, &mHandle); + glBindTexture(GL_TEXTURE_2D, mHandle); -// auto size = image.GetSize(); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexImage2D(GL_TEXTURE_2D, 0, sourceFormat, size.x, size.y, 0, sourceFormat, GL_UNSIGNED_BYTE, dataPtr); -// std::unique_ptr<uint8_t[]> dataStorage; -// uint8_t* dataPtr; -// if (flipVertically) { -// dataStorage = std::make_unique<uint8_t[]>(image.GetDataLength()); -// dataPtr = dataStorage.get(); + mInfo.size = size; -// size_t rowStride = size.width * image.GetChannels() * sizeof(uint8_t); -// for (size_t y = 0; y < size.height; ++y) { -// size_t invY = (size.height - 1) - y; -// std::memcpy(dataPtr + invY * rowStride, image.GetDataPtr() + y * rowStride, rowStride); -// } -// } else { -// // dataStorage is unused, we read pixels directly from `image` -// dataPtr = image.GetDataPtr(); -// } + return EC_Success; +} -// glDeleteTextures(1, &mHandle); -// glGenTextures(1, &mHandle); -// glBindTexture(GL_TEXTURE_2D, mHandle); -// glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, ::MapTextureFilteringToGL(props.minifyingFilter)); -// glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, ::MapTextureFilteringToGL(props.magnifyingFilter)); -// glTexImage2D(GL_TEXTURE_2D, 0, sourceFormat, size.width, size.height, 0, sourceFormat, GL_UNSIGNED_BYTE, dataPtr); +Texture::ErrorCode Texture::InitAtlas(const AtlasInput& in, AtlasOutput* out) { + // Force RGBA for easier time uploading to GL texture + constexpr int kDesiredChannels = 4; -// mInfo.size = size; -// mInfo.isAtlas = false; -// return true; -// } + PodVector<stbrp_rect> rects; + rects.resize(in.sources.size()); + + for (size_t i = 0; i < in.sources.size(); ++i) { + auto size = in.sources[i].image.GetSize(); + auto& rect = rects[i]; + rect.w = static_cast<stbrp_coord>(size.x); + rect.h = static_cast<stbrp_coord>(size.y); + } + + int atlasWidth; + int atlasHeight; + + // 1. Pack the candidate rectanges onto the (not yet allocated) atlas + // Note that the coordinates here are top-left origin + switch (in.packingMode) { + case PM_KeepSquare: { + atlasWidth = 512; + atlasHeight = 512; + + PodVector<stbrp_node> nodes; + while (true) { + // No need to zero initialize stbrp_node, library will take care of that + nodes.resize(atlasWidth); + + stbrp_context ctx; + stbrp_init_target(&ctx, atlasWidth, atlasHeight, &nodes[0], (int)nodes.size()); + int result = stbrp_pack_rects(&ctx, rects.data(), (int)rects.size()); + + if (result != 1) { + atlasWidth *= 2; + atlasHeight *= 2; + } else { + // Break out of the while loop + break; + } + } + } break; + + case PM_VerticalExtension: + case PM_HorizontalExtension: { + constexpr int kMaxHeight = 1024 * 32; + atlasWidth = 0; + atlasHeight = 0; + + PodVector<stbrp_node> nodes; + stbrp_context ctx; + stbrp_init_target(&ctx, atlasWidth, atlasHeight, &nodes[0], nodes.size()); + stbrp_pack_rects(&ctx, rects.data(), rects.size()); + + // Calculate width/height needed for atlas + auto& limiter = in.packingMode == PM_VerticalExtension ? atlasHeight : atlasWidth; + for (auto& rect : rects) { + int bottom = rect.y + rect.h; + limiter = std::max(limiter, bottom); + } + limiter = std::bit_ceil<uint32_t>(limiter); + } break; + } + + // 2. Allocate atlas bitmap + + // Number of bytes in *bitmap* + auto bytes = atlasWidth * atlasHeight * kDesiredChannels * sizeof(uint8_t); + // Note that the origin (first pixel) is the bottom-left corner, to be consistent with OpenGL + auto bitmap = std::make_unique<uint8_t[]>(bytes); + std::memset(bitmap.get(), 0, bytes * sizeof(uint8_t)); + + // 3. Put all candidate images to the atlas bitmap + // TODO don't flip + // We essentially flip the candidate images vertically when putting into the atlas bitmap, so that when OpenGL reads + // these bytes, it sees the "bottom row" (if talking in top-left origin) first + // (empty spots are set with 0, "flipping" doesn't apply to them) + // + // Conceptually, we flip the atlas bitmap vertically so that the origin is at bottom-left + // i.e. all the coordinates we talk (e.g. rect.x/y) are still in top-left origin + + // Unit: bytes + size_t bitmapRowStride = atlasWidth * kDesiredChannels * sizeof(uint8_t); + for (size_t i = 0; i < in.sources.size(); ++i) { + auto& rect = rects[i]; + // Data is assumed to be stored in top-left origin + auto data = in.sources[i].image.GetDataPtr(); + + // We need to copy row by row, because the candidate image bytes won't land in a continuous chunk in our atlas bitmap + // Unit: bytes + size_t incomingRowStride = rect.w * kDesiredChannels * sizeof(uint8_t); + // Unit: bytes + size_t bitmapX = rect.x * kDesiredChannels * sizeof(uint8_t); + for (int y = 0; y < rect.h; ++y) { + auto src = data + y * incomingRowStride; + + int bitmapY = y; + auto dst = bitmap.get() + bitmapY * bitmapRowStride + bitmapX; + + std::memcpy(dst, src, incomingRowStride); + } + } + + // 4. Upload to VRAM + GLuint atlasTexture; + glGenTextures(1, &atlasTexture); + glBindTexture(GL_TEXTURE_2D, atlasTexture); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, atlasWidth, atlasHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, bitmap.get()); + + // 5. Generate atlas texture info + mHandle = atlasTexture; + mInfo.size = { atlasWidth, atlasHeight }; + + // 6. Generate output information + if (out) { + out->elements.reserve(in.sources.size()); + for (size_t i = 0; i < in.sources.size(); ++i) { + auto& rect = rects[i]; + auto& source = in.sources[i]; + out->elements.push_back(AltasElement{ + .name = source.name, + .subregion = Subregion{ + .u0 = (float)(rect.x) / atlasWidth, + .v0 = (float)(rect.y + rect.h) / atlasHeight, + .u1 = (float)(rect.x + rect.w) / atlasWidth, + .v1 = (float)(rect.y) / atlasHeight, + }, + .subregionSize = glm::ivec2(rect.w, rect.h), + }); + } + } + + return EC_Success; +} const TextureInfo& Texture::GetInfo() const { return mInfo; @@ -98,15 +228,20 @@ bool Texture::IsValid() const { return mHandle != 0; } -void TextureManager::DiscoverTextures() { - // TODO +Texture* IresTexture::CreateInstance() const { } -Texture* TextureManager::FindTexture(std::string_view name) { - auto iter = mTextures.find(name); - if (iter != mTextures.end()) { - return iter->second.Get(); - } else { - return nullptr; +Texture* IresTexture::GetInstance() { + if (mInstance == nullptr) { + mInstance.Attach(CreateInstance()); } + return mInstance.Get(); +} + +void IresTexture::Write(rapidjson::Value& value, rapidjson::Document& root) const { + // TODO +} + +void IresTexture::Read(const rapidjson::Value& value) { + // TODO } diff --git a/source/Texture.hpp b/source/Texture.hpp index c330bb3..ef4d136 100644 --- a/source/Texture.hpp +++ b/source/Texture.hpp @@ -1,18 +1,28 @@ #pragma once +#include "GraphicsTags.hpp" +#include "Image.hpp" +#include "Ires.hpp" #include "RcPtr.hpp" -#include <absl/container/flat_hash_map.h> #include <glad/glad.h> +#include <cstdint> #include <glm/glm.hpp> -#include <memory> +#include <span> // TODO abstract texture traits such as component sizes from OpenGL -class TextureInfo { -public: +struct Subregion { + float u0 = 0.0f; + float v0 = 0.0f; + float u1 = 0.0f; + float v1 = 0.0f; +}; + +struct TextureInfo { glm::ivec2 size; - bool isAtlas = false; + Tags::TexFilter minifyingFilter = Tags::TF_Linear; + Tags::TexFilter magnifyingFilter = Tags::TF_Linear; }; class Texture : public RefCounted { @@ -31,18 +41,41 @@ public: Texture(Texture&&) = default; Texture& operator=(Texture&&) = default; - enum Filtering { - LinearFilter, - NearestFilter, + enum ErrorCode { + EC_Success, + EC_AlreadyInitialized, + EC_FileIoFailed, + EC_InvalidImage, }; - struct TextureProperties { - Filtering minifyingFilter = LinearFilter; - Filtering magnifyingFilter = LinearFilter; + ErrorCode InitFromFile(const char* filePath); + ErrorCode InitFromImage(const Image& image); + + struct AtlasSource { + std::string name; + Image image; }; - bool InitFromFile(const char* filePath, const TextureProperties& props, bool flipVertically = false); - // bool InitFromImage(const Image& image, const TextureProperties& props, bool flipVertically = false); + struct AltasElement { + std::string name; + Subregion subregion; + glm::ivec2 subregionSize; + }; + + enum PackingMode { + PM_KeepSquare, + PM_VerticalExtension, + PM_HorizontalExtension, + }; + + struct AtlasInput { + std::span<AtlasSource> sources; + PackingMode packingMode; + }; + struct AtlasOutput { + std::vector<AltasElement> elements; + }; + ErrorCode InitAtlas(const AtlasInput& in, AtlasOutput* out = nullptr); const TextureInfo& GetInfo() const; GLuint GetHandle() const; @@ -50,32 +83,17 @@ public: bool IsValid() const; }; -/// A pure numerical subregion of a texture. u0/v0 are the UV coordinates of bottom left -/// corner, and u1/v1 are the top left corner. -struct Subregion { - /// Bottom left corner - float u0 = 0.0f; - float v0 = 0.0f; - /// Top right corner - float u1 = 0.0f; - float v1 = 0.0f; -}; - -/// A subregion of a specific texture. -struct TextureSubregion : public Subregion { - RcPtr<Texture> atlasTexture; -}; - -class TextureManager { +class IresTexture : public IresObject { public: - static inline TextureManager* instance = nullptr; - -private: - absl::flat_hash_map<std::string_view, RcPtr<Texture>> mTextures; + RcPtr<Texture> mInstance; public: - void DiscoverTextures(); + IresTexture() + : IresObject(KD_Texture) {} + + Texture* CreateInstance() const; + Texture* GetInstance(); - const auto& GetTextures() const { return mTextures; } - Texture* FindTexture(std::string_view name); + void Write(rapidjson::Value& value, rapidjson::Document& root) const override; + void Read(const rapidjson::Value& value) override; }; diff --git a/source/Utils.cpp b/source/Utils.cpp index d47f35b..5083eb7 100644 --- a/source/Utils.cpp +++ b/source/Utils.cpp @@ -56,3 +56,20 @@ FILE* Utils::OpenCstdioFile(const char* path, IoMode mode, bool binary) { return fopen(path, ::GetModeString(mode, binary)); #endif } + +bool Utils::InRangeInclusive(int n, int lower, int upper) { + if (lower > upper) { + std::swap(lower, upper); + } + return n >= lower && n <= upper; +} + +bool Utils::LineContains(glm::ivec2 p1, glm::ivec2 p2, glm::ivec2 candidate) { + bool verticalLine = p1.x == p2.x && InRangeInclusive(candidate.x, p1.x, p2.x); + bool horizontalLine = p1.y == p2.y && InRangeInclusive(candidate.y, p1.y, p2.y); + return verticalLine && horizontalLine; +} + +bool Utils::IsColinear(glm::ivec2 p1, glm::ivec2 p2) { + return p1.x == p2.x || p1.y == p2.y; +} diff --git a/source/Utils.hpp b/source/Utils.hpp index 03fdfed..6239667 100644 --- a/source/Utils.hpp +++ b/source/Utils.hpp @@ -1,7 +1,10 @@ #pragma once +#include <robin_hood.h> #include <cstdio> +#include <cstring> #include <filesystem> +#include <glm/glm.hpp> namespace Utils { @@ -18,4 +21,33 @@ constexpr float Abs(float v) noexcept { return v < 0.0f ? -v : v; } +bool InRangeInclusive(int n, int lower, int upper); +bool LineContains(glm::ivec2 p1, glm::ivec2 p2, glm::ivec2 candidate); +bool IsColinear(glm::ivec2 p1, glm::ivec2 p2); + } // namespace Utils + +struct StringHash { + using is_transparent = void; + + std::size_t operator()(const std::string& key) const { return robin_hood::hash_bytes(key.c_str(), key.size()); } + std::size_t operator()(std::string_view key) const { return robin_hood::hash_bytes(key.data(), key.size()); } + std::size_t operator()(const char* key) const { return robin_hood::hash_bytes(key, std::strlen(key)); } +}; + +struct StringEqual { + using is_transparent = int; + + bool operator()(std::string_view lhs, const std::string& rhs) const { + const std::string_view view = rhs; + return lhs == view; + } + + bool operator()(const char* lhs, const std::string& rhs) const { + return std::strcmp(lhs, rhs.c_str()) == 0; + } + + bool operator()(const std::string& lhs, const std::string& rhs) const { + return lhs == rhs; + } +}; diff --git a/source/main.cpp b/source/main.cpp index 8f16403..3d02f8d 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -2,14 +2,16 @@ #include "AppConfig.hpp" #include "EditorNotification.hpp" +#include "Ires.hpp" #include "Material.hpp" +#include "Mesh.hpp" #include "Shader.hpp" -#include "Texture.hpp" #define GLFW_INCLUDE_NONE #include <GLFW/glfw3.h> #include <backends/imgui_impl_glfw.h> +#include <backends/imgui_impl_opengl2.h> #include <backends/imgui_impl_opengl3.h> #include <glad/glad.h> #include <imgui.h> @@ -55,18 +57,35 @@ static void GlfwKeyCallback(GLFWwindow* window, int key, int scancode, int actio } int main(int argc, char* argv[]) { + using namespace Tags; + + constexpr auto kImGuiBackend = "imgui-backend"; constexpr auto kGameDataDir = "game-data-directory"; constexpr auto kGameAssetDir = "game-asset-directory"; cxxopts::Options options(std::string(AppConfig::kAppName), ""); // clang-format off options.add_options() + (kImGuiBackend, "ImGui backend. Options: opengl2, opengl3. Leave empty to default.", cxxopts::value<std::string>()) (kGameAssetDir, "Directory in which assets are looked up from. Can be relative paths to the executable.", cxxopts::value<std::string>()->default_value(".")) (kGameDataDir, "Directory in which game data (such as saves and options) are saved to. Leave empty to use the default directory on each platform.", cxxopts::value<std::string>()) - ; + ; // clang-format on auto args = options.parse(argc, argv); + bool imguiUseOpenGL3; + { + auto imguiBackend = args[kImGuiBackend].as<std::string>(); + if (imguiBackend == "opengl2") { + imguiUseOpenGL3 = false; + } else if (imguiBackend == "opengl3") { + imguiUseOpenGL3 = true; + } else { + // TODO support more backends? + imguiUseOpenGL3 = false; + } + } + { auto assetDir = args[kGameAssetDir].as<std::string>(); @@ -103,20 +122,20 @@ int main(int argc, char* argv[]) { // Decide GL+GLSL versions #if defined(IMGUI_IMPL_OPENGL_ES2) // GL ES 2.0 + GLSL 100 - const char* glsl_version = "#version 100"; + const char* imguiGlslVersion = "#version 100"; glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API); #elif defined(__APPLE__) // GL 3.2 + GLSL 150 - const char* glsl_version = "#version 150"; + const char* imguiGlslVersion = "#version 150"; glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // 3.2+ only glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // Required on Mac #else // GL 3.0 + GLSL 130 - const char* glsl_version = "#version 130"; + const char* imguiGlslVersion = "#version 130"; glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); #endif @@ -146,41 +165,99 @@ int main(int argc, char* argv[]) { ImGui::CreateContext(); ImGui_ImplGlfw_InitForOpenGL(window, true); - ImGui_ImplOpenGL3_Init(glsl_version); + if (imguiUseOpenGL3) { + ImGui_ImplOpenGL3_Init(imguiGlslVersion); + } else { + ImGui_ImplOpenGL2_Init(); + } ShaderManager::instance = new ShaderManager(); - TextureManager::instance = new TextureManager(); MaterialManager::instance = new MaterialManager(); + IresManager::instance = new IresManager(); ShaderManager::instance->DiscoverShaders(); - TextureManager::instance->DiscoverTextures(); MaterialManager::instance->DiscoverMaterials(); + IresManager::instance->DiscoverFilesDesignatedLocation(); + + gVformatStandard.Attach(new VertexFormat()); + gVformatStandard->AddElement(VertexElementFormat{ + .bindingIndex = 0, + .type = VET_Float3, + .semantic = VES_Position, + }); + gVformatStandard->AddElement(VertexElementFormat{ + .bindingIndex = 1, + .type = VET_Float2, + .semantic = VES_TexCoords1, + }); + gVformatStandard->AddElement(VertexElementFormat{ + .bindingIndex = 1, + .type = VET_Ubyte4Norm, + .semantic = VES_Color1, + }); + + gVformatStandardPacked.Attach(new VertexFormat()); + gVformatStandardPacked->AddElement(VertexElementFormat{ + .bindingIndex = 0, + .type = VET_Float3, + .semantic = VES_Position, + }); + gVformatStandardPacked->AddElement(VertexElementFormat{ + .bindingIndex = 0, + .type = VET_Float2, + .semantic = VES_TexCoords1, + }); + gVformatStandardPacked->AddElement(VertexElementFormat{ + .bindingIndex = 0, + .type = VET_Ubyte4Norm, + .semantic = VES_Color1, + }); app.Init(); while (!glfwWindowShouldClose(window)) { glfwPollEvents(); - ImGui_ImplOpenGL3_NewFrame(); - ImGui_ImplGlfw_NewFrame(); - ImGui::NewFrame(); + int fbWidth, fbHeight; + glfwGetFramebufferSize(window, &fbWidth, &fbHeight); + glViewport(0, 0, fbWidth, fbHeight); + auto clearColor = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); + glClearColor(clearColor.x * clearColor.w, clearColor.y * clearColor.w, clearColor.z * clearColor.w, clearColor.w); + glClear(GL_COLOR_BUFFER_BIT); + + { // Regular draw + app.Update(); + app.Draw(); + } - app.Show(); - ImGui::ShowNotifications(); + { // ImGui stuff + if (imguiUseOpenGL3) { + ImGui_ImplOpenGL3_NewFrame(); + } else { + ImGui_ImplOpenGL2_NewFrame(); + } + ImGui_ImplGlfw_NewFrame(); + ImGui::NewFrame(); - ImGui::Render(); - int display_w, display_h; - glfwGetFramebufferSize(window, &display_w, &display_h); - glViewport(0, 0, display_w, display_h); - auto clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); - glClearColor(clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w); - glClear(GL_COLOR_BUFFER_BIT); - ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); + app.Show(); + ImGui::ShowNotifications(); + + ImGui::Render(); + if (imguiUseOpenGL3) { + ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); + } else { + ImGui_ImplOpenGL2_RenderDrawData(ImGui::GetDrawData()); + } + } glfwSwapBuffers(window); } app.Shutdown(); - ImGui_ImplOpenGL3_Shutdown(); + if (imguiUseOpenGL3) { + ImGui_ImplOpenGL3_Shutdown(); + } else { + ImGui_ImplOpenGL2_Shutdown(); + } ImGui_ImplGlfw_Shutdown(); ImGui::DestroyContext(); |