This commit is contained in:
lachrymaLF 2024-06-27 16:48:54 -04:00
parent 5ca9c91098
commit cacc2d48ca
11 changed files with 427 additions and 361 deletions

View file

@ -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 ()

136
Keishiki/Entry.cpp Normal file
View file

@ -0,0 +1,136 @@
#include "Keishiki.h"
#include "Graphics.h"
#include "UI.h"
#if BX_PLATFORM_LINUX && WL_EGL_PLATFORM
#include <wayland-egl.h>
#endif
#include <SDL3/SDL.h>
#include <SDL3/SDL_timer.h>
#include <bgfx/bgfx.h>
#include <bgfx/platform.h>
#include "backends/imgui_impl_sdl3.h"
#include <imgui.h>
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(&current_event) != 0;) {
ImGui_ImplSDL3_ProcessEvent(&current_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;
}

View file

@ -1,140 +1,67 @@
#include "Keishiki.h"
#include "Graphics.h"
#include "UI.h"
#if BX_PLATFORM_LINUX && WL_EGL_PLATFORM
#include <wayland-egl.h>
#endif
#include <SDL3/SDL.h>
#include <SDL3/SDL_timer.h>
#include <bgfx/bgfx.h>
#include <bgfx/platform.h>
#include "backends/imgui_impl_sdl3.h"
#include <imgui.h>
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<f32>(width), 0.0f, static_cast<f32>(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<f32>(width), static_cast<f32>(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(&current_event) != 0;) {
ImGui_ImplSDL3_ProcessEvent(&current_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);
}
}

View file

@ -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;
}
}

View file

@ -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<Byte *>(std::malloc(s.width * s.height * 4));
bg.uniforms[0].val = ShaderGraph::XYZ{ static_cast<f32>(s.width), static_cast<f32>(s.height), 0.0f };
bx::mtxOrtho(proj, 0.0f, static_cast<f32>(s.width), 0.0f, static_cast<f32>(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<f32>(s.width), static_cast<f32>(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<Plugboard::T_Count>::type& var) {
std::visit([](auto&& arg) {
using T = std::decay_t<decltype(arg)>;
@ -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<f32>(s.width), static_cast<f32>(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<Plugboard::K_P_Nodes>(node_current));
s.plugboard.nodes.Store(static_cast<Plugboard::K_P_Nodes>(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<std::decay_t<decltype(node)>>(s, node, view_pos, io, style, dragging_on_socket,
drag_source, node_delete_requested, links_pos);
PlugboardDrawNode<std::decay_t<decltype(node)>>(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<Plugboard::Chain*>(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<i32>(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<decltype(arg)>;
if constexpr (std::is_same_v<T, Plugboard::ConnectInfo>)
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<Plugboard::Chain*>(&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<Plugboard::Chain*>(&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<i32>(j);
bg.uniforms[0].val = ShaderGraph::XYZ{
static_cast<f32>(app_state.project.compositions[app_state.project.inspecting_composition].width),
static_cast<f32>(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<i32>(comp_save_called->width), static_cast<i32>(comp_save_called->height), 4, save_buffer, static_cast<i32>(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<i32>(comp_save_called->width),
static_cast<i32>(comp_save_called->height), 4, save_buffer,
static_cast<i32>(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<ImWchar> 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();

@ -1 +1 @@
Subproject commit 701d84c2227e9219ad44c760e75c3c6f24a2cff0
Subproject commit c2b7416a58a8610eecba5f6e3940e30c1b64e1f9

@ -1 +1 @@
Subproject commit 73720c7c9958e87b3d134a7574d1720ad2d24442
Subproject commit bab9564a979d1f15f39f047b9035f92a37ed8317

@ -1 +1 @@
Subproject commit cec502d0c0f2052d07df4b0d488f7e3e23818b9d
Subproject commit 3ffd379050bfd0d83664ac469c2495cbff2c8446

@ -1 +1 @@
Subproject commit 7237d3e5c3a6b837b7b457460877cf5eea8c3745
Subproject commit 10a5a857f5b5cd6eae7f86461b939ec05e9235b2

View file

@ -5,10 +5,6 @@
#include <atomic>
namespace K {
bool Init();
void Run();
void Shutdown();
struct Layer {
VisualTrack track;
String name;
@ -31,6 +27,17 @@ namespace K {
Vector<u32> 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 !

View file

@ -582,8 +582,10 @@ namespace K::Plugboard {
return std::get<plf::colony<N>>(nodes).insert(v);
}
template <SameAsAny<Ns...> N>
auto& Store(N&& v) {
return *std::get<plf::colony<N>>(nodes).emplace(std::forward<N>(v));
auto& Store(N&& v, const ImVec2& pos = {}) {
auto n = std::get<plf::colony<N>>(nodes).emplace(std::forward<N>(v));
n->pos = pos;
return *n;
}
auto& Store(const std::variant<Ns...>& v) {
return std::visit([this](auto&& arg) { return store(arg); }, v);
@ -604,12 +606,12 @@ namespace K::Plugboard {
typename std::tuple_element<N, std::tuple<Ts...>>::type;
template <std::size_t... Is>
void Store(K_P_Nodes i, std::index_sequence<Is...>) {
((Is == i ? Store(NthTypeOf<Is, Ns...>{}) : NthTypeOf<Is, Ns...>{}), ...);
void Store(K_P_Nodes i, std::index_sequence<Is...>, const ImVec2& pos = {}) {
((Is == i ? Store(NthTypeOf<Is, Ns...>{}, pos) : NthTypeOf<Is, Ns...>{}), ...);
}
void Store(K_P_Nodes i) {
Store(i, std::index_sequence_for<Ns...>{});
void Store(K_P_Nodes i, const ImVec2& pos = {}) {
Store(i, std::index_sequence_for<Ns...>{}, pos);
}
template <std::size_t... Is>