editor.cpp

cross platform rendering playground

src/editor/editor.cpp

11.13 KB
#ifdef ENABLE_EDITOR
#define IMGUI_DEFINE_MATH_OPERATORS
#include "editor.h"

#include <imgui.h>
#include <imgui_internal.h>

#include "backend/imgui_backend.h"
#include "core/logger.h"
#include "core/platform.h"
#include "renderer.h"
#include "scene/scene.h"

namespace editor {

struct DebugItem {
    DebugViewMode mode;
    const char *name;
};

const DebugItem DEBUG_ITEMS[] = {
    {NONE, "NONE"},
    {DEPTH_grayscale, "DEPTH_grayscale"},
    {DEPTH_color, "DEPTH_color"},
    {UV, "UV"},
    {NORMAL_TEX, "NORMAL_TEX"},
    {NORMAL_GEO, "NORMAL_GEO"},
    {TANGENT_GEO, "TANGENT_GEO"},
    {BITANGENT_GEO, "BITANGENT_GEO"},
    {TANGENT_GEO_W, "TANGENT_GEO_W"},
    {SHADING_NORMAL, "SHADING_NORMAL"},
    {ALPHA, "ALPHA"},
    {EMISSIVE, "EMISSIVE"},
    {ALBEDO, "ALBEDO"},
    {METALLIC, "METALLIC"},
    {ROUGHNESS, "ROUGHNESS"},
    {OCCLUSION, "OCCLUSION"},
    {SHADOW_CASCADE, "SHADOW_CASCADE"},
    {LIGHTING_ONLY, "LIGHTING_ONLY"},
    {CLUSTER_HEATMAP, "CLUSTER_HEATMAP"},
    {CLUSTER_BOUNDS, "CLUSTER_BOUNDS"},
    {WIREFRAME, "WIREFRAME"},
    {PROBES_RAY_SAMPLES, "PROBES_RAY_SAMPLES"},
    {PROBES_IRRADIANCE, "PROBES_IRRADIANCE"},
    {PROBES_DISTANCE, "PROBES_DISTANCE"},
    {PROBES_CLASSIFICATION, "PROBES_CLASSIFICATION"}
};

void build_layout_0(Editor &e, ImGuiID dockspace_id, ImVec2 size) {
    ImGui::DockBuilderRemoveNode(dockspace_id);
    ImGui::DockBuilderAddNode(dockspace_id, ImGuiDockNodeFlags_DockSpace | ImGuiDockNodeFlags_PassthruCentralNode);
    ImGui::DockBuilderSetNodeSize(dockspace_id, size);

    ImGuiID root = dockspace_id;

    // split 0: root → left (20%) + remaining (80%)
    ImGuiID left;
    ImGuiID remaining;
    ImGui::DockBuilderSplitNode(root, ImGuiDir_Left, 0.20f, &left, &remaining);

    // split 1: remaining → right (25%) + center (75%)
    ImGuiID right;
    ImGuiID center;
    ImGui::DockBuilderSplitNode(remaining, ImGuiDir_Right, 0.25f, &right, &center);

    // split 2: left → left_bottom (50%) + left (50%)
    ImGuiID left_bottom;
    ImGui::DockBuilderSplitNode(left, ImGuiDir_Down, 0.5f, &left_bottom, &left);

    // split 3: center → center_bottom (35%) + center (65%)
    ImGuiID center_bottom;
    ImGui::DockBuilderSplitNode(center, ImGuiDir_Down, 0.35f, &center_bottom, &center);

    // split 4: right → right_bottom (50%) + right (50%)
    ImGuiID right_bottom;
    ImGui::DockBuilderSplitNode(right, ImGuiDir_Down, 0.5f, &right_bottom, &right);

    ImGui::DockBuilderDockWindow("Hierarchy", left);
    ImGui::DockBuilderDockWindow("Viewport", center);
    ImGui::DockBuilderDockWindow("Timings", center_bottom);
    ImGui::DockBuilderDockWindow("Log", center_bottom);
    ImGui::DockBuilderDockWindow("Inspector", right);
    ImGui::DockBuilderDockWindow("Texture Viewer", right_bottom);

    ImGui::DockBuilderFinish(dockspace_id);
    e.dock_layout_built = true;
}

void draw_menu_bar(Editor &editor, RenderData &rd) {
    if (ImGui::BeginMenuBar()) {

        ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(10.0f, 8.0f));
        if (ImGui::BeginMenu("File")) {
            if (ImGui::MenuItem("Exit")) {
                platform_request_close(*editor.platform);
            }
            ImGui::EndMenu();
        }
        if (ImGui::BeginMenu("View")) {
            ImGui::MenuItem("Viewport", nullptr, &editor.show_viewport);
            ImGui::MenuItem("Hierarchy", nullptr, &editor.show_hierarchy);
            ImGui::MenuItem("Inspector", nullptr, &editor.show_inspector);
            ImGui::MenuItem("Texture Viewer", nullptr, &editor.show_texture_viewer);
            ImGui::MenuItem("Timings", nullptr, &editor.show_timings);
            ImGui::MenuItem("Log", nullptr, &editor.show_log);
            if (ImGui::BeginMenu("Render Mode")) {
                for (int i = 0; i < IM_ARRAYSIZE(DEBUG_ITEMS); i++) {
                    if (ImGui::MenuItem(DEBUG_ITEMS[i].name, nullptr, rd.debug == DEBUG_ITEMS[i].mode)) {
                        rd.debug = DEBUG_ITEMS[i].mode;
                    }
                }
                ImGui::EndMenu();
            }
            if (ImGui::BeginMenu("Debug Draw")) {
                ImGui::MenuItem("Lines enabled", nullptr, &editor.show_debug_lines);
                ImGui::MenuItem("Force X-ray (ignore depth)", nullptr, &editor.force_xray);
                ImGui::Separator();
                ImGui::MenuItem("Category: default", nullptr, &editor.show_cat_default);
                ImGui::MenuItem("Category: grid", nullptr, &editor.show_cat_grid);
                ImGui::MenuItem("Category: proxy", nullptr, &editor.show_cat_proxy);
                ImGui::MenuItem("Category: selection", nullptr, &editor.show_cat_selection);
                ImGui::MenuItem("Category: light", nullptr, &editor.show_cat_light);
                ImGui::MenuItem("Category: DDGI", nullptr, &editor.show_cat_ddgi);
                ImGui::MenuItem("Category: gizmo", nullptr, &editor.show_cat_gizmo);
                ImGui::EndMenu();
            }
            ImGui::EndMenu();
        }
        ImGui::PopStyleVar();

        const char *button_text = "Reload Shaders";
        f32 button_width = ImGui::CalcTextSize(button_text).x + ImGui::GetStyle().FramePadding.x * 2.0f;
        f32 right_padding = ImGui::GetStyle().FramePadding.x;
        f32 target_x = ImGui::GetCursorPosX() + ImGui::GetContentRegionAvail().x - button_width - right_padding;
        ImGui::SetCursorPosX(target_x);
        if (ImGui::Button(button_text)) {
            request_reload(editor);
        }

        ImGui::EndMenuBar();
    }
}

void draw_status_bar(Editor &editor, RenderData &rd) {
    const ImGuiViewport *viewport = ImGui::GetMainViewport();
    f32 frame_height = ImGui::GetFrameHeight();
    ImGui::SetNextWindowPos(ImVec2(viewport->Pos.x, viewport->Pos.y + viewport->Size.y - frame_height));
    ImGui::SetNextWindowSize(ImVec2(viewport->Size.x, frame_height));
    ImGui::SetNextWindowViewport(viewport->ID);

    ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
    ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
    ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(6.0f, 2.0f));

    ImGuiWindowFlags flags = ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoMove |
                             ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_NoSavedSettings |
                             ImGuiWindowFlags_NoDocking;

    ImGui::PushStyleColor(ImGuiCol_WindowBg, ImGui::GetStyleColorVec4(ImGuiCol_MenuBarBg));

    if (ImGui::Begin("##StatusBarWindow", nullptr, flags)) {
        const FrameStats &st = rd.frame_stats;

        auto sep = []() {
            ImGui::SameLine();
            ImGui::TextUnformatted("|");
            ImGui::SameLine();
        };

        ImGui::PushFont(editor.font_mono);
        ImGui::Text("FPS: %.1f", st.fps());
        sep();
        ImGui::Text("Viewport: %ux%u", editor.viewport_width, editor.viewport_height);
        ImGui::PopFont();
        sep();

        if (rd.debug >= 0 && rd.debug < IM_ARRAYSIZE(DEBUG_ITEMS)) {
            ImGui::Text("Mode: %s", DEBUG_ITEMS[rd.debug].name);
        } else {
            ImGui::Text("Mode: %d", rd.debug);
        }

        ImGui::End();
    }

    ImGui::PopStyleColor();
    ImGui::PopStyleVar(3);
}

void init(
    Editor &e,
    rhi::Device &device,
    Platform &platform,
    QuickSubmit &qs,
    Arena &staging,
    FrameGraph &fg,
    AssetManager &assets
) {
    e.device = &device;
    e.platform = &platform;
    e.assets = &assets;
    e.qs = &qs;
    VEL_INFO("Initializing ImGui...");

    IMGUI_CHECKVERSION();
    ImGui::CreateContext();
    ImGuiIO &io = ImGui::GetIO();
    io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
    io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;

    static const ImWchar ranges[] = {0x0020, 0x007E, 0};

    ImGuiStyle &style = ImGui::GetStyle();
    f32 scale_factor = platform_content_scale(platform);
    style.ScaleAllSizes(scale_factor);

    apply_style(scale_factor, style);

    ImFontConfig cfg{};
    e.font_ui = io.Fonts->AddFontFromFileTTF("assets/fonts/Inter-Regular.otf", 16.0f, &cfg, ranges);
    e.font_mono = io.Fonts->AddFontFromFileTTF("assets/fonts/MonaspaceNeon-Regular.otf", 14.0f, &cfg, ranges);

    io.ConfigDpiScaleFonts = true;
    io.ConfigDpiScaleViewports = true;
    io.ConfigViewportsNoDecoration = true;
    io.ConfigViewportsNoDefaultParent = true;
    io.ConfigNavCaptureKeyboard = true;
    io.ConfigInputTextCursorBlink = true;
    io.ConfigWindowsResizeFromEdges = true;
    cfg.PixelSnapH = true;
    cfg.PixelSnapV = true;

    imgui_backend::init(device, platform_native_window(platform), qs, staging);

    rhi::SamplerDesc vp_sampler_desc;
    vp_sampler_desc.filter = rhi::SamplerFilter::LINEAR;
    vp_sampler_desc.address = rhi::SamplerAddressMode::CLAMP;
    vp_sampler_desc.name = "samp/viewport";
    rhi::create_sampler(device, vp_sampler_desc, e.viewport_sampler);

    init_log_viewer();
}

void shutdown(Editor &e, rhi::Device &device) {
    VEL_INFO("Shutting down ImGui...");
    shutdown_texture_inspectors(device);
    imgui_backend::shutdown(device);
    ImGui::DestroyContext();
    rhi::destroy_sampler(device, e.viewport_sampler);
}

void begin_frame(Editor &e, RenderData &rd) {
    imgui_backend::begin_frame();

    ImGuiViewport *viewport = ImGui::GetMainViewport();
    ImGui::SetNextWindowPos(viewport->WorkPos);
    ImGui::SetNextWindowSize(viewport->WorkSize);
    ImGui::SetNextWindowViewport(viewport->ID);
    ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
    ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
    ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));

    ImGui::Begin(
        "##DockSpaceHost",
        nullptr,
        ImGuiWindowFlags_NoDocking | ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoTitleBar |
            ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove |
            ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoNavFocus
    );

    draw_menu_bar(e, rd);

    ImVec2 host_size = ImGui::GetContentRegionAvail();
    ImGuiID dockspace_id = ImGui::GetID("MainDockSpace");

    // status bar substract
    f32 status_h = ImGui::GetFrameHeight();
    ImVec2 subtracted_dock_size = ImVec2(host_size.x, ImMax(host_size.y - status_h, 0.0f));
    ImGui::DockSpace(dockspace_id, subtracted_dock_size, ImGuiDockNodeFlags_None);

    if (!e.dock_layout_built) {
        build_layout_0(e, dockspace_id, subtracted_dock_size);
    }

    draw_status_bar(e, rd);

    ImGui::End();
    ImGui::PopStyleVar(3);
}

void end_frame(Editor &e, Scene &scene, RenderData &rd, const PassInspectorEntry *passes, u32 pass_count) {
    // ImGui::ShowStyleEditor();
    if (e.show_timings) {
        draw_timings_window(scene, rd, passes, pass_count, e, e.font_mono);
    }
    // if (e.show_texture_viewer) {
    //     draw_texture_viewer_window(*e.device, e, rd);
    // }
    if (e.show_log) {
        draw_log_window(e.font_mono);
    }
    if (e.show_hierarchy && e.assets) {
        draw_scene_graph_window(scene, *e.assets, e.selected_node);
    }
    if (e.show_inspector && e.assets) {
        draw_inspector_window(scene, *e.assets, e.font_mono, e);
    }
    if (e.show_viewport) {
        draw_viewport_central(e, rd);
    }
}

} // namespace editor

#endif // ENABLE_EDITOR