diff --git a/.gitmodules b/.gitmodules index d013618..707d577 100644 --- a/.gitmodules +++ b/.gitmodules @@ -14,3 +14,6 @@ [submodule "Keishiki/ext/plf_colony"] path = Keishiki/ext/plf_colony url = https://github.com/mattreecebentley/plf_colony.git +[submodule "Keishiki/ext/fmt"] + path = Keishiki/ext/fmt + url = https://github.com/fmtlib/fmt.git diff --git a/Keishiki/CMakeLists.txt b/Keishiki/CMakeLists.txt index 61caa35..f1f85e5 100644 --- a/Keishiki/CMakeLists.txt +++ b/Keishiki/CMakeLists.txt @@ -1,7 +1,7 @@ # CMakeList.txt : CMake project for Keishiki, include source and define # project specific logic here. # -cmake_minimum_required (VERSION 3.12) +cmake_minimum_required (VERSION 3.16) project("Main") @@ -10,7 +10,7 @@ file(GLOB IMGUI_SRC ${PROJECT_SOURCE_DIR}/ext/imgui/*.cpp) file(GLOB IMPLOT_SRC ${PROJECT_SOURCE_DIR}/ext/implot/*.cpp) add_executable (Keishiki ${IMGUI_SRC} ${IMPLOT_SRC} - "ext/imgui_impl_bgfx.cpp" "ext/imgui/backends/imgui_impl_sdl2.cpp" "ext/imgui/misc/cpp/imgui_stdlib.cpp" + "ext/imgui_impl_bgfx.cpp" "ext/imgui/backends/imgui_impl_sdl3.cpp" "ext/imgui/misc/cpp/imgui_stdlib.cpp" ${SRC_FILES}) @@ -18,7 +18,10 @@ set_property(TARGET Keishiki PROPERTY CXX_STANDARD 23) include_directories ("include" "include/ext" "ext/imgui" "ext/implot" "ext/plf_colony") +add_subdirectory("ext/fmt") add_subdirectory("ext/freetype") + +add_compile_definitions(WL_EGL_PLATFORM) add_subdirectory("ext/bgfx") add_compile_definitions(IMGUI_DEFINE_MATH_OPERATORS) @@ -40,6 +43,9 @@ elseif (APPLE) find_package(SDL2 REQUIRED) target_link_libraries (Keishiki PRIVATE freetype bgfx bx SDL2::SDL2) else () - find_package(SDL2 REQUIRED) - target_link_libraries (Keishiki PRIVATE freetype bgfx bx SDL2::SDL2 SDL2::SDL2main) + find_package(SDL3 REQUIRED CONFIG REQUIRED COMPONENTS SDL3-shared) + find_package(ECM REQUIRED NO_MODULE) + set(CMAKE_MODULE_PATH ${ECM_FIND_MODULE_DIR}) + find_package(Wayland REQUIRED Egl) + target_link_libraries (Keishiki PRIVATE freetype bgfx bx SDL3::SDL3 ${Wayland_LIBRARIES} fmt) endif () diff --git a/Keishiki/Graphics.cpp b/Keishiki/Graphics.cpp index 3262c7b..ea30c17 100644 --- a/Keishiki/Graphics.cpp +++ b/Keishiki/Graphics.cpp @@ -5,13 +5,9 @@ #include -#include #include #include -#define STB_IMAGE_IMPLEMENTATION -#include - namespace { struct PosUVVertex { f32 x; @@ -52,7 +48,7 @@ namespace { error = FT_Load_Char(face, c, FT_LOAD_RENDER); if (error) { - K::LogError("Failed to load character"); + K::Log(K::K_L_Error, "Failed to load character"); } if (slot->bitmap.width != 0) { @@ -95,17 +91,17 @@ namespace { error = FT_New_Face(library, path, 0, &f.face); if (error == FT_Err_Unknown_File_Format) { - K::LogError("FreeType Unknown_File_Format"); + K::Log(K::K_L_Error, "FreeType Unknown_File_Format"); return std::nullopt; } else if (error) { - K::LogError("FreeType font loading error"); + K::Log(K::K_L_Error, "FreeType font loading error"); return std::nullopt; } error = FT_Set_Char_Size(f.face, 0, 20 * 64, 72, 72); if (error) { - K::LogError("FreeType Set_Char_Size error"); + K::Log(K::K_L_Error, "FreeType Set_Char_Size error"); return std::nullopt; } @@ -137,10 +133,10 @@ namespace { } namespace K::Graphics { - bool Init(u16 width, u16 height) { + bool Init() { error = FT_Init_FreeType(&library); if (error) { - LogError("FreeType init error: " + std::to_string(error)); + Log(K_L_Info, "FreeType init error: {}", error); return false; } diff --git a/Keishiki/Keishiki.cpp b/Keishiki/Keishiki.cpp index a5f5ebf..4662a76 100644 --- a/Keishiki/Keishiki.cpp +++ b/Keishiki/Keishiki.cpp @@ -2,38 +2,32 @@ #include "Graphics.h" #include "UI.h" -#include -#include -#include -#include +#if BX_PLATFORM_LINUX && WL_EGL_PLATFORM +#include +#endif + +#include +#include + #include #include -#include "backends/imgui_impl_sdl2.h" +#include "backends/imgui_impl_sdl3.h" #include -namespace { - K::CompState state; - const K::Resource::Resource *logo; -} - namespace K { K::AppState app_state; bool Init() { if (SDL_Init(SDL_INIT_VIDEO)) { - LogError(SDL_GetError()); + Log(K_L_Error, "{}", SDL_GetError()); return false; } - app_state.window = SDL_CreateWindow("Keishiki", - SDL_WINDOWPOS_CENTERED, - SDL_WINDOWPOS_CENTERED, - app_state.window_width, app_state.window_height, - SDL_WINDOW_SHOWN); + app_state.window = SDL_CreateWindow("Keishiki", app_state.window_width, app_state.window_height, SDL_WINDOW_RESIZABLE); if (app_state.window == nullptr) { - LogError(SDL_GetError()); + Log(K_L_Error, "{}", SDL_GetError()); return false; } @@ -43,100 +37,74 @@ namespace K { bgfxInit.resolution.height = app_state.window_height; bgfxInit.resolution.reset = BGFX_RESET_VSYNC; - -#if !BX_PLATFORM_EMSCRIPTEN - SDL_SysWMinfo wmi; - SDL_VERSION(&wmi.version); - if (!SDL_GetWindowWMInfo(app_state.window, &wmi)) { - LogError(SDL_GetError()); - } -#endif #if BX_PLATFORM_WINDOWS - bgfxInit.platformData.nwh = wmi.info.win.window; + bgfxInit.platformData.nwh = SDL_GetProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_WIN32_HWND_POINTER, NULL); #elif BX_PLATFORM_OSX - bgfxInit.platformData.nwh = wmi.info.cocoa.window; + bgfxInit.platformData.nwh = SDL_GetProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_COCOA_WINDOW_POINTER, NULL); bgfx::renderFrame(); #elif BX_PLATFORM_LINUX - bgfxInit.platformData.ndt = wmi.info.x11.display; - bgfxInit.platformData.nwh = (void*)(uintptr_t)wmi.info.x11.window; + if (SDL_strcmp(SDL_GetCurrentVideoDriver(), "x11") == 0) { + bgfxInit.platformData.ndt = SDL_GetProperty(SDL_GetWindowProperties(app_state.window), SDL_PROP_WINDOW_X11_DISPLAY_POINTER, NULL); + bgfxInit.platformData.nwh = (void*)SDL_GetNumberProperty(SDL_GetWindowProperties(app_state.window), SDL_PROP_WINDOW_X11_WINDOW_NUMBER, 0); + } +#if WL_EGL_PLATFORM + else if (SDL_strcmp(SDL_GetCurrentVideoDriver(), "wayland") == 0) { + bgfxInit.platformData.type = bgfx::NativeWindowHandleType::Wayland; + bgfxInit.platformData.ndt = SDL_GetProperty(SDL_GetWindowProperties(app_state.window), SDL_PROP_WINDOW_WAYLAND_DISPLAY_POINTER, NULL); + bgfxInit.platformData.nwh = SDL_GetProperty(SDL_GetWindowProperties(app_state.window), SDL_PROP_WINDOW_WAYLAND_EGL_WINDOW_POINTER, NULL); + if (!bgfxInit.platformData.nwh) { + bgfxInit.platformData.nwh = wl_egl_window_create((struct wl_surface *)SDL_GetProperty(SDL_GetWindowProperties(app_state.window), SDL_PROP_WINDOW_WAYLAND_SURFACE_POINTER, NULL), app_state.window_width, app_state.window_height); + SDL_SetProperty(SDL_GetWindowProperties(app_state.window), SDL_PROP_WINDOW_WAYLAND_EGL_WINDOW_POINTER, (void*)(uintptr_t)bgfxInit.platformData.nwh); + } + } +#endif #elif BX_PLATFORM_EMSCRIPTEN bgfxInit.platformData.nwh = (void*)"#canvas"; #endif if (!bgfx::init(bgfxInit)) { - LogError("bgfx initialization failed"); + Log(K_L_Error, "bgfx initialization failed"); return false; } - bgfx::setDebug(BGFX_DEBUG_TEXT); - bgfx::setViewRect(K::Graphics::K_VIEW_LOGO, 0, 0, app_state.window_width, app_state.window_height); - float view[16]; - bx::mtxTranslate(view, 0.f, 0.f, 1.0f); - float proj[16]; - bx::mtxOrtho(proj, 0.0f, app_state.window_width, 0.0f, app_state.window_height, 0.1f, 100.0f, 0.f, bgfx::getCaps()->homogeneousDepth); - bgfx::setViewTransform(K::Graphics::K_VIEW_LOGO, view, proj); - - K::Resource::Init(); + if (!K::Resource::Init()) + return false; K::UI::Init(app_state.window); - if (!K::Graphics::Init(app_state.window_width, app_state.window_height)) { - LogError("Graphics init failed"); + if (!K::Graphics::Init()) { + Log(K_L_Error, "Graphics init failed"); return false; } - logo = Resource::Load("Keishiki.png"); - - state.width = 1280; - state.height = 550; - state.current_frame = 0; - state.fps = 30.0f; - state.frame_max = 200; -// state.plugboard.in = Plugboard::K_P_CompositionIn{}; -// state.plugboard.out = Plugboard::K_P_CompositionOut{}; - K::UI::SetupViewport(state); + app_state.compositions.emplace_back("default project", 0, 100, 30.0f, 1280, 550); + app_state.inspecting_composition = 0; + K::UI::SetupViewport(app_state.compositions[0]); return true; } - void Render() { - K::Graphics::DrawTextureImageAlpha(K::Graphics::K_VIEW_LOGO, logo, app_state.window_width - logo->w + 10, 0, .4f); - - UI::Draw(app_state.bgfx_frame, state); - - const bgfx::Stats *stat = bgfx::getStats(); - const bgfx::Caps *caps = bgfx::getCaps(); - bgfx::dbgTextClear(); - bgfx::dbgTextPrintf(0, app_state.window_height/16 - 8, 0xf8, - " lachrymal.net :: %s Renderer :: Max Views %u :: Max FBs %u :: Max Texture Samplers %u :: Blitting %s :: %u FPS", - caps->supported & BGFX_CAPS_RENDERER_MULTITHREADED ? "Multithreaded" : "Singlethreaded", - caps->limits.maxViews, caps->limits.maxFrameBuffers, - caps->limits.maxTextureSamplers, caps->supported & BGFX_CAPS_TEXTURE_BLIT ? "OK" : "NO", - stat->cpuTimerFreq / stat->cpuTimeFrame); - bgfx::dbgTextPrintf(0, app_state.window_height/16 - 7, 0xf8, - " Project Keishiki :: %s :: Build %s %s :: SDL %i.%i.%i :: bgfx 1.%i :: Dear ImGui %s :: %s", - BX_COMPILER_NAME, __DATE__, __TIME__, SDL_MAJOR_VERSION, SDL_MINOR_VERSION, SDL_PATCHLEVEL, - BGFX_API_VERSION, ImGui::GetVersion(), bgfx::getRendererName(bgfx::getRendererType())); - - app_state.bgfx_frame = bgfx::frame(); - } - void Run() { - app_state.init_time = SDL_GetTicks64(); + app_state.init_time = SDL_GetTicks(); app_state.last_time = app_state.init_time; while (app_state.running.load()) { - app_state.current_time = SDL_GetTicks64(); + app_state.current_time = SDL_GetTicks(); app_state.delta_t = app_state.current_time - app_state.last_time; for (SDL_Event current_event; SDL_PollEvent(¤t_event) != 0;) { - ImGui_ImplSDL2_ProcessEvent(¤t_event); - if (current_event.type == SDL_QUIT) { + ImGui_ImplSDL3_ProcessEvent(¤t_event); + if (current_event.type == SDL_EVENT_QUIT) { app_state.running.store(false); break; } + if (current_event.type == SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED) { + SDL_GetWindowSize(app_state.window, &app_state.window_width, &app_state.window_height); + bgfx::reset(app_state.window_width, app_state.window_height); + UI::SetLogoView(); + } } - Render(); + UI::Draw(); app_state.last_time = app_state.current_time; } @@ -144,10 +112,14 @@ namespace K { void Shutdown() { K::Graphics::Shutdown(); - K::UI::Shutdown(state); + K::UI::Shutdown(); K::Resource::Shutdown(); bgfx::shutdown(); - SDL_DestroyWindow(app_state.window); + +#if !(BX_PLATFORM_LINUX && WL_EGL_PLATFORM) + SDL_DestroyWindow(app_state.window); +#endif + SDL_Quit(); } } diff --git a/Keishiki/Plugboard.cpp b/Keishiki/Plugboard.cpp index b6ed3ac..ee352f0 100644 --- a/Keishiki/Plugboard.cpp +++ b/Keishiki/Plugboard.cpp @@ -33,7 +33,7 @@ namespace K::Plugboard { to->out[to_out_index].outgoing.push_back({from_instance, input_index}); }, from_instance, to_instance); else - Log("Connect failed -- Cycle detected"); + Log(K_L_Warning, "Connect failed -- Cycle detected"); } void Disconnect(NodeInstanceP from_instance, u8 input_index) { @@ -58,7 +58,7 @@ namespace K::Plugboard { return arg; }, socket.value), type, good)); if (!good) - K::LogError("Type mismatch on plugboard evaluation!"); + Log(K_L_Warning, "Type mismatch on plugboard evaluation!"); return v; } @@ -353,7 +353,7 @@ namespace K::Plugboard { } T_Map::type FetchCompositionInAppTicks(const CompState& s, const CompositionIn& n) { - return T_Map::type{ static_cast(SDL_GetTicks64()) }; + return T_Map::type{ static_cast(SDL_GetTicks()) }; } void Plugboard::RecollectChains() { diff --git a/Keishiki/Resource.cpp b/Keishiki/Resource.cpp index f65dd29..0124ea5 100644 --- a/Keishiki/Resource.cpp +++ b/Keishiki/Resource.cpp @@ -3,6 +3,8 @@ #include #include #include + +#define STB_IMAGE_IMPLEMENTATION #include namespace { @@ -37,6 +39,7 @@ namespace K::Resource { std::filesystem::path ffmpeg_path = "ffmpeg"; std::filesystem::path shaderc_path = "./ext/bgfx/cmake/bgfx/shaderc"; + const char *shaderc_temp_args; Resource *fallback_still; @@ -46,7 +49,7 @@ namespace K::Resource { Resource res{.last_updated = std::filesystem::last_write_time(p)}; res.buf = stbi_load(p.c_str(), &res.w, &res.h, &res.channels, 0); if (res.buf == nullptr) { - K::LogError("Image loading failed for " + p.string()); + Log(K_L_Error, "Image loading failed for {}", p.string()); return nullptr; } const bgfx::Memory *ref = bgfx::makeRef(res.buf, res.w * res.h * res.channels * sizeof(Byte)); @@ -107,7 +110,7 @@ namespace K::Resource { i32 w, h, channels; Byte *buf = stbi_load(path.c_str(), &w, &h, &channels, 0); if (buf == nullptr) { - K::LogError("Image loading failed for " + path.string()); + Log(K_L_Error, "Image loading failed for {}", path.string()); return; } const bgfx::Memory *ref = bgfx::makeRef(buf, w * h * channels * sizeof(Byte)); @@ -127,7 +130,7 @@ namespace K::Resource { arg.tex = tex; } else { - LogError("Bad image! Has your app finished writing? -- " + path.string()); + Log(K_L_Error, "Bad image! Has your app finished writing? -- {}", path.string()); stbi_image_free(buf); } } @@ -148,21 +151,57 @@ namespace K::Resource { } } - void Init() { + bool Init() { switch (bgfx::getRendererType()) { case bgfx::RendererType::Noop: case bgfx::RendererType::Direct3D11: - case bgfx::RendererType::Direct3D12: shader_path = "shaders/dx11/"; break; - case bgfx::RendererType::Gnm: shader_path = "shaders/pssl/"; break; - case bgfx::RendererType::Metal: shader_path = "shaders/metal/"; break; - case bgfx::RendererType::OpenGL: shader_path = "shaders/glsl/"; break; - case bgfx::RendererType::OpenGLES: shader_path = "shaders/essl/"; break; - case bgfx::RendererType::Vulkan: shader_path = "shaders/spirv/"; break; - default: K::LogError("Unsupported renderer"); + case bgfx::RendererType::Direct3D12: + shader_path = "shaders/dx11/"; + shaderc_temp_args = "-f temp.frag " + "--type fragment " + "--platform windows " + "--profile " "s_5_0" " " + "--varyingdef temp.varying.def.sc " + "-i ./ " + "-o shaders/" "dx11" "/temp.frag.bin"; + break; + case bgfx::RendererType::Metal: + shader_path = "shaders/metal/"; + shaderc_temp_args = "-f temp.frag " + "--type fragment " + "--platform osx " + "--profile " "metal" " " + "--varyingdef temp.varying.def.sc " + "-i ./ " + "-o shaders/" "metal" "/temp.frag.bin"; + break; + case bgfx::RendererType::OpenGL: + shader_path = "shaders/glsl/"; + shaderc_temp_args = "-f temp.frag " + "--type fragment " + "--platform windows " + "--profile " "140" " " + "--varyingdef temp.varying.def.sc " + "-i ./ " + "-o shaders/" "glsl" "/temp.frag.bin"; + break; + case bgfx::RendererType::Vulkan: + shader_path = "shaders/spirv/"; + shaderc_temp_args = "-f temp.frag " + "--type fragment " + "--platform linux " + "--profile " "spirv" " ""--varyingdef temp.varying.def.sc " + "-i ./ " + "-o shaders/" "spirv" "/temp.frag.bin"; + break; + default: + Log(K_L_Info, "Unsupported renderer"); + return false; } reload_worker = std::thread(HotReload); fallback_still = Load("mmaker.png"); + return fallback_still != nullptr; } void Shutdown() { diff --git a/Keishiki/UI.cpp b/Keishiki/UI.cpp index 5954ac7..6da3216 100644 --- a/Keishiki/UI.cpp +++ b/Keishiki/UI.cpp @@ -6,25 +6,26 @@ #include #include -#include "backends/imgui_impl_sdl2.h" +#include "backends/imgui_impl_sdl3.h" #include "imgui_impl_bgfx.h" #include #include -#include #define STB_IMAGE_WRITE_IMPLEMENTATION #include #include +#include namespace { + const K::Resource::Resource *logo; + // Panels bool draw_viewport = true, draw_layer = true, draw_comp = true, draw_interpolation = true, -// draw_properties = true, draw_assets = true; const f32 row_height = 20.0f; @@ -37,10 +38,11 @@ namespace { save = BGFX_INVALID_HANDLE; bgfx::FrameBufferHandle composite_fb[2] = { BGFX_INVALID_HANDLE, BGFX_INVALID_HANDLE }, render_fb = BGFX_INVALID_HANDLE; - K::VisualTrack bg{}; f32 proj[16], transform[16], view[16]; + + K::VisualTrack bg{}; K::Byte *save_buffer; - bool save_called = false; + K::CompState *comp_save_called = nullptr; u32 ready_frame; } @@ -57,6 +59,14 @@ namespace ImGui { } namespace K::UI { + void SetLogoView() { + bgfx::setViewRect(Graphics::K_VIEW_LOGO, 0, 0, app_state.window_width, app_state.window_height); + float view[16]; + bx::mtxTranslate(view, 0.f, 0.f, 1.0f); + float proj[16]; + bx::mtxOrtho(proj, 0.0f, app_state.window_width, 0.0f, app_state.window_height, 0.1f, 100.0f, 0.f, bgfx::getCaps()->homogeneousDepth); + bgfx::setViewTransform(K::Graphics::K_VIEW_LOGO, view, proj); + } void AddTransformLayer(CompState& s, const String& name) { auto l = Layer{ @@ -107,7 +117,7 @@ namespace K::UI { s.layers.push_back(std::move(l)); } - void MainMenuBar(const CompState& s) { + void MainMenuBar() { if (ImGui::BeginMainMenuBar()) { if (ImGui::BeginMenu("Edit")) { // if (ImGui::MenuItem("Undo", "CTRL+Z")) {} @@ -393,14 +403,16 @@ namespace K::UI { ImGui::Text("%ux%u", s.width, s.height); ImGui::SameLine(); - ImGui::BeginDisabled(save_called); + ImGui::BeginDisabled(comp_save_called == nullptr); if (ImGui::Button("Save to frame.png")) { bgfx::blit(Graphics::K_VIEW_COMP_COMPOSITE + view_count, save, 0, 0, composite[layers_done % 2]); ready_frame = bgfx::readTexture(save, save_buffer); - save_called = true; + comp_save_called = &s; } ImGui::EndDisabled(); + app_state.n_views = Graphics::K_VIEW_COMP_COMPOSITE + view_count; + ImGui::SetNextItemWidth(50.0f); ImGui::DragFloat("FPS", &s.fps, 1.0f, 1.0f, 120.0f); @@ -474,7 +486,7 @@ namespace K::UI { node_delete_requested = false; } - if (!ImGui::Begin("Composition", &draw_comp, ImGuiWindowFlags_NoScrollbar)) { + if (!ImGui::Begin(("Composition: " + s.name).c_str(), &draw_comp, ImGuiWindowFlags_NoScrollbar)) { ImGui::End(); return; } @@ -483,7 +495,7 @@ namespace K::UI { if (ImGui::Shortcut(ImGuiKey_Space)) TogglePlay(); - static u32 node_current{}; + static u32 node_current = 0; if (ImGui::Button("Add")) { s.plugboard.nodes.Store(static_cast(node_current)); } @@ -1295,14 +1307,18 @@ namespace K::UI { ImPlot::PushStyleColor(ImPlotCol_PlotBg, 0); ImPlot::PushStyleColor(ImPlotCol_PlotBorder, 0); if (ImPlot::BeginPlot("uniform", {view_width, ImGui::GetContentRegionAvail().y}, ImPlotFlags_CanvasOnly | ImPlotFlags_NoFrame | ImPlotFlags_NoInputs)) { + static bool started_dragging = false, dragging = false; + ImPlot::SetupAxis(ImAxis_X1, "time", ImPlotAxisFlags_NoHighlight | ImPlotAxisFlags_NoLabel | ImPlotAxisFlags_NoGridLines); - ImPlot::SetupAxis(ImAxis_Y1, "val", ImPlotAxisFlags_NoHighlight | ImPlotAxisFlags_NoLabel | ImPlotAxisFlags_Opposite | ImPlotAxisFlags_NoTickLabels | ImPlotAxisFlags_NoTickMarks | ImPlotAxisFlags_AutoFit); + auto flags = ImPlotAxisFlags_NoHighlight | ImPlotAxisFlags_NoLabel | ImPlotAxisFlags_Opposite | ImPlotAxisFlags_NoTickLabels | ImPlotAxisFlags_NoTickMarks; + if (!dragging) + flags |= ImPlotAxisFlags_AutoFit; + ImPlot::SetupAxis(ImAxis_Y1, "val", flags); const f32 begin = view_left * static_cast(s.frame_max + 1), end = view_right * static_cast(s.frame_max + 1); ImPlot::SetupAxisLimits(ImAxis_X1, begin, end, ImGuiCond_Always); ImPlot::SetupFinish(); - static bool started_dragging = false, dragging = false; if (started_dragging) { dragging = true; } @@ -1377,15 +1393,20 @@ namespace K::UI { ImPlot::Annotation(p3x, p3y, {1.0f, 1.0f, 1.0f, 1.0f}, {15.0f, 0.0f}, true); static f32 x_tmp, y_tmp; + static f32 cap_l, cap_r; ImGui::SetCursorScreenPos(ImPlot::PlotToPixels(p2x, p2y) - ImVec2{10.0f / 2.0f, 10.0f / 2.0f}); ImGui::Button("##p2", {10.0f, 10.0f}); if (ImGui::IsItemClicked()) { x_tmp = segment.v[0]; y_tmp = segment.v[1]; + auto cp1 = segment.v[2] + 1.0f; + auto d_sqrt = std::sqrt(cp1 * cp1 - 4.0f * segment.v[2] * segment.v[2]); + cap_l = (cp1 - d_sqrt) / 2.0f; + cap_r = (cp1 + d_sqrt) / 2.0f; } if (ImGui::IsItemActive()) { - segment.v[0] = std::clamp(x_tmp + static_cast(drag_off_x) / (static_cast(frame_nxt) - static_cast(frame)), 0.0f, 1.0f); + segment.v[0] = std::clamp(x_tmp + static_cast(drag_off_x) / (static_cast(frame_nxt) - static_cast(frame)), segment.v[2] >= 0.0f ? 0.0f : cap_l, cap_r); segment.v[1] = y_tmp + static_cast(drag_off_y / (v_nxt - v)); } @@ -1394,9 +1415,12 @@ namespace K::UI { if (ImGui::IsItemClicked()) { x_tmp = segment.v[2]; y_tmp = segment.v[3]; + auto d_sqrt = std::sqrt(4.0f * segment.v[0] - 3.0f * segment.v[0] * segment.v[0]); + cap_l = (segment.v[0] - d_sqrt) / 2.0f; + cap_r = (segment.v[0] + d_sqrt) / 2.0f; } if (ImGui::IsItemActive()) { - segment.v[2] = std::clamp(x_tmp + static_cast(drag_off_x) / (static_cast(frame_nxt) - static_cast(frame)), 0.0f, 1.0f); + segment.v[2] = std::clamp(x_tmp + static_cast(drag_off_x) / (static_cast(frame_nxt) - static_cast(frame)), cap_l, segment.v[0] <= 1.0f ? 1.0f : cap_r); segment.v[3] = y_tmp + static_cast(drag_off_y / (v_nxt - v)); } @@ -1691,25 +1715,29 @@ namespace K::UI { ImGui::SetCursorPos(ImVec2{w * .8f, w - 15.0f,}); ImGui::Text("%.2f", y_min); + static f32 y_range_old{}; + ImVec2 size = {15.0f, 15.0f}; ImGui::SetCursorScreenPos(tp2 - size / 2.0f); ImVec2 off = io.MousePos - io.MouseClickedPos[0]; ImGui::Button("##P2", size); if (ImGui::IsItemClicked()) { + y_range_old = y_range; p2_old = p2; } if (ImGui::IsItemActive()) { - p2 = p2_old + ImVec2{off.x / w, off.y / -w * y_range}; + p2 = p2_old + ImVec2{off.x / w, off.y / -w * y_range_old}; p2.x = std::clamp(p2.x, 0.0f, 1.0f); } ImGui::SetCursorScreenPos(tp3 - size / 2.0f); ImGui::Button("##P3", size); if (ImGui::IsItemClicked()) { + y_range_old = y_range; p3_old = p3; } if (ImGui::IsItemActive()) { - p3 = p3_old + ImVec2{off.x / w, off.y / -w * y_range}; + p3 = p3_old + ImVec2{off.x / w, off.y / -w * y_range_old}; p3.x = std::clamp(p3.x, 0.0f, 1.0f); } } @@ -1754,9 +1782,8 @@ namespace K::UI { ImGui::End(); } - void Assets(CompState& s) { + void Assets() { if (ImGui::Begin("Assets", &draw_assets)) { - ImGui::TextUnformatted("Loaded assets:"); if (ImGui::BeginTable("Assets", 3, ImGuiTableFlags_Borders)) { ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthStretch); ImGui::TableSetupColumn("Type", ImGuiTableColumnFlags_WidthFixed); @@ -1764,6 +1791,16 @@ namespace K::UI { ImGui::TableHeadersRow(); i32 i = 0; + for (auto& comp : app_state.compositions) { + ImGui::TableNextRow(); + ImGui::PushID(i++); + ImGui::TableNextColumn(); + ImGui::Text("%s", comp.name.c_str()); + ImGui::TableNextColumn(); + ImGui::Text("Composition"); + ImGui::TableNextColumn(); + ImGui::PopID(); + } for (auto& [name, res] : Resource::resources) { ImGui::TableNextRow(); ImGui::PushID(i++); @@ -1789,10 +1826,10 @@ namespace K::UI { } ImGui::TableNextColumn(); if constexpr (std::is_same_v>) { - ImGui::Text("%s", std::format("{:%Y-%m-%d %X}", arg.frag_last_updated).c_str()); + ImGui::Text("%s", fmt::format("{:%Y-%m-%d %X}", std::chrono::file_clock::to_sys(arg.frag_last_updated)).c_str()); } else { - ImGui::Text("%s", std::format("{:%Y-%m-%d %X}", arg.last_updated).c_str()); + ImGui::Text("%s", fmt::format("{:%Y-%m-%d %X}", std::chrono::file_clock::to_sys(arg.last_updated)).c_str()); } }, res); ImGui::PopID(); @@ -1803,31 +1840,55 @@ namespace K::UI { ImGui::End(); } - void Draw(u32 frame, CompState& s) { - ImGui_ImplSDL2_NewFrame(); + void Draw() { + ImGui_ImplSDL3_NewFrame(); ImGui_Implbgfx_NewFrame(); ImGui::NewFrame(); + Graphics::DrawTextureImageAlpha(K::Graphics::K_VIEW_LOGO, logo, app_state.window_width - logo->w + 10, 0, .4f); + ImGui::DockSpaceOverViewport(0, ImGui::GetMainViewport(), ImGuiDockNodeFlags_PassthruCentralNode); ImGui::ShowDemoWindow(); - MainMenuBar(s); - if (draw_viewport) Viewport(s); // Must go before comp! - if (draw_layer) Layer(s); - if (draw_comp) Composition(s); - if (draw_interpolation) Interpolation(s); - if (draw_assets) Assets(s); + MainMenuBar(); - if (save_called && ready_frame <= frame) { - stbi_write_png("frame.png", static_cast(s.width), static_cast(s.height), 4, save_buffer, static_cast(s.width) * 4); - save_called = false; + if (draw_assets) Assets(); + + auto& comp = app_state.compositions[app_state.inspecting_composition]; + if (draw_viewport) Viewport(comp); + if (draw_layer) Layer(comp); + if (draw_comp) Composition(comp); + if (draw_interpolation) Interpolation(comp); + + if (comp_save_called != nullptr && ready_frame <= app_state.bgfx_frame) { + stbi_write_png("frame.png", static_cast(comp_save_called->width), static_cast(comp_save_called->height), 4, save_buffer, static_cast(comp_save_called->width) * 4); + comp_save_called = nullptr; } ImGui::Render(); ImGui_Implbgfx_RenderDrawLists(ImGui::GetDrawData()); + + const bgfx::Stats *stat = bgfx::getStats(); + const bgfx::Caps *caps = bgfx::getCaps(); + bgfx::dbgTextClear(); + bgfx::dbgTextPrintf(0, app_state.window_height/16 - 8, 0xf8, + " lachrymal.net :: %s Renderer :: %u/%u Views :: %u FBs :: %u Samplers :: Blit %s :: %s :: %u FPS", + caps->supported & BGFX_CAPS_RENDERER_MULTITHREADED ? "Multithreaded" : "Singlethreaded", + app_state.n_views, caps->limits.maxViews, caps->limits.maxFrameBuffers, + caps->limits.maxTextureSamplers, caps->supported & BGFX_CAPS_TEXTURE_BLIT ? "OK" : "NO", + SDL_GetCurrentVideoDriver(), + stat->cpuTimerFreq / stat->cpuTimeFrame); + bgfx::dbgTextPrintf(0, app_state.window_height/16 - 7, 0xf8, + " Project Keishiki :: %s :: Build %s %s :: SDL %i.%i.%i :: bgfx 1.%i :: Dear ImGui %s :: %s", + BX_COMPILER_NAME, __DATE__, __TIME__, SDL_MAJOR_VERSION, SDL_MINOR_VERSION, SDL_MICRO_VERSION, + BGFX_API_VERSION, ImGui::GetVersion(), bgfx::getRendererName(bgfx::getRendererType())); + + app_state.bgfx_frame = bgfx::frame(); } void Init(SDL_Window *window) { + bgfx::setDebug(BGFX_DEBUG_TEXT); + ImGui::CreateContext(); ImPlot::CreateContext(); ImGuiIO& io = ImGui::GetIO(); @@ -1835,37 +1896,42 @@ namespace K::UI { io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; ImGui_Implbgfx_Init(Graphics::K_VIEW_UI); bgfx::setViewClear(Graphics::K_VIEW_UI, BGFX_CLEAR_COLOR); + SetLogoView(); switch (bgfx::getRendererType()) { case bgfx::RendererType::Noop: case bgfx::RendererType::Direct3D11: case bgfx::RendererType::Direct3D12: - ImGui_ImplSDL2_InitForD3D(window); + ImGui_ImplSDL3_InitForD3D(window); break; case bgfx::RendererType::Metal: - ImGui_ImplSDL2_InitForMetal(window); + ImGui_ImplSDL3_InitForMetal(window); break; case bgfx::RendererType::OpenGL: - ImGui_ImplSDL2_InitForOpenGL(window, nullptr); + ImGui_ImplSDL3_InitForOpenGL(window, nullptr); break; case bgfx::RendererType::Vulkan: - ImGui_ImplSDL2_InitForVulkan(window); + ImGui_ImplSDL3_InitForVulkan(window); break; default: - LogError("Unsupported Renderer"); + Log(K_L_Error, "Unsupported Renderer"); } bg.pg = Resource::Load("Checkerboard")->pg; bg.AddUniform("f_hw", ShaderGraph::Type::T_XYZ); + + logo = Resource::Load("Keishiki.png"); } - void Shutdown(CompState& s) { + void Shutdown() { bgfx::destroy(bg.uniforms[0].handle); bg.uniforms.erase(bg.uniforms.begin()); - DestroyViewport(s); + for (auto& comp : app_state.compositions) + DestroyViewport(comp); + ImGui_Implbgfx_Shutdown(); - ImGui_ImplSDL2_Shutdown(); + ImGui_ImplSDL3_Shutdown(); ImPlot::DestroyContext(); ImGui::DestroyContext(); } diff --git a/Keishiki/VisualTrack.cpp b/Keishiki/VisualTrack.cpp index 777aeb4..bc43800 100644 --- a/Keishiki/VisualTrack.cpp +++ b/Keishiki/VisualTrack.cpp @@ -127,45 +127,14 @@ namespace K { String s = Resource::shaderc_path; s += " "; - switch (bgfx::getRendererType()) { - case bgfx::RendererType::Noop: - case bgfx::RendererType::Direct3D11: - case bgfx::RendererType::Direct3D12: s += "-f temp.frag " - "--type fragment " - "--platform windows " - "--profile " "s_5_0" " " - "--varyingdef temp.varying.def.sc " - "-i ./ " - "-o shaders/" "dx11" "/temp.frag.bin"; break; - case bgfx::RendererType::Metal: s += "-f temp.frag " - "--type fragment " - "--platform osx " - "--profile " "metal" " " - "--varyingdef temp.varying.def.sc " - "-i ./ " - "-o shaders/" "metal" "/temp.frag.bin"; break; - case bgfx::RendererType::OpenGL: s += "-f temp.frag " - "--type fragment " - "--platform windows " - "--profile " "140" " " - "--varyingdef temp.varying.def.sc " - "-i ./ " - "-o shaders/" "glsl" "/temp.frag.bin"; break; - case bgfx::RendererType::Vulkan: s += "-f temp.frag " - "--type fragment " - "--platform linux " - "--profile " "spirv" " ""--varyingdef temp.varying.def.sc " - "-i ./ " - "-o shaders/" "spirv" "/temp.frag.bin"; break; - default: LogError("Unsupported renderer"); break; - } + s += Resource::shaderc_temp_args; if (std::system(s.c_str()) == 0) { if (isValid(pg)) bgfx::destroy(pg); pg = Resource::FetchUnmanagedShaderProgram("temp"); } else - Log("User shader compilation failed"); + Log(K_L_Warning, "User shader compilation failed"); } void VisualTrack::Clear() { diff --git a/Keishiki/ext/fmt b/Keishiki/ext/fmt new file mode 160000 index 0000000..bbf44cc --- /dev/null +++ b/Keishiki/ext/fmt @@ -0,0 +1 @@ +Subproject commit bbf44cc000531dc7737d5321ccfa9f2f11b20127 diff --git a/Keishiki/include/Common.h b/Keishiki/include/Common.h index 412bb08..df91105 100644 --- a/Keishiki/include/Common.h +++ b/Keishiki/include/Common.h @@ -16,7 +16,7 @@ typedef uint64_t u64; #include #include #include -#include +#include namespace K { using String = std::string; @@ -25,19 +25,23 @@ namespace K { template using Vector = std::vector; template using Dict = std::unordered_map; - inline void LogBase(const std::string_view& s, u8 level) { - static const char *levels[] = { - "Info", - "Warning", - "Error" - }; - level > 1 ? std::cerr : std::cout << "[" << levels[level] << "] Keishiki: " << s << std::endl; - } - inline void Log(const std::string_view& s) { - LogBase(s, 0); - } - inline void LogError(const std::string_view& s) { - LogBase(s, 2); + enum LogLevel { + K_L_Info, + K_L_Warning, + K_L_Error + }; + + static const char *LogLevelNames[] = { + "Info", + "Warning", + "Error" + }; + + template + inline void Log(LogLevel level, fmt::format_string fmt, Ts&&... args) { + fmt::print(level > K_L_Info ? stderr : stdout, "[{}] Keishiki: ", LogLevelNames[level]); + fmt::print(fmt, std::forward(args)...); + fmt::print("\n"); } struct CompState; // fwd declared -- Keishiki.h <-> VisualTrack.h has dependency cycle diff --git a/Keishiki/include/Graphics.h b/Keishiki/include/Graphics.h index cf93469..838f773 100644 --- a/Keishiki/include/Graphics.h +++ b/Keishiki/include/Graphics.h @@ -2,7 +2,7 @@ #include "Common.h" #include -#include +#include #include #include @@ -18,7 +18,7 @@ namespace K::Graphics { K_VIEW_COMP_COMPOSITE }; - bool Init(u16 width, u16 height); + bool Init(); void Shutdown(); void DrawQuad(u32 view_id, f32 mtx[16], u64 state, const bgfx::ProgramHandle& pg); diff --git a/Keishiki/include/Keishiki.h b/Keishiki/include/Keishiki.h index 61ae1b9..d1ed116 100644 --- a/Keishiki/include/Keishiki.h +++ b/Keishiki/include/Keishiki.h @@ -9,8 +9,6 @@ namespace K { void Run(); void Shutdown(); - void Render(); - struct Layer { VisualTrack track; String name; @@ -19,6 +17,7 @@ namespace K { }; struct CompState { + String name; // Time u64 current_frame; u64 frame_max; @@ -36,11 +35,16 @@ namespace K { extern struct AppState { SDL_Window *window = nullptr; - u16 window_width = 2300, + i32 window_width = 2300, window_height = 1300; + std::atomic_bool running {true}; + u64 init_time{}, last_time{}, current_time{}, delta_t{}; u32 bgfx_frame{}; - std::atomic_bool running {true}; + u32 n_views{}; + + i32 inspecting_composition = -1; + Vector compositions{}; } app_state; // global ! } diff --git a/Keishiki/include/Plugboard.h b/Keishiki/include/Plugboard.h index 7e98f1c..740dd99 100644 --- a/Keishiki/include/Plugboard.h +++ b/Keishiki/include/Plugboard.h @@ -1,10 +1,8 @@ #pragma once #include "Common.h" #include "Graphics.h" -#include #include #include -#include #include #include #include @@ -373,7 +371,7 @@ namespace K::Plugboard { T_Map::type FetchNegate(const CompState& s, const Negate& n); struct Negate { - const static char constexpr *name = static_cast("Add"); + const static char constexpr *name = static_cast("Negate"); std::array in = { InSocket{"a", T_Float } }; std::array out = { OutSocket{ "-a", T_Float } }; std::array::type (*)(const CompState&, const Negate&), 1> fetch = { FetchNegate }; diff --git a/Keishiki/include/Resource.h b/Keishiki/include/Resource.h index 92f099a..1e7b55a 100644 --- a/Keishiki/include/Resource.h +++ b/Keishiki/include/Resource.h @@ -7,6 +7,7 @@ namespace K::Resource { extern std::filesystem::path ffmpeg_path; extern std::filesystem::path shaderc_path; + extern const char *shaderc_temp_args; extern std::shared_mutex resource_lock; @@ -75,7 +76,7 @@ namespace K::Resource { extern Resource *fallback_still; - void Init(); + bool Init(); void Shutdown(); template diff --git a/Keishiki/include/UI.h b/Keishiki/include/UI.h index dfa41f4..b88345c 100644 --- a/Keishiki/include/UI.h +++ b/Keishiki/include/UI.h @@ -4,7 +4,9 @@ namespace K::UI { void Init(SDL_Window *window); - void Draw(u32 frame, CompState& s); - void Shutdown(CompState& s); + void Draw(); + void Shutdown(); void SetupViewport(CompState& s); + void DestroyViewport(CompState& s); + void SetLogoView(); } diff --git a/Keishiki/include/VisualTrack.h b/Keishiki/include/VisualTrack.h index 5b4eb16..5ddcbdc 100644 --- a/Keishiki/include/VisualTrack.h +++ b/Keishiki/include/VisualTrack.h @@ -26,7 +26,7 @@ namespace K { Plugboard::T_Map::type ShaderValToPlugboard(const ShaderGraph::T_Map::type& val); struct VisualTrack { - ShaderGraph::ShaderGraph tree; +// ShaderGraph::ShaderGraph tree; bgfx::ProgramHandle pg = BGFX_INVALID_HANDLE; String shader; Vector uniforms; diff --git a/README.md b/README.md index 8c96f43..00e676d 100644 --- a/README.md +++ b/README.md @@ -5,9 +5,10 @@ ## Libraries - [bgfx](https://github.com/bkaradzic/bgfx) - [Dear ImGui](https://github.com/ocornut/imgui) -- [ImPlot](https://github.com/epezent/implot) +- [fmt](https://github.com/fmtlib/fmt) - [FreeType](https://freetype.org/) - [imgui_impl_bgfx](https://gist.github.com/pr0g/aff79b71bf9804ddb03f39ca7c0c3bbb) +- [ImPlot](https://github.com/epezent/implot) - [SDL2](https://www.libsdl.org/) - [stb](https://github.com/nothings/stb) - [SRELL](https://www.akenotsuki.com/misc/srell/en/) diff --git a/TODO.md b/TODO.md index 2abafde..f679b00 100644 --- a/TODO.md +++ b/TODO.md @@ -10,6 +10,7 @@ - Pre-compose/Layer Groups - Undo's - Node groups + - Motion blur - Text (idea: index-based evaluation in plugboard)