diff --git a/Keishiki/CMakeLists.txt b/Keishiki/CMakeLists.txt index 51c1c5f..7171226 100644 --- a/Keishiki/CMakeLists.txt +++ b/Keishiki/CMakeLists.txt @@ -27,12 +27,12 @@ add_subdirectory("ext/bgfx") add_compile_definitions(IMGUI_DEFINE_MATH_OPERATORS) # Disable RTTI and exceptions -if(MSVC) +if (MSVC) string(REGEX REPLACE "/GR" "/GR-" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") string(REGEX REPLACE "/EHsc" "/EHs-c-" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") -else() +else () set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-rtti -fno-exceptions") -endif() +endif () diff --git a/Keishiki/Entry.cpp b/Keishiki/Entry.cpp new file mode 100644 index 0000000..547e9b2 --- /dev/null +++ b/Keishiki/Entry.cpp @@ -0,0 +1,136 @@ +#include "Keishiki.h" +#include "Graphics.h" +#include "UI.h" + +#if BX_PLATFORM_LINUX && WL_EGL_PLATFORM +#include +#endif + +#include +#include + +#include +#include + +#include "backends/imgui_impl_sdl3.h" +#include + +namespace K { + + 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_HIGH_PIXEL_DENSITY); + SDL_SetWindowResizable(app_state.window, true); + + if (app_state.window == nullptr) { + Log(K_L_Error, "{}", SDL_GetError()); + return false; + } + + bgfx::Init bgfxInit; + bgfxInit.type = bgfx::RendererType::Count; // Automatically choose a renderer. + bgfxInit.resolution.width = app_state.window_width; + bgfxInit.resolution.height = app_state.window_height; + bgfxInit.resolution.reset = BGFX_RESET_VSYNC; + +#if BX_PLATFORM_WINDOWS + bgfxInit.platformData.nwh = SDL_GetProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_WIN32_HWND_POINTER, NULL); +#elif BX_PLATFORM_OSX + bgfxInit.platformData.nwh = SDL_GetProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_COCOA_WINDOW_POINTER, NULL); + bgfx::renderFrame(); +#elif BX_PLATFORM_LINUX + 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)) { + Log(K_L_Error, "bgfx initialization failed"); + return false; + } + + if (!K::Resource::Init()) + return false; + UI::Init(app_state.window); + + if (!K::Graphics::Init()) { + Log(K_L_Error, "Graphics init failed"); + return false; + } + + return true; + } + + void Run() { + app_state.init_time = SDL_GetTicks(); + app_state.last_time = app_state.init_time; + + while (app_state.running.load()) { + 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_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) { + 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(); + } + } + + UI::Draw(); + + app_state.last_time = app_state.current_time; + } + } + + void Shutdown() { + K::Graphics::Shutdown(); + K::UI::Shutdown(); + K::Resource::Shutdown(); + bgfx::shutdown(); + +#if !(BX_PLATFORM_LINUX && WL_EGL_PLATFORM) + SDL_DestroyWindow(app_state.window); +#endif + + SDL_Quit(); + } + +} + +int main(int argc, char *argv[]) { + if (!K::Init()) + return EXIT_FAILURE; + + K::Run(); + + K::Shutdown(); + return EXIT_SUCCESS; +} diff --git a/Keishiki/Keishiki.cpp b/Keishiki/Keishiki.cpp index 8c7c1da..179ce61 100644 --- a/Keishiki/Keishiki.cpp +++ b/Keishiki/Keishiki.cpp @@ -1,140 +1,67 @@ #include "Keishiki.h" -#include "Graphics.h" -#include "UI.h" - -#if BX_PLATFORM_LINUX && WL_EGL_PLATFORM -#include -#endif - -#include -#include - -#include -#include - -#include "backends/imgui_impl_sdl3.h" -#include 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; - } + void CompositionState::Setup() { + composite[0] = bgfx::createTexture2D(width, height, false, 1, bgfx::TextureFormat::RGBA8, BGFX_TEXTURE_RT); + composite[1] = bgfx::createTexture2D(width, height, false, 1, bgfx::TextureFormat::RGBA8, BGFX_TEXTURE_RT); + render = bgfx::createTexture2D(width, height, false, 1, bgfx::TextureFormat::RGBA8, BGFX_TEXTURE_RT); + bgfx::Attachment comp{}, comp2{}, ren{}; + comp.init(composite[0], bgfx::Access::Write); // DX 11 requires Write only + comp2.init(composite[1], bgfx::Access::Write); // DX 11 requires Write only + ren.init(render, bgfx::Access::ReadWrite); + composite_fb[0] = bgfx::createFrameBuffer(1, &comp); + composite_fb[1] = bgfx::createFrameBuffer(1, &comp2); + render_fb = bgfx::createFrameBuffer(1, &ren); - 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); + bx::mtxOrtho(proj, 0.0f, static_cast(width), 0.0f, static_cast(height), + 0.1f, 100.0f, 0.f, bgfx::getCaps()->homogeneousDepth); + f32 mtx1[16], mtx2[16]; + bx::mtxTranslate(mtx1, 0, 0, 0.0f); + bx::mtxScale(mtx2, static_cast(width), static_cast(height), 1.0f); + bx::mtxMul(transform, mtx2, mtx1); + bx::mtxTranslate(view, 0.f, 0.f, 1.0f); + } - if (app_state.window == nullptr) { - Log(K_L_Error, "{}", SDL_GetError()); - return false; - } - - bgfx::Init bgfxInit; - bgfxInit.type = bgfx::RendererType::Count; // Automatically choose a renderer. - bgfxInit.resolution.width = app_state.window_width; - bgfxInit.resolution.height = app_state.window_height; - bgfxInit.resolution.reset = BGFX_RESET_VSYNC; - -#if BX_PLATFORM_WINDOWS - bgfxInit.platformData.nwh = SDL_GetProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_WIN32_HWND_POINTER, NULL); -#elif BX_PLATFORM_OSX - bgfxInit.platformData.nwh = SDL_GetProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_COCOA_WINDOW_POINTER, NULL); - bgfx::renderFrame(); -#elif BX_PLATFORM_LINUX - 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); + i32 CompositionState::GetFrame(u32 frame) { + if (frame > frame_max) { + Log(K_L_Error, "Comp {} frame out of range! Requesting {}, max {}", name, frame, frame_max); + return -1; } -#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); - } + + u64 tmp = current_frame; + current_frame = frame; + + // Get clean alpha backdrop + bgfx::setViewFrameBuffer(app_state.render_view, composite_fb[0]); // the other fb will be cleared in composite + bgfx::setViewClear(app_state.render_view, BGFX_CLEAR_COLOR, 0x00000000); + bgfx::setViewRect(app_state.render_view, 0, 0, width, height); + bgfx::touch(app_state.render_view); + app_state.render_view++; + + // Render & Composite + i32 layers_done = 0; + for (u32 i = layers.size() - 1; i != u32(-1); i--) { + if (std::ranges::find(disabled, i) != disabled.end()) continue; + if (current_frame > layers[i].out || current_frame < layers[i].in) continue; + + layers[i].track.GetFrame(*this, app_state.render_view++, render_fb, width, + height, proj, view, transform); + Graphics::Composite(app_state.render_view++, composite_fb[(layers_done + 1) % 2], composite[layers_done % 2], render, layers[i].mode, width, height, proj, transform); + layers_done++; } -#endif -#elif BX_PLATFORM_EMSCRIPTEN - bgfxInit.platformData.nwh = (void*)"#canvas"; -#endif - if (!bgfx::init(bgfxInit)) { - Log(K_L_Error, "bgfx initialization failed"); - return false; - } + current_frame = tmp; + return layers_done % 2; // result available in composite[layers_done % 2] + } - if (!K::Resource::Init()) - return false; - UI::Init(app_state.window); - - if (!K::Graphics::Init()) { - Log(K_L_Error, "Graphics init failed"); - return false; - } - - 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; - } - - void Run() { - app_state.init_time = SDL_GetTicks(); - app_state.last_time = app_state.init_time; - - while (app_state.running.load()) { - 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_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) { - 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(); - } - } - - UI::Draw(); - - app_state.last_time = app_state.current_time; - } - } - - void Shutdown() { - K::Graphics::Shutdown(); - K::UI::Shutdown(); - K::Resource::Shutdown(); - bgfx::shutdown(); - -#if !(BX_PLATFORM_LINUX && WL_EGL_PLATFORM) - SDL_DestroyWindow(app_state.window); -#endif - - SDL_Quit(); - } -} - -int main(int argc, char *argv[]) { - if (!K::Init()) - return EXIT_FAILURE; - - K::Run(); - - K::Shutdown(); - return EXIT_SUCCESS; + void CompositionState::Destroy() { + bgfx::destroy(composite_fb[0]); + bgfx::destroy(composite_fb[1]); + bgfx::destroy(composite[0]); + bgfx::destroy(composite[1]); + bgfx::destroy(render_fb); + bgfx::destroy(render); + } } diff --git a/Keishiki/Resource.cpp b/Keishiki/Resource.cpp index 97e25a5..ea91e5e 100644 --- a/Keishiki/Resource.cpp +++ b/Keishiki/Resource.cpp @@ -243,77 +243,4 @@ 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 32b33ee..7d44970 100644 --- a/Keishiki/UI.cpp +++ b/Keishiki/UI.cpp @@ -33,16 +33,11 @@ namespace { const f32 row_height = 20.0f; // Viewport + K::VisualTrack bg{}; bool playing; u64 last_frame_tick; - bgfx::TextureHandle composite[2] = { BGFX_INVALID_HANDLE, BGFX_INVALID_HANDLE }, - render = BGFX_INVALID_HANDLE, - save = BGFX_INVALID_HANDLE; - bgfx::FrameBufferHandle composite_fb[2] = { BGFX_INVALID_HANDLE, BGFX_INVALID_HANDLE }, - render_fb = BGFX_INVALID_HANDLE; - f32 proj[16], transform[16], view[16]; - K::VisualTrack bg{}; + bgfx::TextureHandle save = BGFX_INVALID_HANDLE; K::Byte *save_buffer; K::CompositionState *comp_save_called = nullptr; u32 ready_frame; @@ -148,47 +143,6 @@ namespace K::UI { ImGui::EndMainMenuBar(); } - 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); - bgfx::Attachment comp{}, comp2{}, ren{}; - comp.init(composite[0], bgfx::Access::Write); // DX 11 requires Write only - comp2.init(composite[1], bgfx::Access::Write); // DX 11 requires Write only - ren.init(render, bgfx::Access::ReadWrite); - composite_fb[0] = bgfx::createFrameBuffer(1, &comp); - composite_fb[1] = bgfx::createFrameBuffer(1, &comp2); - render_fb = bgfx::createFrameBuffer(1, &ren); - - save = bgfx::createTexture2D(s.width, s.height, false, 1, bgfx::TextureFormat::RGBA8, BGFX_TEXTURE_READ_BACK | BGFX_TEXTURE_BLIT_DST); - save_buffer = static_cast(std::malloc(s.width * s.height * 4)); - - bg.uniforms[0].val = ShaderGraph::XYZ{ static_cast(s.width), static_cast(s.height), 0.0f }; - - bx::mtxOrtho(proj, 0.0f, static_cast(s.width), 0.0f, static_cast(s.height), - 0.1f, 100.0f, 0.f, bgfx::getCaps()->homogeneousDepth); - f32 mtx1[16], mtx2[16]; - bx::mtxTranslate(mtx1, 0, 0, 0.0f); - bx::mtxScale(mtx2, static_cast(s.width), static_cast(s.height), 1.0f); - bx::mtxMul(transform, mtx2, mtx1); - bx::mtxTranslate(view, 0.f, 0.f, 1.0f); - } - - void DestroyViewport(CompositionState& s) { - bgfx::destroy(composite_fb[0]); - bgfx::destroy(composite_fb[1]); - bgfx::destroy(composite[0]); - bgfx::destroy(composite[1]); - bgfx::destroy(render_fb); - bgfx::destroy(render); - - bgfx::destroy(save); - std::free(save_buffer); - - for (auto& layer : s.layers) - layer.track.Clear(); - } - void DrawPlugboardVariableEditWidget(Plugboard::T_Map::type& var) { std::visit([](auto&& arg) { using T = std::decay_t; @@ -371,59 +325,47 @@ namespace K::UI { } if (ImGui::Begin("Viewport")) { + static bool do_bg = true; + if (ImGui::Shortcut(ImGuiKey_Space)) TogglePlay(); - // Perform rendering - u32 view_count = 0, layers_done = 0; - bgfx::setViewFrameBuffer(Graphics::K_VIEW_COMP_COMPOSITE + view_count, composite_fb[0]); // the other fb will be cleared in composite - bgfx::setViewClear(Graphics::K_VIEW_COMP_COMPOSITE + view_count, BGFX_CLEAR_COLOR, 0x00000000); - bgfx::setViewRect(Graphics::K_VIEW_COMP_COMPOSITE + view_count, 0, 0, s.width, s.height); - bgfx::touch(Graphics::K_VIEW_COMP_COMPOSITE + view_count); - view_count++; + app_state.render_view = Graphics::K_VIEW_COMP_COMPOSITE; - for (u32 i = s.layers.size() - 1; i != u32(-1); i--) { - if (std::ranges::find(s.disabled, i) != s.disabled.end()) continue; - if (s.current_frame > s.layers[i].out || s.current_frame < s.layers[i].in) continue; + i32 result_index = s.GetFrame(s.current_frame); - s.layers[i].track.GetFrame(s, Graphics::K_VIEW_COMP_COMPOSITE + view_count++, render_fb, s.width, - s.height, proj, view, transform); - Graphics::Composite(Graphics::K_VIEW_COMP_COMPOSITE + view_count++, composite_fb[(layers_done + 1) % 2], composite[layers_done % 2], render, s.layers[i].mode, s.width, s.height, proj, transform); - layers_done++; - } - if (layers_done != 0) { - bg.GetFrame(s, Graphics::K_VIEW_COMP_COMPOSITE + view_count++, render_fb, s.width, s.height, proj, view, - transform); - Graphics::Composite(Graphics::K_VIEW_COMP_COMPOSITE + view_count++, composite_fb[(layers_done + 1) % 2], - render, composite[layers_done % 2], Graphics::K_V_AlphaOver, s.width, s.height, - proj, transform); - layers_done++; - } - else { - bg.GetFrame(s, Graphics::K_VIEW_COMP_COMPOSITE + view_count++, composite_fb[0], s.width, s.height, proj, - view, - transform); - } + bgfx::TextureHandle present = BGFX_INVALID_HANDLE; - ImTextureID idx = ImGui::toId(composite[layers_done % 2], 1, 0); + if (do_bg) { + bg.GetFrame(s, app_state.render_view++, s.render_fb, s.width, s.height, s.proj, s.view, s.transform); + + Graphics::Composite(app_state.render_view++, s.composite_fb[1 - result_index], + s.render, s.composite[result_index], Graphics::K_V_AlphaOver, s.width, s.height, + s.proj, s.transform); + present = s.composite[1 - result_index]; + } + else + present = s.composite[result_index]; + + ImTextureID idx = ImGui::toId(present, 1, 0); if (idx != nullptr) ImGui::Image(idx, ImVec2{ static_cast(s.width), static_cast(s.height) }); ImGui::Text("%ux%u", s.width, s.height); ImGui::SameLine(); - ImGui::BeginDisabled(comp_save_called == nullptr); +/* 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); comp_save_called = &s; } - ImGui::EndDisabled(); - - app_state.n_views = Graphics::K_VIEW_COMP_COMPOSITE + view_count; + ImGui::EndDisabled();*/ ImGui::SetNextItemWidth(50.0f); ImGui::DragFloat("FPS", &s.fps, 1.0f, 1.0f, 120.0f); + ImGui::SameLine(); + ImGui::Checkbox("BG", &do_bg); } ImGui::End(); @@ -495,7 +437,7 @@ namespace K::UI { node_delete_requested = false; } - if (!ImGui::Begin(("Composition: " + s.name).c_str(), nullptr, ImGuiWindowFlags_NoScrollbar)) { + if (!ImGui::Begin("Composition", nullptr, ImGuiWindowFlags_NoScrollbar)) { ImGui::End(); return; } @@ -504,9 +446,12 @@ namespace K::UI { if (ImGui::Shortcut(ImGuiKey_Space)) TogglePlay(); + static f32 nodes_view_scale = 1.0f; + static ImVec2 nodes_view_pos{}, nodes_old_view_pos{}, nodes_begin{}; + static u32 node_current = 0; if (ImGui::Button("Add")) { - s.plugboard.nodes.Store(static_cast(node_current)); + s.plugboard.nodes.Store(static_cast(node_current), ImVec2{-nodes_view_pos.x + 70.0f, -nodes_view_pos.y + 70.0f}); } ImGui::SameLine(); ImGui::SetNextItemWidth(150.0f); @@ -550,9 +495,6 @@ namespace K::UI { drag_rect.Add(io.MouseClickedPos[ImGuiMouseButton_Left]); ImGuiStyle style = ImGui::GetStyle(); - static f32 view_scale = 1.0f; - static ImVec2 view_pos{}, old_view_pos{}, nodes_begin{}; - bool dragging_on_socket{}; ImVec2 drag_source{}; @@ -574,42 +516,43 @@ namespace K::UI { // Grid const f32 grid_inc = 20.0f; const f32 w = window->ContentRegionRect.GetWidth(), h = window->ContentRegionRect.GetHeight(); - for (f32 x = std::fmodf(view_pos.x, grid_inc); x < w; x += grid_inc) + for (f32 x = std::fmodf(nodes_view_pos.x, grid_inc); x < w; x += grid_inc) window->DrawList->AddLine(window_pos + ImVec2(x, 0.0f), window_pos + ImVec2(x, h), 0xFF222222, 1.0f); - for (f32 y = std::fmodf(view_pos.y, grid_inc); y < h; y += grid_inc) + for (f32 y = std::fmodf(nodes_view_pos.y, grid_inc); y < h; y += grid_inc) window->DrawList->AddLine(window_pos + ImVec2(0.0f, y), window_pos + ImVec2(w, y), 0xFF222222, 1.0f); // why are we re-getting positions on each frame? - // this is just convenient because sometimes we would be dragging on a socket! + // because it's just convenient because sometimes we would be dragging on a socket! links_pos.clear(); if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) { - old_view_pos = view_pos; + nodes_old_view_pos = nodes_view_pos; } if (ImGui::IsItemActive()) { - view_pos = old_view_pos + ImGui::GetMouseDragDelta(); + nodes_view_pos = nodes_old_view_pos + ImGui::GetMouseDragDelta(); } std::apply([&](auto&&... args) { (([&](auto&& arg){ for (auto& node : arg) { - PlugboardDrawNode>(s, node, view_pos, io, style, dragging_on_socket, - drag_source, node_delete_requested, links_pos); + PlugboardDrawNode>(s, node, nodes_view_pos, io, style, dragging_on_socket, + drag_source, node_delete_requested, links_pos); } }(args)), ...); }, s.plugboard.nodes.nodes); ImGui::SetCursorPos({}); ImGui::PushStyleColor(ImGuiCol_ChildBg, 0x33FFFFFF); - ImGui::BeginChild("Composition In", {}, + if (ImGui::BeginChild("Composition In", {}, ImGuiChildFlags_AlwaysAutoResize | ImGuiChildFlags_AutoResizeX | - ImGuiChildFlags_AutoResizeY); - ImGui::PopStyleColor(); - if (ImGui::BeginTable("Composition In", 3)) { - PlugboardNodeDrawSockets(s, s.plugboard.in_instance, style, dragging_on_socket, drag_source, links_pos, false); - ImGui::EndTable(); + ImGuiChildFlags_AutoResizeY)) { + ImGui::PopStyleColor(); + if (ImGui::BeginTable("Composition In", 3)) { + PlugboardNodeDrawSockets(s, s.plugboard.in_instance, style, dragging_on_socket, drag_source, + links_pos, false); + ImGui::EndTable(); + } } - ImGui::EndChild(); } ImGui::PopStyleColor(); @@ -685,7 +628,7 @@ namespace K::UI { }; constexpr static f32 knob_width = 8.0f; - constexpr static f32 kf_tab_width = 6.0f; + constexpr static f32 kf_tab_width = 12.0f; auto kf_drag_btn_handler = [&io, &drag_rect]( Plugboard::ChainSel *key_loop_target, bool dragging, Plugboard::ChainSel& chain, i32 frame, bool is_sel, @@ -741,8 +684,8 @@ namespace K::UI { if (ImGui::BeginTable("Layers", 5, ImGuiTableFlags_BordersInner | ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_RowBg | ImGuiTableFlags_ScrollY)) { ImGui::TableSetupScrollFreeze(0, 1); // Make top row always visible ImGui::TableSetupColumn("#", ImGuiTableColumnFlags_NoSort | ImGuiTableColumnFlags_IndentDisable); - ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_NoSort); - ImGui::TableSetupColumn("Blending", ImGuiTableColumnFlags_NoSort, 100.0f); + ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_NoSort, 140.0f); + ImGui::TableSetupColumn("Blending", ImGuiTableColumnFlags_NoSort, 120.0f); ImGui::TableSetupColumn("##Enable", ImGuiTableColumnFlags_NoSort); ImGui::TableSetupColumn("##Timeline", ImGuiTableColumnFlags_NoSort | ImGuiTableColumnFlags_WidthStretch); @@ -859,9 +802,31 @@ namespace K::UI { {0.0f, (row_height - ImGui::GetTextLineHeight()) / 2.0f}); bool layer_open = ImGui::TreeNodeEx(current_layer.name.c_str(), flags); if (ImGui::BeginPopupContextItem()) { - if (!s.selected.empty() && ImGui::Button("Delete")) { - layer_delete_requested = true; - ImGui::CloseCurrentPopup(); + if (!s.selected.empty()) { + if (ImGui::Button("Delete")) { + layer_delete_requested = true; + ImGui::CloseCurrentPopup(); + } + if (ImGui::Button("Make Sub-composition")) { + CompositionState new_comp{.width = s.width, .height = s.height}; + u32 index_in_new_comp = 0; + u64 frame_min = -1, frame_max = 0; + for (auto j : s.selected) { + new_comp.layers.emplace_back(std::move(s.layers[j])); + if (std::ranges::find(s.disabled, j) != s.disabled.end()) + new_comp.disabled.push_back(index_in_new_comp); + frame_min = std::min(frame_min, s.layers[j].in); + frame_max = std::max(frame_max, s.layers[j].out); + index_in_new_comp++; + } + new_comp.frame_max = frame_max - frame_min; + for (auto& layer : new_comp.layers) + layer.in -= frame_min; + layer_delete_requested = true; + app_state.project.compositions.push_back(std::move(new_comp)); + /* todo make comp */ + ImGui::CloseCurrentPopup(); + } } ImGui::EndPopup(); } @@ -1172,6 +1137,8 @@ namespace K::UI { ImGui::TableSetColumnIndex(2); auto& [chain, chain_copy] = std::get(info.p)->extra; + if (tl_clear_selection_request) + chain.selected.clear(); bool m_empty = chain.segments.empty(), m_has_before = !m_empty && chain.segments.begin()->frame < s.current_frame; @@ -1198,9 +1165,11 @@ namespace K::UI { if (l != chain.segments.end() && l->frame == static_cast(s.current_frame)) l->value = v; else { + auto d = std::distance(chain.segments.begin(), l); for (auto& sel : chain.selected) - if (sel >= std::distance(chain.segments.begin(), l)) + if (sel >= d) sel++; + chain.selected.push_back(d); chain.segments.emplace(l, s.current_frame, v, Plugboard::InterpolationExtra{ m_has_before ? std::prev(l)->interp.interp : Plugboard::K_I_Linear }); @@ -1251,7 +1220,11 @@ namespace K::UI { ImGui::SetCursorScreenPos(draw_pos); - ImGui::Button(is_sel ? "s" : "k", {kf_tab_width, row_height * .8f}); + ImGui::PushStyleColor(ImGuiCol_Button, 0x00000000); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(1.0f, 0)); + ImGui::Button((const char*)(is_sel ? u8"\u25c8" : u8"\u25c7"), {kf_tab_width, row_height * .8f}); + ImGui::PopStyleVar(); + ImGui::PopStyleColor(); kf_drag_btn_handler(key_loop_target, dragging, chain, frame, is_sel, sel_it, segment, val, ii, started_dragging, @@ -1265,13 +1238,11 @@ namespace K::UI { window->DrawList->AddRectFilled( {TimelineFrameToScreenView(view_left, view_amt, view_width, it->frame, - s.frame_max) + begin_tl.x + - kf_tab_width / 2.0f, + s.frame_max) + begin_tl.x, begin_tl.y + row_height / 2.0f - 2.0f}, {TimelineFrameToScreenView(view_left, view_amt, view_width, nit->frame, - s.frame_max) + begin_tl.x - - kf_tab_width / 2.0f, + s.frame_max) + begin_tl.x, begin_tl.y + row_height / 2.0f + 2.0f}, std::ranges::find(chain.selected, std::distance(chain.segments.begin(), it)) != @@ -1295,11 +1266,11 @@ namespace K::UI { else for (auto& u: current_layer.track.uniforms) { if (u.status != K_U_Exposed) continue; - std::visit([table_left](auto &&arg) { + std::visit([table_left, &init_y](auto &&arg) { using T = std::decay_t; if constexpr (std::is_same_v) links_pos[arg].sinks.push_back( // make nodes connect to collapsed layer row - {table_left, ImGui::GetCursorScreenPos().y - row_height / 2.0f}); + {table_left, init_y + row_height / 2.0f}); }, std::visit([&u](auto&& arg) -> auto& { return arg->in[u.connection.index].value; }, u.connection.p)); } @@ -1536,6 +1507,12 @@ namespace K::UI { ImPlot::PopStyleVar(2); ImGui::SetCursorScreenPos(ImGui::GetCursorStartPos()); tl_bg_handler(ImGui::GetContentRegionAvail().y, ImGui::GetContentRegionAvail().x); + if (tl_clear_selection_request) + for (auto& p : s.plugboard.selected_nodes) + if (auto *n = std::get_if(&p)){ + auto& [chain, _] = (*n)->extra; + chain.selected.clear(); + } } ImGui::EndChild(); ImGui::PopStyleColor(); @@ -1602,13 +1579,6 @@ namespace K::UI { } } } - - if (tl_clear_selection_request) - for (auto& p : s.plugboard.selected_nodes) - if (auto *n = std::get_if(&p)){ - auto& [chain, _] = (*n)->extra; - chain.selected.clear(); - } if (tl_clear_selection_request_this_frame && tl_clear_selection_request) tl_clear_selection_request = false; } @@ -1739,7 +1709,7 @@ namespace K::UI { } ImGui::EndTabBar(); } - ImGui::InputTextMultiline("##source", &s.layers[s.active].track.shader, ImVec2(-FLT_MIN, ImGui::GetContentRegionAvail().y - 25.0f), ImGuiInputTextFlags_AllowTabInput); + ImGui::InputTextMultiline("##source", &s.layers[s.active].track.shader, ImVec2(-FLT_MIN, ImGui::GetContentRegionAvail().y - 30.0f), ImGuiInputTextFlags_AllowTabInput); if (ImGui::Button("Submit Shader")) s.layers[s.active].track.Compile(); } @@ -1882,6 +1852,84 @@ namespace K::UI { void Assets() { if (ImGui::Begin("Assets", &draw_assets)) { + if (ImGui::Button("New Composition")) + ImGui::OpenPopup("New Composition"); + + ImVec2 center = ImGui::GetMainViewport()->GetCenter(); + ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f)); + if (ImGui::BeginPopupModal("New Composition", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) { + static String name("Comp"); + ImGui::InputText("Name", &name); + + static i32 frames = 100; + if (ImGui::InputInt("Frames", &frames, 1)) + frames = std::max(frames, 1); + + static f32 fps = 30.0f; + if (ImGui::InputFloat("FPS", &fps, 1.0f, 5.0f, "%.1f")) + fps = std::clamp(fps, 1.0f, 120.0f); + + static i32 dims[2] = { 1280, 720 }; + if (ImGui::InputInt2("Dimensions", dims)) { + dims[0] = std::max(1, dims[0]); + dims[1] = std::max(1, dims[1]); + } + + if (ImGui::Button("OK", ImVec2(120, 0)) && !name.empty()) { + app_state.project.compositions.emplace_back(name, 0, frames, fps, dims[0], dims[1]); + app_state.project.compositions.back().Setup(); + ImGui::CloseCurrentPopup(); + } + ImGui::SetItemDefaultFocus(); + ImGui::SameLine(); + if (ImGui::Button("Cancel", ImVec2(120, 0))) { + name.clear(); + ImGui::CloseCurrentPopup(); + } + ImGui::EndPopup(); + } + + + if (ImGui::BeginTable("Compositions", 4, ImGuiTableFlags_Borders)) { + ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthStretch); + ImGui::TableSetupColumn("Frames", ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("Dimensions", ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("FPS", ImGuiTableColumnFlags_WidthFixed); + ImGui::TableHeadersRow(); + + for (u32 j = 0; j < app_state.project.compositions.size(); j++) { + ImGui::TableNextRow(); + ImGui::PushID(j); + ImGui::TableNextColumn(); + auto& comp = app_state.project.compositions[j]; + bool sel = app_state.project.inspecting_composition == j; + if (ImGui::Selectable(comp.name.c_str(), sel, ImGuiSelectableFlags_SpanAllColumns) && !sel) { + app_state.project.inspecting_composition = static_cast(j); + bg.uniforms[0].val = ShaderGraph::XYZ{ + static_cast(app_state.project.compositions[app_state.project.inspecting_composition].width), + static_cast(app_state.project.compositions[app_state.project.inspecting_composition].height), + 0.0f }; + } + + if (sel && ImGui::BeginPopupContextItem()) { + if (ImGui::Button("Delete")) { + /* todo this is gonna be rough... */ + ImGui::CloseCurrentPopup(); + } + ImGui::EndPopup(); + } + + ImGui::TableNextColumn(); + ImGui::Text("%lu", comp.frame_max); + ImGui::TableNextColumn(); + ImGui::Text("%ux%u", comp.width, comp.height); + ImGui::TableNextColumn(); + ImGui::Text("%.1f", comp.fps); + ImGui::PopID(); + } + ImGui::EndTable(); + } + if (ImGui::Button("Load")) { static const SDL_DialogFileFilter import_filters[] = { { "PNG images", "png" }, @@ -1918,6 +1966,7 @@ namespace K::UI { 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); @@ -1925,16 +1974,6 @@ namespace K::UI { ImGui::TableHeadersRow(); i32 i = 0; - for (auto& comp : app_state.project.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++); @@ -1988,15 +2027,19 @@ namespace K::UI { if (draw_assets) Assets(); - 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); - if (draw_interpolation) Interpolation(comp); + if (app_state.project.inspecting_composition >= 0) { + 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); + 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; + 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(); @@ -2008,7 +2051,7 @@ namespace K::UI { 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, + app_state.render_view, caps->limits.maxViews, caps->limits.maxFrameBuffers, caps->limits.maxTextureSamplers, caps->supported & BGFX_CAPS_TEXTURE_BLIT ? "OK" : "NO", SDL_GetCurrentVideoDriver(), stat->cpuTimerFreq / stat->cpuTimeFrame); @@ -2028,6 +2071,27 @@ namespace K::UI { ImGuiIO& io = ImGui::GetIO(); io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; + + ImVector ranges; + ImFontGlyphRangesBuilder builder; + builder.AddChar(0x25C7); + builder.AddChar(0x25C8); + builder.AddRanges(io.Fonts->GetGlyphRangesDefault()); + builder.AddRanges(io.Fonts->GetGlyphRangesJapanese()); + builder.AddRanges(io.Fonts->GetGlyphRangesChineseFull()); + builder.AddRanges(io.Fonts->GetGlyphRangesKorean()); + builder.BuildRanges(&ranges); + io.Fonts->AddFontFromFileTTF("SourceHanSans-Regular.ttc", 16, nullptr, ranges.Data); + io.Fonts->Build(); + + ImGuiStyle& style = ImGui::GetStyle(); + style.WindowBorderSize = 0.0f; + style.DockingSeparatorSize = 1.0f; + style.ScrollbarRounding = 0.0f; + style.TabRounding = 0.0f; + style.ScrollbarSize = 8.0f; + style.FramePadding.y = 2.0f; + ImGui_Implbgfx_Init(Graphics::K_VIEW_UI); bgfx::setViewClear(Graphics::K_VIEW_UI, BGFX_CLEAR_COLOR); SetLogoView(); @@ -2062,7 +2126,10 @@ namespace K::UI { bg.uniforms.erase(bg.uniforms.begin()); for (auto& comp : app_state.project.compositions) - DestroyViewport(comp); + for (auto& layer : comp.layers) + layer.track.Clear(); + + app_state.project.compositions[app_state.project.inspecting_composition].Destroy(); ImGui_Implbgfx_Shutdown(); ImGui_ImplSDL3_Shutdown(); diff --git a/Keishiki/ext/bgfx b/Keishiki/ext/bgfx index 701d84c..c2b7416 160000 --- a/Keishiki/ext/bgfx +++ b/Keishiki/ext/bgfx @@ -1 +1 @@ -Subproject commit 701d84c2227e9219ad44c760e75c3c6f24a2cff0 +Subproject commit c2b7416a58a8610eecba5f6e3940e30c1b64e1f9 diff --git a/Keishiki/ext/freetype b/Keishiki/ext/freetype index 73720c7..bab9564 160000 --- a/Keishiki/ext/freetype +++ b/Keishiki/ext/freetype @@ -1 +1 @@ -Subproject commit 73720c7c9958e87b3d134a7574d1720ad2d24442 +Subproject commit bab9564a979d1f15f39f047b9035f92a37ed8317 diff --git a/Keishiki/ext/glaze b/Keishiki/ext/glaze index cec502d..3ffd379 160000 --- a/Keishiki/ext/glaze +++ b/Keishiki/ext/glaze @@ -1 +1 @@ -Subproject commit cec502d0c0f2052d07df4b0d488f7e3e23818b9d +Subproject commit 3ffd379050bfd0d83664ac469c2495cbff2c8446 diff --git a/Keishiki/ext/imgui b/Keishiki/ext/imgui index 7237d3e..10a5a85 160000 --- a/Keishiki/ext/imgui +++ b/Keishiki/ext/imgui @@ -1 +1 @@ -Subproject commit 7237d3e5c3a6b837b7b457460877cf5eea8c3745 +Subproject commit 10a5a857f5b5cd6eae7f86461b939ec05e9235b2 diff --git a/Keishiki/include/Keishiki.h b/Keishiki/include/Keishiki.h index 704b4db..b9096cd 100644 --- a/Keishiki/include/Keishiki.h +++ b/Keishiki/include/Keishiki.h @@ -5,10 +5,6 @@ #include namespace K { - bool Init(); - void Run(); - void Shutdown(); - struct Layer { VisualTrack track; String name; @@ -31,6 +27,17 @@ namespace K { Vector disabled; // indices for layers Plugboard::Plugboard plugboard; + + // Rendering + bgfx::TextureHandle composite[2] = { BGFX_INVALID_HANDLE, BGFX_INVALID_HANDLE }, + render = BGFX_INVALID_HANDLE; + bgfx::FrameBufferHandle composite_fb[2] = { BGFX_INVALID_HANDLE, BGFX_INVALID_HANDLE }, + render_fb = BGFX_INVALID_HANDLE; + f32 proj[16], transform[16], view[16]; + + void Setup(); + i32 GetFrame(u32 frame); + void Destroy(); }; struct ProjectState { @@ -47,7 +54,7 @@ namespace K { u64 init_time{}, last_time{}, current_time{}, delta_t{}; u32 bgfx_frame{}; - u32 n_views{}; + u32 render_view{}; ProjectState project{}; } app_state; // global ! diff --git a/Keishiki/include/Plugboard.h b/Keishiki/include/Plugboard.h index bd86466..61621fc 100644 --- a/Keishiki/include/Plugboard.h +++ b/Keishiki/include/Plugboard.h @@ -582,8 +582,10 @@ namespace K::Plugboard { return std::get>(nodes).insert(v); } template N> - auto& Store(N&& v) { - return *std::get>(nodes).emplace(std::forward(v)); + auto& Store(N&& v, const ImVec2& pos = {}) { + auto n = std::get>(nodes).emplace(std::forward(v)); + n->pos = pos; + return *n; } auto& Store(const std::variant& v) { return std::visit([this](auto&& arg) { return store(arg); }, v); @@ -604,12 +606,12 @@ namespace K::Plugboard { typename std::tuple_element>::type; template - void Store(K_P_Nodes i, std::index_sequence) { - ((Is == i ? Store(NthTypeOf{}) : NthTypeOf{}), ...); + void Store(K_P_Nodes i, std::index_sequence, const ImVec2& pos = {}) { + ((Is == i ? Store(NthTypeOf{}, pos) : NthTypeOf{}), ...); } - void Store(K_P_Nodes i) { - Store(i, std::index_sequence_for{}); + void Store(K_P_Nodes i, const ImVec2& pos = {}) { + Store(i, std::index_sequence_for{}, pos); } template