diff --git a/Keishiki/CMakeLists.txt b/Keishiki/CMakeLists.txt index f1f85e5..51c1c5f 100644 --- a/Keishiki/CMakeLists.txt +++ b/Keishiki/CMakeLists.txt @@ -19,9 +19,9 @@ 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/glaze") add_subdirectory("ext/freetype") - -add_compile_definitions(WL_EGL_PLATFORM) +#add_compile_definitions(WL_EGL_PLATFORM) add_subdirectory("ext/bgfx") add_compile_definitions(IMGUI_DEFINE_MATH_OPERATORS) @@ -47,5 +47,5 @@ else () 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) + target_link_libraries (Keishiki PRIVATE freetype bgfx bx SDL3::SDL3 ${Wayland_LIBRARIES} fmt glaze_glaze) endif () diff --git a/Keishiki/Keishiki.cpp b/Keishiki/Keishiki.cpp index 4662a76..8c7c1da 100644 --- a/Keishiki/Keishiki.cpp +++ b/Keishiki/Keishiki.cpp @@ -19,12 +19,16 @@ namespace K { K::AppState app_state; bool Init() { +#if !WL_EGL_PLATFORM + SDL_SetHint(SDL_HINT_VIDEO_DRIVER, "x11"); +#endif if (SDL_Init(SDL_INIT_VIDEO)) { Log(K_L_Error, "{}", SDL_GetError()); return false; } - app_state.window = SDL_CreateWindow("Keishiki", app_state.window_width, app_state.window_height, SDL_WINDOW_RESIZABLE); + app_state.window = SDL_CreateWindow("Keishiki", app_state.window_width, app_state.window_height, SDL_WINDOW_HIGH_PIXEL_DENSITY); + SDL_SetWindowResizable(app_state.window, true); if (app_state.window == nullptr) { Log(K_L_Error, "{}", SDL_GetError()); @@ -69,16 +73,16 @@ namespace K { if (!K::Resource::Init()) return false; - K::UI::Init(app_state.window); + UI::Init(app_state.window); if (!K::Graphics::Init()) { Log(K_L_Error, "Graphics init failed"); return false; } - 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]); + app_state.project.compositions.emplace_back("default project", 0, 100, 30.0f, 1280, 550); + app_state.project.inspecting_composition = 0; + UI::SetupViewport(app_state.project.compositions[app_state.project.inspecting_composition]); return true; } @@ -98,8 +102,9 @@ namespace K { 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); + app_state.window_width = current_event.window.data1; + app_state.window_height = current_event.window.data2; + bgfx::reset(current_event.window.data1, current_event.window.data2, BGFX_RESET_VSYNC); UI::SetLogoView(); } } diff --git a/Keishiki/Plugboard.cpp b/Keishiki/Plugboard.cpp index ee352f0..4bbbee3 100644 --- a/Keishiki/Plugboard.cpp +++ b/Keishiki/Plugboard.cpp @@ -48,7 +48,7 @@ namespace K::Plugboard { } template - typename T_Map::type GetNodeInputArg(const CompState& s, const InSocket& socket) { + typename T_Map::type GetNodeInputArg(const CompositionState& s, const InSocket& socket) { bool good; typename T_Map::type v = std::get::type>(ConvertValue(std::visit([&s](auto&& arg) { using U = std::decay_t; @@ -113,83 +113,83 @@ namespace K::Plugboard { "Invalid" }; - T_Map::type FetchAdd(const CompState& s, const Add& n) { + T_Map::type FetchAdd(const CompositionState& s, const Add& n) { return GetNodeInputArg(s, n.in[0]) + GetNodeInputArg(s, n.in[1]); } - T_Map::type FetchNegate(const CompState& s, const Negate& n) { + T_Map::type FetchNegate(const CompositionState& s, const Negate& n) { return -GetNodeInputArg(s, n.in[0]); } - T_Map::type FetchSubtract(const CompState& s, const Subtract& n) { + T_Map::type FetchSubtract(const CompositionState& s, const Subtract& n) { return GetNodeInputArg(s, n.in[0]) - GetNodeInputArg(s, n.in[1]); } - T_Map::type FetchMultiply(const CompState& s, const Multiply& n) { + T_Map::type FetchMultiply(const CompositionState& s, const Multiply& n) { return GetNodeInputArg(s, n.in[0]) * GetNodeInputArg(s, n.in[1]); } - T_Map::type FetchDivide(const CompState& s, const Divide& n) { + T_Map::type FetchDivide(const CompositionState& s, const Divide& n) { return GetNodeInputArg(s, n.in[0]) / GetNodeInputArg(s, n.in[1]); } - T_Map::type FetchSign(const CompState& s, const Sign& n) { + T_Map::type FetchSign(const CompositionState& s, const Sign& n) { return std::signbit(GetNodeInputArg(s, n.in[0])) ? -1.0f : 1.0f; } - T_Map::type FetchSin(const CompState& s, const Sin& n) { + T_Map::type FetchSin(const CompositionState& s, const Sin& n) { return std::sin(GetNodeInputArg(s, n.in[0])); } - T_Map::type FetchCos(const CompState& s, const Cos& n) { + T_Map::type FetchCos(const CompositionState& s, const Cos& n) { return std::cos(GetNodeInputArg(s, n.in[0])); } - T_Map::type FetchTan(const CompState& s, const Tan& n) { + T_Map::type FetchTan(const CompositionState& s, const Tan& n) { return std::tan(GetNodeInputArg(s, n.in[0])); } - T_Map::type FetchArcsin(const CompState& s, const Arcsin& n) { + T_Map::type FetchArcsin(const CompositionState& s, const Arcsin& n) { return std::asin(GetNodeInputArg(s, n.in[0])); } - T_Map::type FetchArccos(const CompState& s, const Arccos& n) { + T_Map::type FetchArccos(const CompositionState& s, const Arccos& n) { return std::acos(GetNodeInputArg(s, n.in[0])); } - T_Map::type FetchArctan(const CompState& s, const Arctan& n) { + T_Map::type FetchArctan(const CompositionState& s, const Arctan& n) { return std::atan(GetNodeInputArg(s, n.in[0])); } - T_Map::type FetchAtan2(const CompState& s, const Atan2& n) { + T_Map::type FetchAtan2(const CompositionState& s, const Atan2& n) { return std::atan2(GetNodeInputArg(s, n.in[0]), GetNodeInputArg(s, n.in[1])); } - T_Map::type FetchMinimum(const CompState& s, const Minimum& n) { + T_Map::type FetchMinimum(const CompositionState& s, const Minimum& n) { return std::min(GetNodeInputArg(s, n.in[0]), GetNodeInputArg(s, n.in[1])); } - T_Map::type FetchMaximum(const CompState& s, const Maximum& n) { + T_Map::type FetchMaximum(const CompositionState& s, const Maximum& n) { return std::max(GetNodeInputArg(s, n.in[0]), GetNodeInputArg(s, n.in[1])); } - T_Map::type FetchPower(const CompState& s, const Power& n) { + T_Map::type FetchPower(const CompositionState& s, const Power& n) { return std::pow(GetNodeInputArg(s, n.in[0]), GetNodeInputArg(s, n.in[1])); } - T_Map::type FetchSquareRoot(const CompState& s, const SquareRoot& n) { + T_Map::type FetchSquareRoot(const CompositionState& s, const SquareRoot& n) { return std::sqrt(GetNodeInputArg(s, n.in[0])); } - T_Map::type FetchNaturalLogarithm(const CompState& s, const NaturalLogarithm& n) { + T_Map::type FetchNaturalLogarithm(const CompositionState& s, const NaturalLogarithm& n) { return std::log(GetNodeInputArg(s, n.in[0])); } - T_Map::type FetchAbsoluteValue(const CompState& s, const AbsoluteValue& n) { + T_Map::type FetchAbsoluteValue(const CompositionState& s, const AbsoluteValue& n) { return std::abs(GetNodeInputArg(s, n.in[0])); } - T_Map::type FetchInterpolation(const CompState& s, const Interpolation& n) { + T_Map::type FetchInterpolation(const CompositionState& s, const Interpolation& n) { return EvalInterpolation(GetNodeInputArg(s, n.in[0]), n.extra); } @@ -208,7 +208,7 @@ namespace K::Plugboard { } } - T_Map::type FetchChain(const CompState& s, const Chain& n) { + T_Map::type FetchChain(const CompositionState& s, const Chain& n) { i32 frame = static_cast(GetNodeInputArg(s, n.in[0])); auto& v = n.extra.chain.segments; @@ -250,7 +250,7 @@ namespace K::Plugboard { }, var); } - T_Map::type Eval(const CompState& s, const ConnectInfo& info) { + T_Map::type Eval(const CompositionState& s, const ConnectInfo& info) { return std::visit([&info, &s](auto&& arg) { return arg->fetch[info.index](s, *arg); }, info.p); } @@ -343,25 +343,32 @@ namespace K::Plugboard { }, v); } - - T_Map::type FetchCompositionInFrame(const CompState& s, const CompositionIn& n) { + T_Map::type FetchCompositionInFrame(const CompositionState& s, const GroupIn& n) { return T_Map::type{static_cast(s.current_frame)}; } - T_Map::type FetchCompositionInTime(const CompState& s, const CompositionIn& n) { - return T_Map::type{static_cast(s.current_frame) / s.fps}; - } - - T_Map::type FetchCompositionInAppTicks(const CompState& s, const CompositionIn& n) { + T_Map::type FetchCompositionInAppTicks(const CompositionState& s, const GroupIn& n) { return T_Map::type{ static_cast(SDL_GetTicks()) }; } - + Plugboard::Plugboard() { + AddOutSocket("Frame", T_Int, FetchCompositionInFrame); + AddOutSocket("App Ticks", T_Float, FetchCompositionInAppTicks); + AddInSocket("Frame", T_Int); + AddInSocket("Ticks", T_Int); + } + void Plugboard::AddInSocket(const String& s, Type t) { + in.emplace_back(s, t); + } + void Plugboard::AddOutSocket(const String& s, Type t, T_Map::type(*fetch)(const CompositionState&, const GroupIn&)) { + in_instance.out.emplace_back(s, t); + in_instance.fetch.push_back(fetch); + } void Plugboard::RecollectChains() { - for (u32 i = 0; i < out.in.size(); i++) { + for (u32 i = 0; i < out_instance.in.size(); i++) { std::visit([i, this](auto&& arg) { if constexpr (std::is_same_v, ConnectInfo>) - CollectChains(arg, out.show_nodes[i]); - }, out.in[i].value); + CollectChains(arg, show_nodes[i]); + }, out_instance.in[i].value); } } } diff --git a/Keishiki/Resource.cpp b/Keishiki/Resource.cpp index 0124ea5..97e25a5 100644 --- a/Keishiki/Resource.cpp +++ b/Keishiki/Resource.cpp @@ -1,12 +1,28 @@ #include -#include #include #include #include +#include +#include #define STB_IMAGE_IMPLEMENTATION #include +template <> +struct glz::meta { + using T = ImVec2; + static constexpr auto value = array(&T::x, &T::y); +}; + +namespace K::Resource { + void SaveCurrentProject() { + auto ec = glz::write_file_json("soon!", "./temp.json", String{}); + if (ec.ec != glz::error_code::none) { + Log(K_L_Error, "{}", ec.includer_error); + } + } +} + namespace { bgfx::ShaderHandle LoadShader(const std::filesystem::path& file_path) { FILE *file = fopen(file_path.c_str(), "rb"); @@ -227,4 +243,77 @@ namespace K::Resource { } }, r.second); } + + static int DecodeURI(char *buf, int len) { + /* Decodes URI escape sequences in string buf of len bytes + (excluding the terminating NULL byte) in-place. Since + URI-encoded characters take three times the space of + normal characters, this should not be an issue. + + Returns the number of decoded bytes that wound up in + the buffer, excluding the terminating NULL byte. + + The buffer is guaranteed to be NULL-terminated but + may contain embedded NULL bytes. + + On error, -1 is returned. + */ + int ri, wi, di; + char decode = '\0'; + if (!buf || len < 0) { + errno = EINVAL; + return -1; + } + if (len == 0) { + len = SDL_strlen(buf); + } + for (ri = 0, wi = 0, di = 0; ri < len && wi < len; ri += 1) { + if (di == 0) { + /* start decoding */ + if (buf[ri] == '%') { + decode = '\0'; + di += 1; + continue; + } + /* normal write */ + buf[wi] = buf[ri]; + wi += 1; + continue; + } else if (di == 1 || di == 2) { + char off = '\0'; + char isa = buf[ri] >= 'a' && buf[ri] <= 'f'; + char isA = buf[ri] >= 'A' && buf[ri] <= 'F'; + char isn = buf[ri] >= '0' && buf[ri] <= '9'; + if (!(isa || isA || isn)) { + /* not a hexadecimal */ + int sri; + for (sri = ri - di; sri <= ri; sri += 1) { + buf[wi] = buf[sri]; + wi += 1; + } + di = 0; + continue; + } + /* itsy bitsy magicsy */ + if (isn) { + off = 0 - '0'; + } else if (isa) { + off = 10 - 'a'; + } else if (isA) { + off = 10 - 'A'; + } + decode |= (buf[ri] + off) << (2 - di) * 4; + if (di == 2) { + buf[wi] = decode; + wi += 1; + di = 0; + } else { + di += 1; + } + continue; + } + } + buf[wi] = '\0'; + return wi; + } } \ No newline at end of file diff --git a/Keishiki/UI.cpp b/Keishiki/UI.cpp index 6da3216..b1bd2f0 100644 --- a/Keishiki/UI.cpp +++ b/Keishiki/UI.cpp @@ -18,6 +18,8 @@ #include #include +#include + namespace { const K::Resource::Resource *logo; @@ -42,7 +44,7 @@ namespace { K::VisualTrack bg{}; K::Byte *save_buffer; - K::CompState *comp_save_called = nullptr; + K::CompositionState *comp_save_called = nullptr; u32 ready_frame; } @@ -68,7 +70,7 @@ namespace K::UI { bgfx::setViewTransform(K::Graphics::K_VIEW_LOGO, view, proj); } - void AddTransformLayer(CompState& s, const String& name) { + void AddTransformLayer(CompositionState& s, const String& name) { auto l = Layer{ VisualTrack{}, name, @@ -76,40 +78,38 @@ namespace K::UI { 0UL, s.frame_max }; - l.track.AddUniform("u_aspect_ratio", ShaderGraph::ExpandVariant(ShaderGraph::T_XY)); - if (s.width > s.height) - l.track.uniforms[0].val = ShaderGraph::T_Map::type{static_cast(s.width)/static_cast(s.height), 1.0f}; - else - l.track.uniforms[0].val = ShaderGraph::T_Map::type{1.0f, static_cast(s.height)/static_cast(s.width)}; + l.track.AddUniform("u_resolution", ShaderGraph::ExpandVariant(ShaderGraph::T_XY), K_U_Exposed); + l.track.uniforms[0].val = ShaderGraph::T_Map::type{static_cast(s.width), static_cast(s.height)}; + K::VisualTrack::ExposeUniform(s, l.track.uniforms.back()); - l.track.AddUniform("u_opacity", ShaderGraph::ExpandVariant(ShaderGraph::T_Float)); + l.track.AddUniform("u_opacity", ShaderGraph::ExpandVariant(ShaderGraph::T_Float), K_U_Exposed); l.track.uniforms[1].val = ShaderGraph::T_Map::type(1.0f); + K::VisualTrack::ExposeUniform(s, l.track.uniforms.back()); - l.track.AddUniform("u_rot", ShaderGraph::ExpandVariant(ShaderGraph::T_Float)); + l.track.AddUniform("u_rot", ShaderGraph::ExpandVariant(ShaderGraph::T_Float), K_U_Exposed); l.track.uniforms[2].val = ShaderGraph::T_Map::type(.0f); + K::VisualTrack::ExposeUniform(s, l.track.uniforms.back()); - l.track.AddUniform("u_scale", ShaderGraph::ExpandVariant(ShaderGraph::T_XY)); + l.track.AddUniform("u_scale", ShaderGraph::ExpandVariant(ShaderGraph::T_XY), K_U_Exposed); l.track.uniforms[3].val = ShaderGraph::T_Map::type{1.0f, 1.0f}; + K::VisualTrack::ExposeUniform(s, l.track.uniforms.back()); - l.track.AddUniform("u_translate", ShaderGraph::ExpandVariant(ShaderGraph::T_XY)); + l.track.AddUniform("u_translate", ShaderGraph::ExpandVariant(ShaderGraph::T_XY), K_U_Exposed); l.track.uniforms[4].val = ShaderGraph::T_Map::type{.0f, .0f}; + K::VisualTrack::ExposeUniform(s, l.track.uniforms.back()); l.track.shader = "void main() {\n" "\tfloat angle = -u_rot * M_PI / 180.0f;\n" "\tvec2 uv = vec2(v_texcoord0.x, 1.0f - v_texcoord0.y) - .5f;\n" - "\tuv = uv * u_aspect_ratio;\n" + "\tuv = uv * u_resolution;\n" "\tuv = uv - u_translate;\n" "\tuv = vec2(cos(angle)*uv.x - sin(angle)*uv.y, sin(angle)*uv.x + cos(angle)*uv.y);\n" + "\tuv = uv / s_texColor_dims;\n" "\tuv = uv / u_scale;\n" "\tuv = uv + .5f;\n\n" "\tvec4 tx = texture2D(s_texColor, uv);\n" "\tgl_FragColor = vec4(tx.rgb, tx.a * u_opacity);\n" "}\n"; - l.track.ExposeUniform(s, 0); - l.track.ExposeUniform(s, 1); - l.track.ExposeUniform(s, 2); - l.track.ExposeUniform(s, 3); - l.track.ExposeUniform(s, 4); l.track.AddSampler("s_texColor"); @@ -119,6 +119,13 @@ namespace K::UI { void MainMenuBar() { if (ImGui::BeginMainMenuBar()) { + if (ImGui::BeginMenu("File")) { + if (ImGui::MenuItem("Save Project", "CTRL+S")) { + Resource::SaveCurrentProject(); + } + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("Edit")) { // if (ImGui::MenuItem("Undo", "CTRL+Z")) {} // if (ImGui::MenuItem("Redo", "CTRL+Y", false, false)) {} // Disabled item @@ -130,16 +137,18 @@ namespace K::UI { } if (ImGui::BeginMenu("View")) { + if (ImGui::MenuItem("Assets", "", &draw_assets)) {} if (ImGui::MenuItem("Viewport", "", &draw_viewport)) {} if (ImGui::MenuItem("Composition", "", &draw_comp)) {} - if (ImGui::MenuItem("Shader", "", &draw_layer)) {} + if (ImGui::MenuItem("Layer", "", &draw_layer)) {} + if (ImGui::MenuItem("Interpolation", "", &draw_interpolation)) {} ImGui::EndMenu(); } } ImGui::EndMainMenuBar(); } - void SetupViewport(CompState& s) { + void SetupViewport(CompositionState& s) { composite[0] = bgfx::createTexture2D(s.width, s.height, false, 1, bgfx::TextureFormat::RGBA8, BGFX_TEXTURE_RT); composite[1] = bgfx::createTexture2D(s.width, s.height, false, 1, bgfx::TextureFormat::RGBA8, BGFX_TEXTURE_RT); render = bgfx::createTexture2D(s.width, s.height, false, 1, bgfx::TextureFormat::RGBA8, BGFX_TEXTURE_RT); @@ -165,7 +174,7 @@ namespace K::UI { bx::mtxTranslate(view, 0.f, 0.f, 1.0f); } - void DestroyViewport(CompState& s) { + void DestroyViewport(CompositionState& s) { bgfx::destroy(composite_fb[0]); bgfx::destroy(composite_fb[1]); bgfx::destroy(composite[0]); @@ -197,8 +206,8 @@ namespace K::UI { } template - void PlugboardNodeDrawSockets(CompState& s, N& n, ImGuiStyle& style, - bool& dragging_on_socket, ImVec2& source, bool dummy = true) { + void PlugboardNodeDrawSockets(CompositionState& s, N& n, ImGuiStyle& style, + bool& dragging_on_socket, ImVec2& source, Dict& links_pos, bool dummy = true) { i32 row = 1; for (u32 out = 0; out < n.out.size(); out++, row++) { ImGui::PushID(row); @@ -233,7 +242,7 @@ namespace K::UI { // Update link info if needed if (!n.out[out].outgoing.empty()) {\ - s.plugboard.links_pos[{&n, out}].source = ImGui::GetCursorScreenPos() + ImVec2{ImGui::GetFrameHeight() / 2, + links_pos[{&n, out}].source = ImGui::GetCursorScreenPos() + ImVec2{ImGui::GetFrameHeight() / 2, -ImGui::GetFrameHeight() / 2 - style.ItemInnerSpacing.y}; } @@ -264,13 +273,13 @@ namespace K::UI { ImGui::TextUnformatted(n.in[in].name.c_str()); // Update link info - std::visit([&socket_link_pos, &dragging_on_socket, &source, &s](auto&& arg) { + std::visit([&links_pos, &socket_link_pos, &dragging_on_socket, &source, &s](auto&& arg) { using T = std::decay_t; if constexpr (std::is_same_v) { if (ImGui::IsItemActive()) - s.plugboard.links_pos[arg].sinks.push_back(ImGui::GetMousePos()); + links_pos[arg].sinks.push_back(ImGui::GetMousePos()); else - s.plugboard.links_pos[arg].sinks.push_back(socket_link_pos); + links_pos[arg].sinks.push_back(socket_link_pos); } else if (ImGui::IsItemActive()) { dragging_on_socket = true; @@ -288,8 +297,8 @@ namespace K::UI { } template - void PlugboardDrawNode(CompState& s, N& n, ImVec2& view_pos, ImGuiIO& io, ImGuiStyle& style, - bool& dragging_on_socket, ImVec2& source, bool& delete_request) { + void PlugboardDrawNode(CompositionState& s, N& n, ImVec2& view_pos, ImGuiIO& io, ImGuiStyle& style, + bool& dragging_on_socket, ImVec2& source, bool& delete_request, Dict& links_pos) { Plugboard::NodeInstanceP ptr{&n}; bool selected = std::ranges::find(s.plugboard.selected_nodes, ptr) != s.plugboard.selected_nodes.end(); const auto& pos = n.pos; @@ -335,7 +344,7 @@ namespace K::UI { ImGui::TableNextColumn(); } - PlugboardNodeDrawSockets(s, n, style, dragging_on_socket, source); + PlugboardNodeDrawSockets(s, n, style, dragging_on_socket, source, links_pos); ImGui::EndTable(); } @@ -351,7 +360,7 @@ namespace K::UI { last_frame_tick = app_state.current_time; } - void Viewport(CompState& s) { + void Viewport(CompositionState& s) { if (playing) { if (static_cast(app_state.current_time - last_frame_tick) >= 1000.0f / s.fps) { f32 delta = static_cast(app_state.current_time - last_frame_tick); @@ -361,7 +370,7 @@ namespace K::UI { } } - if (ImGui::Begin("Viewport", &draw_viewport)) { + if (ImGui::Begin("Viewport")) { if (ImGui::Shortcut(ImGuiKey_Space)) TogglePlay(); @@ -436,11 +445,11 @@ namespace K::UI { struct PlotInfo { f32 begin, end; i32 samples; - const CompState &s; + const CompositionState &s; Plugboard::ConnectInfo &connected_v; }; - void Composition(CompState& s) { + void Composition(CompositionState& s) { static bool layer_delete_requested = false; if (layer_delete_requested) { if (std::ranges::find(s.selected, s.active) != s.selected.end()) // unset active if active is selected @@ -486,7 +495,7 @@ namespace K::UI { node_delete_requested = false; } - if (!ImGui::Begin(("Composition: " + s.name).c_str(), &draw_comp, ImGuiWindowFlags_NoScrollbar)) { + if (!ImGui::Begin(("Composition: " + s.name).c_str(), nullptr, ImGuiWindowFlags_NoScrollbar)) { ImGui::End(); return; } @@ -501,6 +510,7 @@ namespace K::UI { } ImGui::SameLine(); ImGui::SetNextItemWidth(150.0f); + static Dict links_pos; if (ImGui::BeginCombo("##Node", s.plugboard.nodes.GetName(static_cast(node_current)), ImGuiComboFlags_None)) { for (u32 n = 0; n < Plugboard::K_P_Count; n++) { const bool is_selected = (node_current == n); @@ -571,7 +581,7 @@ namespace K::UI { // why are we re-getting positions on each frame? // this is just convenient because sometimes we would be dragging on a socket! - s.plugboard.links_pos.clear(); + links_pos.clear(); if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) { old_view_pos = view_pos; @@ -584,7 +594,7 @@ namespace K::UI { (([&](auto&& arg){ for (auto& node : arg) { PlugboardDrawNode>(s, node, view_pos, io, style, dragging_on_socket, - drag_source, node_delete_requested); + drag_source, node_delete_requested, links_pos); } }(args)), ...); }, s.plugboard.nodes.nodes); @@ -596,7 +606,7 @@ namespace K::UI { ImGuiChildFlags_AutoResizeY); ImGui::PopStyleColor(); if (ImGui::BeginTable("Composition In", 3)) { - PlugboardNodeDrawSockets(s, s.plugboard.in, style, dragging_on_socket, drag_source, false); + PlugboardNodeDrawSockets(s, s.plugboard.in_instance, style, dragging_on_socket, drag_source, links_pos, false); ImGui::EndTable(); } @@ -928,9 +938,13 @@ namespace K::UI { f32 init_y = ImGui::GetCursorScreenPos().y; const static f32 layer_bound_width = 5.0f; - i32 l_in = TimelineFrameInView(view_left, view_right, current_layer.in, s.frame_max), l_out = TimelineFrameInView(view_left, view_right, current_layer.out, s.frame_max); + const bool l_le_right_extr = static_cast(current_layer.in) / static_cast(s.frame_max + 1) <= view_right, + l_in = l_le_right_extr && static_cast(current_layer.in) / static_cast(s.frame_max + 1) >= view_left, + l_ge_left_extr = static_cast(current_layer.out) / static_cast(s.frame_max + 1) >= view_left, + l_out = l_ge_left_extr && static_cast(current_layer.out) / static_cast(s.frame_max + 1) <= view_right; f32 in_pos = TimelineFrameToScreenView(view_left, view_amt, view_width, current_layer.in, s.frame_max), out_pos = TimelineFrameToScreenView(view_left, view_amt, view_width, current_layer.out, s.frame_max); + ImGui::PushID(0); if (l_in) { ImGui::SetCursorPosX(ImGui::GetCursorPosX() + in_pos); static f32 l_in_old; @@ -949,21 +963,22 @@ namespace K::UI { ImGui::PopStyleColor(); ImGui::SameLine(0.0f, 0.0f); } - ImGui::PushID(0); - ImGui::Button(current_layer.name.c_str(), - {std::min(out_pos + fr_step, view_width) - static_cast(l_in) * in_pos - layer_bound_width * static_cast(l_in + l_out), view_height}); - static u32 in_old, out_old; - if (ImGui::IsItemClicked()) { - in_old = current_layer.in; - out_old = current_layer.out; + if (l_le_right_extr && l_ge_left_extr) { + ImGui::Button(current_layer.name.c_str(), + {std::min(out_pos + fr_step, view_width) - static_cast(l_in) * in_pos - + layer_bound_width * static_cast(l_in + l_out), view_height}); + static u32 in_old, out_old; + if (ImGui::IsItemClicked()) { + in_old = current_layer.in; + out_old = current_layer.out; + } + if (ImGui::IsItemActive()) { + i32 off = static_cast(std::trunc(ImGui::GetMouseDragDelta().x / fr_step)); + off = std::max(-static_cast(in_old), off); + current_layer.in = in_old + off; + current_layer.out = out_old + off; + } } - if (ImGui::IsItemActive()) { - i32 off = static_cast(std::trunc(ImGui::GetMouseDragDelta().x / fr_step)); - off = std::max(-static_cast(in_old), off); - current_layer.in = in_old + off; - current_layer.out = out_old + off; - } - ImGui::PopID(); if (l_out) { static f32 r_out_old; ImGui::SameLine(0.0f, 0.0f); @@ -980,6 +995,7 @@ namespace K::UI { } ImGui::PopStyleColor(); } + ImGui::PopID(); ImGui::SetCursorScreenPos(ImVec2{tl_init_pos.x, init_y} - style.CellPadding); ImGui::InvisibleButton("##TL_BG", ImVec2{ view_width + style.CellPadding.x * 2, row_height + style.CellPadding.y}); @@ -992,6 +1008,9 @@ namespace K::UI { if (layer_open) { i32 j = 0; for (auto &u: current_layer.track.uniforms) { + if (u.status != K_U_Exposed) + continue; + ImGui::PushID(j); ImGui::TableNextRow(ImGuiTableRowFlags_None, row_height); ImGui::TableSetColumnIndex(0); @@ -1017,9 +1036,9 @@ namespace K::UI { using T = std::decay_t; if constexpr (std::is_same_v) { if (ImGui::IsItemActive()) - s.plugboard.links_pos[arg].sinks.push_back(io.MousePos); + links_pos[arg].sinks.push_back(io.MousePos); else - s.plugboard.links_pos[arg].sinks.push_back(ImGui::GetCursorScreenPos() + + links_pos[arg].sinks.push_back(ImGui::GetCursorScreenPos() + ImVec2{ImGui::GetFrameHeight() / 2, -ImGui::GetFrameHeight() / 2 - style.ItemInnerSpacing.y}); @@ -1027,9 +1046,11 @@ namespace K::UI { else if (ImGui::IsItemActive()) { if (ImGui::IsKeyPressed(ImGuiKey_LeftShift)) { auto& c = s.plugboard.nodes.Store(Plugboard::Chain{}); + c.extra.chain.segments.emplace_back(s.current_frame, std::get::type>(arg)); + c.extra.chain.selected.push_back(0); Plugboard::Connect(u.connection.p, u.connection.index, &c, 0); - Plugboard::Connect(&c, 0, &s.plugboard.in, 0); - auto& nodes = s.plugboard.out.show_nodes[u.connection.index]; + Plugboard::Connect(&c, 0, &s.plugboard.in_instance, 0); + auto& nodes = s.plugboard.show_nodes[u.connection.index]; nodes.clear(); nodes.emplace_back(&c, 0); } @@ -1044,10 +1065,10 @@ namespace K::UI { ImGui::TableSetColumnIndex(1); - auto u_flags = ImGuiTreeNodeFlags_SpanFullWidth | ImGuiTreeNodeFlags_NoTreePushOnOpen | ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_FramePadding; + auto u_flags = ImGuiTreeNodeFlags_SpanFullWidth | ImGuiTreeNodeFlags_NoTreePushOnOpen | ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_FramePadding | ImGuiTreeNodeFlags_DefaultOpen; if (connected_v.index() != 1) u_flags |= ImGuiTreeNodeFlags_Leaf; - bool uniform_open = ImGui::TreeNodeEx(u.name.c_str(),u_flags); + bool uniform_open = ImGui::TreeNodeEx(u.name.c_str(), u_flags); ImGui::TableSetColumnIndex(2); ImGui::SetNextItemWidth(-FLT_MIN); @@ -1080,7 +1101,7 @@ namespace K::UI { ImPlot::PlotLineG("v", [](int idx, void* user_data) -> ImPlotPoint { PlotInfo i = *(PlotInfo*)user_data; f32 x = i.begin + (i.end - i.begin) * static_cast(idx) / static_cast(i.samples); - CompState ss = i.s; + CompositionState ss = i.s; ss.current_frame = static_cast(std::round(x)); bool good; Vector info; @@ -1099,7 +1120,7 @@ namespace K::UI { tl_bg_handler(); if (uniform_open && connected_v.index() == 1) { - for (const auto& info: s.plugboard.out.show_nodes[u.connection.index]) { + for (const auto& info: s.plugboard.show_nodes[u.connection.index]) { ImGui::TableNextRow(ImGuiTableRowFlags_None, row_height); ImGui::TableSetColumnIndex(0); ImGui::TableSetColumnIndex(1); @@ -1227,13 +1248,16 @@ namespace K::UI { } ImGui::TreePop(); } - else for (auto &u: current_layer.track.uniforms) - std::visit([table_left, &s](auto &&arg) { + else for (auto& u: current_layer.track.uniforms) { + if (u.status != K_U_Exposed) + continue; + std::visit([table_left](auto &&arg) { using T = std::decay_t; if constexpr (std::is_same_v) - s.plugboard.links_pos[arg].sinks.push_back( // make nodes connect to collapsed layer row + links_pos[arg].sinks.push_back( // make nodes connect to collapsed layer row {table_left, ImGui::GetCursorScreenPos().y - row_height / 2.0f}); }, std::visit([&u](auto&& arg) -> auto& { return arg->in[u.connection.index].value; }, u.connection.p)); + } ImGui::PopStyleVar(); // FramePadding @@ -1276,7 +1300,7 @@ namespace K::UI { ImGui::SetCursorScreenPos(nodes_begin); if (ImGui::BeginChild("Node Connection Overlay", {table_left - ImGui::GetWindowPos().x, 0.0f}, false, ImGuiWindowFlags_NoInputs)) { auto *window = ImGui::GetCurrentWindow(); - for (auto &[_, link]: s.plugboard.links_pos) + for (auto &[_, link]: links_pos) for (auto &sink: link.sinks) window->DrawList->AddBezierCubic(link.source, {link.source.x + 60.0f, link.source.y}, @@ -1446,7 +1470,7 @@ namespace K::UI { ImPlot::PlotLineG("v", [](int idx, void *user_data) -> ImPlotPoint { PlotInfo i = *(PlotInfo *) user_data; f32 x = i.begin + (i.end - i.begin) * static_cast(idx) / static_cast(i.samples); - CompState ss = i.s; + CompositionState ss = i.s; ss.current_frame = static_cast(x); bool good; Vector info; @@ -1536,7 +1560,7 @@ namespace K::UI { ImGui::End(); } - void Layer(CompState& s) { + void Layer(CompositionState& s) { struct TextFilters { static int VariableName(ImGuiInputTextCallbackData *data) { return !(data->EventChar < 256 && ((data->EventChar >= 'a' && data->EventChar <= 'z') || @@ -1544,7 +1568,7 @@ namespace K::UI { (data->EventChar >= '0' && data->EventChar <= '9') || data->EventChar == '_')); } }; - if (ImGui::Begin("Layer", &draw_layer)) { + if (ImGui::Begin("Layer")) { if (s.active == -1) ImGui::TextUnformatted("No active layer."); else { @@ -1554,8 +1578,7 @@ namespace K::UI { static i32 type_current = 0; static String name{}; if (ImGui::Button("Add Uniform") && !name.empty() && !isdigit(name[0]) && !(name[0] == 'f' && name.length() == 1)) { - s.layers[s.active].track.AddUniform(name, ExpandVariant( - static_cast(type_current))); + s.layers[s.active].track.AddUniform("u_" + name, ExpandVariant(static_cast(type_current)), K_U_Exposed); VisualTrack::ExposeUniform(s, s.layers[s.active].track.uniforms.back()); } ImGui::SameLine(); @@ -1575,8 +1598,10 @@ namespace K::UI { ImGui::TableHeadersRow(); i32 i = 0; for (auto it = s.layers[s.active].track.uniforms.begin(); it != s.layers[s.active].track.uniforms.end();) { - if (std::visit([](auto&& arg){ return arg == nullptr; }, it->connection.p)) + if (it->status != K_U_Exposed) { + it++; continue; + } ImGui::TableNextRow(); ImGui::PushID(i++); @@ -1611,14 +1636,15 @@ namespace K::UI { if (ImGui::BeginTabItem("Samplers")) { static String name{}; if (ImGui::Button("Add Sampler") && !name.empty() && !isdigit(name[0])) { - s.layers[s.active].track.AddSampler(name); + s.layers[s.active].track.AddSampler("s_" + name); } ImGui::SameLine(); ImGui::InputText("##SamplerName", &name, ImGuiInputTextFlags_CallbackCharFilter, TextFilters::VariableName); - if (ImGui::BeginTable("Samplers", 3, ImGuiTableFlags_Borders)) { + if (ImGui::BeginTable("Samplers", 4, ImGuiTableFlags_Borders)) { ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_NoSort | ImGuiTableColumnFlags_WidthStretch); - ImGui::TableSetupColumn("Resource", ImGuiTableColumnFlags_NoSort | ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("Dims via", ImGuiTableColumnFlags_NoSort | ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("Resource", ImGuiTableColumnFlags_NoSort); ImGui::TableSetupColumn("##del", ImGuiTableColumnFlags_NoSort | ImGuiTableColumnFlags_WidthFixed); ImGui::TableHeadersRow(); i32 i = 0; @@ -1628,7 +1654,19 @@ namespace K::UI { ImGui::TableNextColumn(); ImGui::TextUnformatted(it->name.c_str()); ImGui::TableNextColumn(); - ImGui::Text("%s", it->resource->filename.c_str()); + ImGui::Text("%s%s", it->name.c_str(), "_dims"); + ImGui::TableNextColumn(); + if (ImGui::BeginCombo("##source", it->resource->filename.c_str())) { + for (auto& [file, res_v] : Resource::resources) + if (auto *res = std::get_if>(&res_v)) { + const bool is_selected = it->resource == res; + if (ImGui::Selectable(file.c_str(), is_selected)) { + it->resource = res; + it->dims->val = ShaderGraph::XY{static_cast(res->w), static_cast(res->h)}; + } + } + ImGui::EndCombo(); + } ImGui::TableNextColumn(); if (ImGui::Button("X")) { bgfx::destroy(it->handle); @@ -1652,8 +1690,8 @@ namespace K::UI { ImGui::End(); } - void Interpolation(CompState& s) { - if (ImGui::Begin("Interpolation", &draw_interpolation, ImGuiWindowFlags_NoScrollbar)) { + void Interpolation(CompositionState& s) { + if (ImGui::Begin("Interpolation", nullptr, ImGuiWindowFlags_NoScrollbar)) { ImGuiIO& io = ImGui::GetIO(); static Plugboard::K_Interpolation type_current = Plugboard::K_I_CubicBezier; @@ -1663,8 +1701,10 @@ namespace K::UI { if (ImGui::Button("Apply")) for (auto& layer : s.layers) - for (auto& u : layer.track.uniforms) - for (auto& n : s.plugboard.out.show_nodes[u.connection.index]) { + for (auto& u : layer.track.uniforms) { + if (u.status != K_U_Exposed) + continue; + for (auto& n : s.plugboard.show_nodes[u.connection.index]) { auto& [chain, _] = std::get(n.p)->extra; for (auto it = chain.segments.begin(); it != chain.segments.end(); it++) { auto nit = std::next(it); @@ -1674,6 +1714,7 @@ namespace K::UI { } } } + } ImGui::SameLine(); @@ -1784,6 +1825,42 @@ namespace K::UI { void Assets() { if (ImGui::Begin("Assets", &draw_assets)) { + if (ImGui::Button("Load")) { + static const SDL_DialogFileFilter import_filters[] = { + { "PNG images", "png" }, + { "JPEG images", "jpg;jpeg" }, + { "All images", "png;jpg;jpeg" }, + { NULL, NULL } + }; + struct ImportCallback { + static void SDLCALL f(void *userdata, const char* const* filelist, i32 filter) { + if (!filelist) { + Log(K_L_Error, "{}", SDL_GetError()); + return; + } + else if (!*filelist) // The user did not select any file. Most likely, the dialog was canceled. + return; + + while (*filelist) { + Resource::Load(*filelist); + filelist++; + } + + if (filter == -1) { + Log(K_L_Info, "Platform does not support fetching file filters."); + } + else if (filter < SDL_arraysize(import_filters)) { + SDL_Log("The filter selected by the user is '%s' (%s).", + import_filters[filter].pattern, import_filters[filter].name); + } + else { + SDL_Log("The user did not select any filter."); + } + } + }; + + SDL_ShowOpenFileDialog(ImportCallback::f, nullptr, app_state.window, import_filters, 3, nullptr, true); + } if (ImGui::BeginTable("Assets", 3, ImGuiTableFlags_Borders)) { ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthStretch); ImGui::TableSetupColumn("Type", ImGuiTableColumnFlags_WidthFixed); @@ -1791,7 +1868,7 @@ namespace K::UI { ImGui::TableHeadersRow(); i32 i = 0; - for (auto& comp : app_state.compositions) { + for (auto& comp : app_state.project.compositions) { ImGui::TableNextRow(); ImGui::PushID(i++); ImGui::TableNextColumn(); @@ -1826,10 +1903,10 @@ namespace K::UI { } ImGui::TableNextColumn(); if constexpr (std::is_same_v>) { - ImGui::Text("%s", fmt::format("{:%Y-%m-%d %X}", std::chrono::file_clock::to_sys(arg.frag_last_updated)).c_str()); + ImGui::Text("%s", fmt::format("{:%Y-%m-%d %H:%M:%S}", std::chrono::floor(std::chrono::file_clock::to_sys(arg.frag_last_updated))).c_str()); } else { - ImGui::Text("%s", fmt::format("{:%Y-%m-%d %X}", std::chrono::file_clock::to_sys(arg.last_updated)).c_str()); + ImGui::Text("%s", fmt::format("{:%Y-%m-%d %H:%M:%S}", std::chrono::floor(std::chrono::file_clock::to_sys(arg.last_updated))).c_str()); } }, res); ImGui::PopID(); @@ -1854,7 +1931,7 @@ namespace K::UI { if (draw_assets) Assets(); - auto& comp = app_state.compositions[app_state.inspecting_composition]; + auto& comp = app_state.project.compositions[app_state.project.inspecting_composition]; if (draw_viewport) Viewport(comp); if (draw_layer) Layer(comp); if (draw_comp) Composition(comp); @@ -1918,7 +1995,7 @@ namespace K::UI { } bg.pg = Resource::Load("Checkerboard")->pg; - bg.AddUniform("f_hw", ShaderGraph::Type::T_XYZ); + bg.AddUniform("f_hw", ShaderGraph::Type::T_XYZ, K_U_Inactive); logo = Resource::Load("Keishiki.png"); } @@ -1927,7 +2004,7 @@ namespace K::UI { bgfx::destroy(bg.uniforms[0].handle); bg.uniforms.erase(bg.uniforms.begin()); - for (auto& comp : app_state.compositions) + for (auto& comp : app_state.project.compositions) DestroyViewport(comp); ImGui_Implbgfx_Shutdown(); diff --git a/Keishiki/VisualTrack.cpp b/Keishiki/VisualTrack.cpp index bc43800..15e250e 100644 --- a/Keishiki/VisualTrack.cpp +++ b/Keishiki/VisualTrack.cpp @@ -36,7 +36,7 @@ namespace K { },val); } - void VisualTrack::GetFrame(const CompState& s, u32 view_id, bgfx::FrameBufferHandle fb, u32 w, u32 h, f32 proj[16], + void VisualTrack::GetFrame(const CompositionState& s, u32 view_id, bgfx::FrameBufferHandle fb, u32 w, u32 h, f32 proj[16], f32 view[16], f32 transform[16]) { bgfx::setViewMode(view_id, bgfx::ViewMode::Sequential); bgfx::setViewClear(view_id, BGFX_CLEAR_COLOR); @@ -88,8 +88,8 @@ namespace K { std::shared_lock lk{Resource::resource_lock}; u32 sampler_stage = 0; - for (auto& [_name, handle, res] : samplers) { - bgfx::setTexture(sampler_stage++, handle, res->tex); + for (auto& [_name, handle, res, _u] : samplers) { + bgfx::setTexture(sampler_stage++, handle, res->tex, BGFX_SAMPLER_UVW_BORDER); } Graphics::DrawQuad(view_id, transform, BGFX_STATE_WRITE_RGB | BGFX_STATE_WRITE_A, pg); @@ -101,7 +101,7 @@ namespace K { "#include \n" "#include \n"; u32 sampler_loc = 0; - for (auto& [name, _tex, _res] : samplers) { + for (auto& [name, _tex, _res, _u] : samplers) { f << "SAMPLER2D(" << name << ", " << sampler_loc++ << ");\n"; } for (auto& u : uniforms) { @@ -150,18 +150,21 @@ namespace K { samplers.clear(); } - void VisualTrack::ExposeUniform(CompState& s, Uniform& uu) { - uu.connection = {&s.plugboard.out, static_cast(s.plugboard.out.in.size())}; + void VisualTrack::ExposeUniform(CompositionState& s, Uniform& uu) { + uu.connection = {&s.plugboard.out_instance, static_cast(s.plugboard.out_instance.in.size())}; // this is shaky and bug prone -- relies on the shader types being in line with plugboard types - s.plugboard.out.in.push_back(Plugboard::InSocket{ uu.name, Plugboard::Type(uu.val.index()), ShaderValToPlugboard(uu.val)}); - s.plugboard.out.show_nodes.emplace_back(); + s.plugboard.out_instance.in.push_back(Plugboard::InSocket{ uu.name, Plugboard::Type(uu.val.index()), ShaderValToPlugboard(uu.val)}); + s.plugboard.show_nodes.emplace_back(); } - void VisualTrack::ExposeUniform(CompState& s, u32 i) { + void VisualTrack::ExposeUniform(CompositionState& s, u32 i) { ExposeUniform(s, uniforms[i]); } - void VisualTrack::HideUniform(CompState& s, Uniform& uu) { + void VisualTrack::HideUniform(CompositionState& s, Uniform& uu) { + if (uu.status != K_U_Exposed) + return; + Plugboard::Disconnect(uu.connection.p, uu.connection.index); // decrement indices of uniforms referencing CompositionOut whose index is greater @@ -170,22 +173,22 @@ namespace K { if (u_other.connection.index > uu.connection.index) u_other.connection.index--; - if (std::holds_alternative(s.plugboard.out.in[uu.connection.index].value)) { - Plugboard::Disconnect(s.plugboard.out, uu.connection.index); + if (std::holds_alternative(s.plugboard.out_instance.in[uu.connection.index].value)) { + Plugboard::Disconnect(s.plugboard.out_instance, uu.connection.index); } - s.plugboard.out.in.erase(s.plugboard.out.in.begin() + uu.connection.index); + s.plugboard.out_instance.in.erase(s.plugboard.out_instance.in.begin() + uu.connection.index); uu.connection = {(Plugboard::Add*)(nullptr), 0}; } - void VisualTrack::AddUniform(const String& s, ShaderGraph::T_Map::type&& val) { + void VisualTrack::AddUniform(const String& s, ShaderGraph::T_Map::type&& val, UniformStatus init) { for (auto& u : uniforms) if (u.name == s) return; for (auto& ss : samplers) if (ss.name == s) return; - uniforms.emplace_back(Uniform{s, bgfx::createUniform(("__" + s).c_str(), bgfx::UniformType::Vec4), val, {{}, 0}}); + uniforms.emplace_back(Uniform{s, bgfx::createUniform(("__" + s).c_str(), bgfx::UniformType::Vec4), val, {{}, 0}, init}); } void VisualTrack::AddSampler(const String& s) { @@ -195,6 +198,7 @@ namespace K { for (auto& ss : samplers) if (ss.name == s) return; - samplers.emplace_back(Sampler{s, bgfx::createUniform(s.c_str(), bgfx::UniformType::Sampler), Resource::fallback_still}); + AddUniform(s + "_dims", ShaderGraph::XY{static_cast(Resource::fallback_still->w), static_cast(Resource::fallback_still->h)}, K_U_SamplerDims); + samplers.emplace_back(Sampler{s, bgfx::createUniform(s.c_str(), bgfx::UniformType::Sampler), Resource::fallback_still, &uniforms.back()}); } } diff --git a/Keishiki/include/Common.h b/Keishiki/include/Common.h index df91105..687e747 100644 --- a/Keishiki/include/Common.h +++ b/Keishiki/include/Common.h @@ -39,10 +39,11 @@ namespace K { 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"); + auto& t = level > K_L_Info ? stderr : stdout; + fmt::print(t, "[{}] Keishiki: ", LogLevelNames[level]); + fmt::print(t, fmt, std::forward(args)...); + fmt::print(t, "\n"); } - struct CompState; // fwd declared -- Keishiki.h <-> VisualTrack.h has dependency cycle + struct CompositionState; // fwd declared -- Keishiki.h <-> VisualTrack.h has dependency cycle } diff --git a/Keishiki/include/Keishiki.h b/Keishiki/include/Keishiki.h index d1ed116..704b4db 100644 --- a/Keishiki/include/Keishiki.h +++ b/Keishiki/include/Keishiki.h @@ -16,7 +16,7 @@ namespace K { u64 in, out; }; - struct CompState { + struct CompositionState { String name; // Time u64 current_frame; @@ -33,10 +33,15 @@ namespace K { Plugboard::Plugboard plugboard; }; + struct ProjectState { + i32 inspecting_composition = -1; + Vector compositions{}; + }; + extern struct AppState { SDL_Window *window = nullptr; - i32 window_width = 2300, - window_height = 1300; + i32 window_width = 2200, + window_height = 1200; std::atomic_bool running {true}; @@ -44,7 +49,6 @@ namespace K { u32 bgfx_frame{}; u32 n_views{}; - i32 inspecting_composition = -1; - Vector compositions{}; + ProjectState project{}; } app_state; // global ! } diff --git a/Keishiki/include/Plugboard.h b/Keishiki/include/Plugboard.h index 740dd99..bd86466 100644 --- a/Keishiki/include/Plugboard.h +++ b/Keishiki/include/Plugboard.h @@ -86,8 +86,8 @@ namespace K::Plugboard { struct AbsoluteValue; struct Interpolation; struct Chain; - struct CompositionIn; - struct CompositionOut; + struct GroupIn; + struct GroupOut; enum K_P_Nodes { K_P_Add = 0, @@ -144,8 +144,8 @@ namespace K::Plugboard { AbsoluteValue, Interpolation, Chain, - CompositionIn, - CompositionOut>; + GroupIn, + GroupOut>; using NodeInstanceP = NodeVar::NodeInstanceP; @@ -353,222 +353,220 @@ namespace K::Plugboard { T_Map::type ConvertValue(const T_Map::type& v, Type target, bool& good); - T_Map::type Eval(const CompState& s, const ConnectInfo& info); + T_Map::type Eval(const CompositionState& s, const ConnectInfo& info); struct LinksFromSource { ImVec2 source; Vector sinks; }; - T_Map::type FetchAdd(const CompState& s, const Add& n); + T_Map::type FetchAdd(const CompositionState& s, const Add& n); struct Add { const static char constexpr *name = static_cast("Add"); std::array in = { InSocket{"a", T_Float }, InSocket{"b", T_Float } }; std::array out = { OutSocket{ "a+b", T_Float } }; - std::array::type (*)(const CompState&, const Add&), 1> fetch = { FetchAdd }; + std::array::type (*)(const CompositionState&, const Add&), 1> fetch = {FetchAdd }; ImVec2 pos; }; - T_Map::type FetchNegate(const CompState& s, const Negate& n); + T_Map::type FetchNegate(const CompositionState& s, const Negate& n); struct Negate { 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 }; + std::array::type (*)(const CompositionState&, const Negate&), 1> fetch = {FetchNegate }; ImVec2 pos; }; - T_Map::type FetchSubtract(const CompState& s, const Subtract& n); + T_Map::type FetchSubtract(const CompositionState& s, const Subtract& n); struct Subtract { const static char constexpr *name = static_cast("Subtract"); std::array in = { InSocket{"a", T_Float }, InSocket{"b", T_Float } }; std::array out = { OutSocket{ "a-b", T_Float } }; - std::array::type (*)(const CompState&, const Subtract&), 1>fetch = { FetchSubtract }; + std::array::type (*)(const CompositionState&, const Subtract&), 1>fetch = {FetchSubtract }; ImVec2 pos; }; - T_Map::type FetchMultiply(const CompState& s, const Multiply& n); + T_Map::type FetchMultiply(const CompositionState& s, const Multiply& n); struct Multiply { const static char constexpr *name = static_cast("Multiply"); std::array in = { InSocket{"a", T_Float }, InSocket{"b", T_Float } }; std::array out = { OutSocket{ "a*b", T_Float } }; - std::array::type (*)(const CompState&, const Multiply&), 1> fetch = { FetchMultiply }; + std::array::type (*)(const CompositionState&, const Multiply&), 1> fetch = {FetchMultiply }; ImVec2 pos; }; - T_Map::type FetchDivide(const CompState& s, const Divide& n); + T_Map::type FetchDivide(const CompositionState& s, const Divide& n); struct Divide { const static char constexpr *name = static_cast("Divide"); std::array in = { InSocket{"a", T_Float }, InSocket{"b", T_Float } }; std::array out = { OutSocket{ "a/b", T_Float } }; - std::array::type (*)(const CompState&, const Divide&), 1> fetch = { FetchDivide }; + std::array::type (*)(const CompositionState&, const Divide&), 1> fetch = {FetchDivide }; ImVec2 pos; }; - T_Map::type FetchSign(const CompState& s, const Sign& n); + T_Map::type FetchSign(const CompositionState& s, const Sign& n); struct Sign { const static char constexpr *name = static_cast("Sign"); std::array in = { InSocket{"a", T_Float } }; std::array out = { OutSocket{ "sgn(a)", T_Float } }; - std::array::type (*)(const CompState&, const Sign&), 1> fetch = { FetchSign }; + std::array::type (*)(const CompositionState&, const Sign&), 1> fetch = {FetchSign }; ImVec2 pos; }; - T_Map::type FetchSin(const CompState& s, const Sin& n); + T_Map::type FetchSin(const CompositionState& s, const Sin& n); struct Sin { const static char constexpr *name = static_cast("Sin"); std::array in = { InSocket{"a (rad)", T_Float } }; std::array out = { OutSocket{ "sin(a)", T_Float } }; - std::array::type (*)(const CompState&, const Sin&), 1> fetch = { FetchSin }; + std::array::type (*)(const CompositionState&, const Sin&), 1> fetch = {FetchSin }; ImVec2 pos; }; - T_Map::type FetchCos(const CompState& s, const Cos& n); + T_Map::type FetchCos(const CompositionState& s, const Cos& n); struct Cos { const static char constexpr *name = static_cast("Cos"); std::array in = { InSocket{"a (rad)", T_Float } }; std::array out = { OutSocket{ "cos(a)", T_Float } }; - std::array::type (*)(const CompState&, const Cos&), 1> fetch = { FetchCos }; + std::array::type (*)(const CompositionState&, const Cos&), 1> fetch = {FetchCos }; ImVec2 pos; }; - T_Map::type FetchTan(const CompState& s, const Tan& n); + T_Map::type FetchTan(const CompositionState& s, const Tan& n); struct Tan { const static char constexpr *name = static_cast("Tan"); std::array in = { InSocket{"a (rad)", T_Float } }; std::array out = { OutSocket{ "tan(a)", T_Float } }; - std::array::type (*)(const CompState&, const Tan&), 1> fetch = { FetchTan }; + std::array::type (*)(const CompositionState&, const Tan&), 1> fetch = {FetchTan }; ImVec2 pos; }; - T_Map::type FetchArcsin(const CompState& s, const Arcsin& n); + T_Map::type FetchArcsin(const CompositionState& s, const Arcsin& n); struct Arcsin { const static char constexpr *name = static_cast("Arcsin"); std::array in = { InSocket{"a", T_Float } }; std::array out = { OutSocket{ "arcsin(a) (rad)", T_Float } }; - std::array::type (*)(const CompState&, const Arcsin&), 1> fetch = { FetchArcsin }; + std::array::type (*)(const CompositionState&, const Arcsin&), 1> fetch = {FetchArcsin }; ImVec2 pos; }; - T_Map::type FetchArccos(const CompState& s, const Arccos& n); + T_Map::type FetchArccos(const CompositionState& s, const Arccos& n); struct Arccos { const static char constexpr *name = static_cast("Arccos"); std::array in = { InSocket{"a", T_Float } }; std::array out = { OutSocket{ "arccos(a) (rad)", T_Float } }; - std::array::type (*)(const CompState&, const Arccos&), 1> fetch = { FetchArccos }; + std::array::type (*)(const CompositionState&, const Arccos&), 1> fetch = {FetchArccos }; ImVec2 pos; }; - T_Map::type FetchArctan(const CompState& s, const Arctan& n); + T_Map::type FetchArctan(const CompositionState& s, const Arctan& n); struct Arctan { const static char constexpr *name = static_cast("Arctan"); std::array in = { InSocket{"a", T_Float } }; std::array out = { OutSocket{ "arctan(a) (rad)", T_Float } }; - std::array::type (*)(const CompState&, const Arctan&), 1> fetch = { FetchArctan }; + std::array::type (*)(const CompositionState&, const Arctan&), 1> fetch = {FetchArctan }; ImVec2 pos; }; - T_Map::type FetchAtan2(const CompState& s, const Atan2& n); + T_Map::type FetchAtan2(const CompositionState& s, const Atan2& n); struct Atan2 { const static char constexpr *name = static_cast("Atan2"); std::array in = { InSocket{"y", T_Float }, InSocket{"x", T_Float } }; std::array out = { OutSocket{ "atan2(y, x) (rad)", T_Float } }; - std::array::type (*)(const CompState&, const Atan2&), 1> fetch = { FetchAtan2 }; + std::array::type (*)(const CompositionState&, const Atan2&), 1> fetch = {FetchAtan2 }; ImVec2 pos; }; - T_Map::type FetchMinimum(const CompState& s, const Minimum& n); + T_Map::type FetchMinimum(const CompositionState& s, const Minimum& n); struct Minimum { const static char constexpr *name = static_cast("Minimum"); std::array in = { InSocket{"a", T_Float }, InSocket{"b", T_Float } }; std::array out = { OutSocket{ "min(a, b)", T_Float } }; - std::array::type (*)(const CompState&, const Minimum&), 1> fetch = { FetchMinimum }; + std::array::type (*)(const CompositionState&, const Minimum&), 1> fetch = {FetchMinimum }; ImVec2 pos; }; - T_Map::type FetchMaximum(const CompState& s, const Maximum& n); + T_Map::type FetchMaximum(const CompositionState& s, const Maximum& n); struct Maximum { const static char constexpr *name = static_cast("Maximum"); std::array in = { InSocket{"a", T_Float }, InSocket{"b", T_Float } }; std::array out = { OutSocket{ "max(a, b)", T_Float } }; - std::array::type (*)(const CompState&, const Maximum&), 1> fetch = { FetchMaximum }; + std::array::type (*)(const CompositionState&, const Maximum&), 1> fetch = {FetchMaximum }; ImVec2 pos; }; - T_Map::type FetchPower(const CompState& s, const Power& n); + T_Map::type FetchPower(const CompositionState& s, const Power& n); struct Power { const static char constexpr *name = static_cast("Power"); std::array in = { InSocket{"a", T_Float }, InSocket{"b", T_Float } }; std::array out = { OutSocket{ "a^b", T_Float } }; - std::array::type (*)(const CompState&, const Power&), 1> fetch = { FetchPower }; + std::array::type (*)(const CompositionState&, const Power&), 1> fetch = {FetchPower }; ImVec2 pos; }; - T_Map::type FetchSquareRoot(const CompState& s, const SquareRoot& n); + T_Map::type FetchSquareRoot(const CompositionState& s, const SquareRoot& n); struct SquareRoot { const static char constexpr *name = static_cast("Square Root"); std::array in = { InSocket{"a", T_Float } }; std::array out = { OutSocket{ "sqrt(a)", T_Float } }; - std::array::type (*)(const CompState&, const SquareRoot&), 1> fetch = { FetchSquareRoot }; + std::array::type (*)(const CompositionState&, const SquareRoot&), 1> fetch = {FetchSquareRoot }; ImVec2 pos; }; - T_Map::type FetchNaturalLogarithm(const CompState& s, const NaturalLogarithm& n); + T_Map::type FetchNaturalLogarithm(const CompositionState& s, const NaturalLogarithm& n); struct NaturalLogarithm { const static char constexpr *name = static_cast("Natural Logarithm"); std::array in = { InSocket{"a", T_Float } }; std::array out = { OutSocket{ "log(a)", T_Float } }; - std::array::type (*)(const CompState&, const NaturalLogarithm&), 1> fetch = { FetchNaturalLogarithm }; + std::array::type (*)(const CompositionState&, const NaturalLogarithm&), 1> fetch = {FetchNaturalLogarithm }; ImVec2 pos; }; - T_Map::type FetchAbsoluteValue(const CompState& s, const AbsoluteValue& n); + T_Map::type FetchAbsoluteValue(const CompositionState& s, const AbsoluteValue& n); struct AbsoluteValue { const static char constexpr *name = static_cast("Absolute Value"); std::array in = { InSocket{"a", T_Float } }; std::array out = { OutSocket{ "|a|", T_Float } }; - std::array::type (*)(const CompState&, const AbsoluteValue&), 1> fetch = { FetchAbsoluteValue }; + std::array::type (*)(const CompositionState&, const AbsoluteValue&), 1> fetch = {FetchAbsoluteValue }; ImVec2 pos; }; - T_Map::type FetchInterpolation(const CompState& s, const Interpolation& n); + T_Map::type FetchInterpolation(const CompositionState& s, const Interpolation& n); struct Interpolation { const static char constexpr *name = static_cast("Interpolation"); std::array in = { InSocket{"t", T_Float } }; std::array out = { OutSocket{ "Value", T_Float } }; - std::array::type (*)(const CompState&, const Interpolation&), 1> fetch = { FetchInterpolation }; + std::array::type (*)(const CompositionState&, const Interpolation&), 1> fetch = {FetchInterpolation }; ImVec2 pos; InterpolationExtra extra{}; void Draw(); }; - T_Map::type FetchChain(const CompState& s, const Chain& n); + T_Map::type FetchChain(const CompositionState& s, const Chain& n); struct Chain { const static char constexpr *name = static_cast("Chain"); std::array in = { InSocket{"In", T_Int } }; std::array out = { OutSocket{ "Out", T_Float } }; - std::array::type (*)(const CompState&, const Chain&), 1> fetch = { FetchChain }; + std::array::type (*)(const CompositionState&, const Chain&), 1> fetch = {FetchChain }; ImVec2 pos; ChainExtra extra{}; }; - T_Map::type FetchCompositionInFrame(const CompState& s, const CompositionIn& n); - T_Map::type FetchCompositionInTime(const CompState& s, const CompositionIn& n); - T_Map::type FetchCompositionInAppTicks(const CompState& s, const CompositionIn& n); - struct CompositionIn { - const static char constexpr *name = static_cast("Composition In"); - std::array in = {}; - std::array out = { OutSocket{"Frame", T_Int}, OutSocket{"Time (s)", T_Float}, OutSocket{"App Ticks", T_Float} }; - std::array::type (*)(const CompState&, const CompositionIn&), 3> fetch = { FetchCompositionInFrame, FetchCompositionInTime, FetchCompositionInAppTicks }; + T_Map::type FetchCompositionInFrame(const CompositionState& s, const GroupIn& n); + T_Map::type FetchCompositionInAppTicks(const CompositionState& s, const GroupIn& n); + struct GroupIn { + const static char constexpr *name = static_cast("Group In"); + std::array in{}; + Vector out{}; + Vector::type (*)(const CompositionState&, const GroupIn&)> fetch; ImVec2 pos; }; - struct CompositionOut { - const static char constexpr *name = static_cast("Composition Out"); - Vector in = {}; - Vector> show_nodes; - std::array out = {}; - std::array::type (*)(const CompState&, const CompositionOut&), 0> fetch = {}; + struct GroupOut { + const static char constexpr *name = static_cast("Group Out"); + Vector in{}; + std::array out{}; + std::array::type (*)(const CompositionState&, const GroupOut&), 0> fetch = {}; ImVec2 pos; }; @@ -648,18 +646,29 @@ namespace K::Plugboard { AbsoluteValue, Interpolation, Chain, - CompositionIn, - CompositionOut + GroupIn, + GroupOut >; - struct Plugboard { + struct Group { + const static char constexpr *name = static_cast("Group"); + Vector in; + Vector out; + Vector::type (*)(const CompositionState&, const GroupIn&)> fetch; + ImVec2 pos; + + GroupIn in_instance; + GroupOut out_instance; + NodeList nodes; // OK complexity wise since we would usually traverse the entire thing anyway, locality will likely be bad - Dict links_pos; // this is hilariously bad - CompositionIn in; - CompositionOut out; - Vector selected_nodes{}; + }; + struct Plugboard : Group { + Vector> show_nodes; + Plugboard(); + void AddInSocket(const String& s, Type t); + void AddOutSocket(const String& s, Type t, T_Map::type(*fetch)(const CompositionState&, const GroupIn&)); void RecollectChains(); }; diff --git a/Keishiki/include/Resource.h b/Keishiki/include/Resource.h index 1e7b55a..3f590d8 100644 --- a/Keishiki/include/Resource.h +++ b/Keishiki/include/Resource.h @@ -83,5 +83,7 @@ namespace K::Resource { Resource *Load(const std::filesystem::path& p); bgfx::ProgramHandle FetchUnmanagedShaderProgram(const String& shader_name); + + void SaveCurrentProject(); } diff --git a/Keishiki/include/UI.h b/Keishiki/include/UI.h index b88345c..cda8133 100644 --- a/Keishiki/include/UI.h +++ b/Keishiki/include/UI.h @@ -6,7 +6,7 @@ namespace K::UI { void Init(SDL_Window *window); void Draw(); void Shutdown(); - void SetupViewport(CompState& s); - void DestroyViewport(CompState& s); + void SetupViewport(CompositionState& s); + void DestroyViewport(CompositionState& s); void SetLogoView(); } diff --git a/Keishiki/include/VisualTrack.h b/Keishiki/include/VisualTrack.h index 5ddcbdc..30e0b0d 100644 --- a/Keishiki/include/VisualTrack.h +++ b/Keishiki/include/VisualTrack.h @@ -8,17 +8,26 @@ #include "Plugboard.h" namespace K { + enum UniformStatus { + K_U_Inactive = 0, + K_U_Exposed, + K_U_SamplerDims, + K_U_Count + }; + struct Uniform { String name; bgfx::UniformHandle handle; ShaderGraph::T_Map::type val; Plugboard::ConnectInfo connection; + UniformStatus status = K_U_Inactive; }; struct Sampler { String name; bgfx::UniformHandle handle; Resource::Resource *resource; + Uniform *dims; }; ShaderGraph::T_Map::type PlugboardValToShader(const Plugboard::T_Map::type& val); @@ -31,13 +40,13 @@ namespace K { String shader; Vector uniforms; Vector samplers; - void GetFrame(const CompState& s, u32 view_id, bgfx::FrameBufferHandle fb, u32 w, u32 h, f32 proj[16], + void GetFrame(const CompositionState& s, u32 view_id, bgfx::FrameBufferHandle fb, u32 w, u32 h, f32 proj[16], f32 view[16], f32 transform[16]); - static void ExposeUniform(CompState& s, Uniform& uu); - void ExposeUniform(CompState& s, u32 i); - static void HideUniform(CompState& s, Uniform& uu); + static void ExposeUniform(CompositionState& s, Uniform& uu); + void ExposeUniform(CompositionState& s, u32 i); + static void HideUniform(CompositionState& s, Uniform& uu); - void AddUniform(const String& s, ShaderGraph::T_Map::type&& val); + void AddUniform(const String& s, ShaderGraph::T_Map::type&& val, UniformStatus init); void AddSampler(const String& s); void Compile(); diff --git a/README.md b/README.md index 00e676d..43cbb96 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ - [Dear ImGui](https://github.com/ocornut/imgui) - [fmt](https://github.com/fmtlib/fmt) - [FreeType](https://freetype.org/) +- [Glaze](https://github.com/stephenberry/glaze) - [imgui_impl_bgfx](https://gist.github.com/pr0g/aff79b71bf9804ddb03f39ca7c0c3bbb) - [ImPlot](https://github.com/epezent/implot) - [SDL2](https://www.libsdl.org/) diff --git a/TODO.md b/TODO.md index f679b00..36f749c 100644 --- a/TODO.md +++ b/TODO.md @@ -23,8 +23,6 @@ # Later ## IO -- File dialogues pending SDL3 - - https://wiki.libsdl.org/SDL3/CategoryDialog - Clipboard pending SDL3 - OIIO output for more than PNG's - don't care about video export -- leave it for ffmpeg @@ -45,4 +43,3 @@ - Wait for SDL3! - SDL_mixer will be able to do all of wav ogg flac mp3 opus, we live in good times - output needs to be handled differently (if we care at all -- likely not) -z \ No newline at end of file