commit ac77711dd16331d9a8d38e763fee2bb4991262da
Author: 54108 <3318195572@qq.com>
Date:   Mon Sep 16 01:26:08 2024 +0800

    添加项目基础结构,包括CMake配置、头文件和实现文件,新增.gitignore和README文档

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..d73e005
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+.cache
+build
\ No newline at end of file
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..108bd0b
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,41 @@
+cmake_minimum_required(VERSION 3.5)
+project(atom C CXX)
+
+if(NOT CMAKE_BUILD_TYPE)
+  set(CMAKE_BUILD_TYPE Debug CACHE STRING "" FORCE)
+endif()
+
+set (CMAKE_CXX_STANDARD 23)
+set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DVK_PROTOTYPES")
+set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DVK_PROTOTYPES")
+set (CMAKE_EXPORT_COMPILE_COMMANDS ON)
+
+# GLFW
+set(GLFW_DIR glfw) # Set this to point to an up-to-date GLFW repo
+option(GLFW_BUILD_EXAMPLES "Build the GLFW example programs" OFF)
+option(GLFW_BUILD_TESTS "Build the GLFW test programs" OFF)
+option(GLFW_BUILD_DOCS "Build the GLFW documentation" OFF)
+option(GLFW_INSTALL "Generate installation target" OFF)
+option(GLFW_DOCUMENT_INTERNALS "Include internals in documentation" OFF)
+add_subdirectory(${GLFW_DIR} binary_dir EXCLUDE_FROM_ALL)
+include_directories(${GLFW_DIR}/include)
+
+# Dear ImGui
+set(IMGUI_DIR imgui)
+include_directories(${IMGUI_DIR} ${IMGUI_DIR}/backends)
+
+# Libraries
+find_package(Vulkan REQUIRED)
+#find_library(VULKAN_LIBRARY
+  #NAMES vulkan vulkan-1)
+#set(LIBRARIES "glfw;${VULKAN_LIBRARY}")
+set(LIBRARIES "glfw;Vulkan::Vulkan")
+
+# Use vulkan headers from glfw:
+include_directories(${GLFW_DIR}/deps)
+
+file(GLOB sources *.cpp)
+
+add_executable(atom ${sources} ${IMGUI_DIR}/backends/imgui_impl_glfw.cpp ${IMGUI_DIR}/backends/imgui_impl_vulkan.cpp ${IMGUI_DIR}/imgui.cpp ${IMGUI_DIR}/imgui_draw.cpp ${IMGUI_DIR}/imgui_demo.cpp ${IMGUI_DIR}/imgui_tables.cpp ${IMGUI_DIR}/imgui_widgets.cpp)
+target_link_libraries(atom ${LIBRARIES})
+target_compile_definitions(atom PUBLIC -DImTextureID=ImU64)
diff --git a/HYWenHei-85W.ttf b/HYWenHei-85W.ttf
new file mode 100644
index 0000000..ebcab43
Binary files /dev/null and b/HYWenHei-85W.ttf differ
diff --git a/atom.cpp b/atom.cpp
new file mode 100644
index 0000000..a41d6b7
--- /dev/null
+++ b/atom.cpp
@@ -0,0 +1,77 @@
+#include "atom.h"
+#include "imgui.h"
+
+void InitStyle(ImGuiStyle &style)
+{
+    ImVec4 *colors                      = style.Colors;
+    colors[ImGuiCol_FrameBg]            = ImVec4(0.16f, 0.16f, 0.17f, 1.00f);
+    colors[ImGuiCol_FrameBgHovered]     = ImVec4(0.37f, 0.36f, 0.36f, 102.00f);
+    colors[ImGuiCol_FrameBgActive]      = ImVec4(0.10f, 0.10f, 0.10f, 171.00f);
+    colors[ImGuiCol_TitleBgActive]      = ImVec4(0.20f, 0.20f, 0.20f, 255.00f);
+    colors[ImGuiCol_CheckMark]          = ImVec4(0.61f, 0.61f, 0.61f, 1.00f);
+    colors[ImGuiCol_SliderGrab]         = ImVec4(0.64f, 0.64f, 0.64f, 1.00f);
+    colors[ImGuiCol_SliderGrabActive]   = ImVec4(0.31f, 0.31f, 0.31f, 1.00f);
+    colors[ImGuiCol_Button]             = ImVec4(0.22f, 0.22f, 0.22f, 0.40f);
+    colors[ImGuiCol_ButtonHovered]      = ImVec4(0.29f, 0.29f, 0.29f, 1.00f);
+    colors[ImGuiCol_ButtonActive]       = ImVec4(0.13f, 0.13f, 0.13f, 1.00f);
+    colors[ImGuiCol_Header]             = ImVec4(0.45f, 0.45f, 0.45f, 0.31f);
+    colors[ImGuiCol_HeaderHovered]      = ImVec4(0.55f, 0.55f, 0.55f, 0.80f);
+    colors[ImGuiCol_HeaderActive]       = ImVec4(0.09f, 0.09f, 0.09f, 1.00f);
+    colors[ImGuiCol_ResizeGrip]         = ImVec4(1.00f, 1.00f, 1.00f, 0.20f);
+    colors[ImGuiCol_ResizeGripHovered]  = ImVec4(0.46f, 0.46f, 0.46f, 0.67f);
+    colors[ImGuiCol_ResizeGripActive]   = ImVec4(0.17f, 0.17f, 0.17f, 0.95f);
+    colors[ImGuiCol_SeparatorActive]    = ImVec4(0.42f, 0.42f, 0.42f, 1.00f);
+    colors[ImGuiCol_SeparatorHovered]   = ImVec4(0.50f, 0.50f, 0.50f, 0.78f);
+    colors[ImGuiCol_TabHovered]         = ImVec4(0.45f, 0.45f, 0.45f, 0.80f);
+    colors[ImGuiCol_TabActive]          = ImVec4(0.28f, 0.28f, 0.28f, 1.00f);
+    colors[ImGuiCol_TabUnfocusedActive] = ImVec4(0.19f, 0.19f, 0.19f, 1.00f);
+    colors[ImGuiCol_DockingPreview]     = ImVec4(0.51f, 0.51f, 0.51f, 0.70f);
+    colors[ImGuiCol_Tab]                = ImVec4(0.21f, 0.21f, 0.21f, 0.86f);
+    colors[ImGuiCol_TabUnfocused]       = ImVec4(0.15f, 0.15f, 0.15f, 0.97f);
+    colors[ImGuiCol_NavHighlight]       = ImVec4(1.00f, 0.40f, 0.13f, 1.00f);
+    colors[ImGuiCol_TextSelectedBg]     = ImVec4(0.45f, 1.00f, 0.85f, 0.35f);
+
+    style.WindowRounding    = 4;
+    style.FrameRounding     = 4;
+    style.GrabRounding      = 3;
+    style.ScrollbarSize     = 7;
+    style.ScrollbarRounding = 0;
+}
+
+void DrawTitleBar(const char *title, bool &p_open)
+{
+    ImGuiWindow *window = ImGui::GetCurrentWindow();
+    if (window->SkipItems)
+        return;
+
+    ImGuiIO &io = ImGui::GetIO();
+    (void)io;
+    // 绘制左上角的文字
+    ImGui::Text("%s", title);
+    // ImGui::SameLine();
+    // ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / io.Framerate, io.Framerate);
+
+    // 计算关闭按钮位置
+    float  button_padding = 5.0f;  // 关闭按钮距离边框的距离
+    ImVec2 close_pos      = ImVec2(window->Pos.x + window->Size.x - ImGui::GetStyle().FramePadding.x - ImGui::GetFrameHeight() - button_padding, window->Pos.y + ImGui::GetStyle().FramePadding.y + button_padding);
+    ImGui::SetCursorScreenPos(close_pos);
+
+    // 绘制关闭按钮
+    bool close_clicked = ImGui::InvisibleButton("##close_button", ImVec2(ImGui::GetFrameHeight(), ImGui::GetFrameHeight()));
+    if (ImGui::IsItemHovered())
+    {
+        ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);  // 更改鼠标光标
+        ImGui::SetTooltip("关闭窗口");
+    }
+
+    // 绘制关闭按钮的图标(一个简单的'X')
+    ImDrawList *draw_list    = ImGui::GetWindowDrawList();
+    ImVec2      close_center = ImVec2(close_pos.x + ImGui::GetFrameHeight() * 0.5f, close_pos.y + ImGui::GetFrameHeight() * 0.5f);
+    float       cross_extent = ImGui::GetFrameHeight() * 0.3f * 0.7071f;
+    draw_list->AddLine(ImVec2(close_center.x - cross_extent, close_center.y - cross_extent), ImVec2(close_center.x + cross_extent, close_center.y + cross_extent), IM_COL32(255, 255, 255, 255), 1.0f);
+    draw_list->AddLine(ImVec2(close_center.x + cross_extent, close_center.y - cross_extent), ImVec2(close_center.x - cross_extent, close_center.y + cross_extent), IM_COL32(255, 255, 255, 255), 1.0f);
+
+    // 如果关闭按钮被点击,将布尔值设置为 false
+    if (close_clicked)
+        p_open = false;
+}
\ No newline at end of file
diff --git a/atom.h b/atom.h
new file mode 100644
index 0000000..aab8516
--- /dev/null
+++ b/atom.h
@@ -0,0 +1,20 @@
+#ifndef ATOM_H
+#define ATOM_H
+
+#include "imgui.h"
+#include "imgui_internal.h"
+#include "imgui_impl_glfw.h"
+#include "imgui_impl_vulkan.h"
+#include "imstb_truetype.h"
+#include <iostream>
+#include <vector>
+#include <cstdlib>
+#define GLFW_INCLUDE_NONE
+#define GLFW_INCLUDE_VULKAN
+#include <GLFW/glfw3.h>
+
+void InitStyle(ImGuiStyle &style);
+
+void DrawTitleBar(const char *title, bool &p_open);
+
+#endif
\ No newline at end of file
diff --git a/main.cpp b/main.cpp
new file mode 100644
index 0000000..f3f9b13
--- /dev/null
+++ b/main.cpp
@@ -0,0 +1,661 @@
+// Dear ImGui: standalone example application for Glfw + Vulkan
+
+// Learn about Dear ImGui:
+// - FAQ                  https://dearimgui.com/faq
+// - Getting Started      https://dearimgui.com/getting-started
+// - Documentation        https://dearimgui.com/docs (same as your local docs/ folder).
+// - Introduction, links and more at the top of imgui.cpp
+
+// Important note to the reader who wish to integrate imgui_impl_vulkan.cpp/.h in their own engine/app.
+// - Common ImGui_ImplVulkan_XXX functions and structures are used to interface with imgui_impl_vulkan.cpp/.h.
+//   You will use those if you want to use this rendering backend in your engine/app.
+// - Helper ImGui_ImplVulkanH_XXX functions and structures are only used by this example (main.cpp) and by
+//   the backend itself (imgui_impl_vulkan.cpp), but should PROBABLY NOT be used by your own engine/app code.
+// Read comments in imgui_impl_vulkan.h.
+
+#include "atom.h"
+#include "imgui.h"
+#include <vector>
+
+// Volk headers
+#ifdef IMGUI_IMPL_VULKAN_USE_VOLK
+#define VOLK_IMPLEMENTATION
+#include <volk.h>
+#endif
+
+// [Win32] Our example includes a copy of glfw3.lib pre-compiled with VS2010 to maximize ease of testing and compatibility with old VS compilers.
+// To link with VS2010-era libraries, VS2015+ requires linking with legacy_stdio_definitions.lib, which we do using this pragma.
+// Your own project should not be affected, as you are likely to link with a newer binary of GLFW that is adequate for your version of Visual Studio.
+#if defined(_MSC_VER) && (_MSC_VER >= 1900) && !defined(IMGUI_DISABLE_WIN32_FUNCTIONS)
+#pragma comment(lib, "legacy_stdio_definitions")
+#endif
+
+// #define APP_USE_UNLIMITED_FRAME_RATE
+#ifdef _DEBUG
+#define APP_USE_VULKAN_DEBUG_REPORT
+#endif
+
+// Data
+static VkAllocationCallbacks   *g_Allocator      = nullptr;
+static VkInstance               g_Instance       = VK_NULL_HANDLE;
+static VkPhysicalDevice         g_PhysicalDevice = VK_NULL_HANDLE;
+static VkDevice                 g_Device         = VK_NULL_HANDLE;
+static uint32_t                 g_QueueFamily    = (uint32_t)-1;
+static VkQueue                  g_Queue          = VK_NULL_HANDLE;
+static VkDebugReportCallbackEXT g_DebugReport    = VK_NULL_HANDLE;
+static VkPipelineCache          g_PipelineCache  = VK_NULL_HANDLE;
+static VkDescriptorPool         g_DescriptorPool = VK_NULL_HANDLE;
+
+static ImGui_ImplVulkanH_Window g_MainWindowData;
+static int                      g_MinImageCount    = 2;
+static bool                     g_SwapChainRebuild = false;
+
+static void glfw_error_callback(int error, const char *description)
+{
+    fprintf(stderr, "GLFW Error %d: %s\n", error, description);
+}
+static void check_vk_result(VkResult err)
+{
+    if (err == 0)
+        return;
+    fprintf(stderr, "[vulkan] Error: VkResult = %d\n", err);
+    if (err < 0)
+        abort();
+}
+
+#ifdef APP_USE_VULKAN_DEBUG_REPORT
+static VKAPI_ATTR VkBool32 VKAPI_CALL debug_report(VkDebugReportFlagsEXT flags, VkDebugReportObjectTypeEXT objectType, uint64_t object, size_t location, int32_t messageCode, const char *pLayerPrefix, const char *pMessage, void *pUserData)
+{
+    (void)flags;
+    (void)object;
+    (void)location;
+    (void)messageCode;
+    (void)pUserData;
+    (void)pLayerPrefix;  // Unused arguments
+    fprintf(stderr, "[vulkan] Debug report from ObjectType: %i\nMessage: %s\n\n", objectType, pMessage);
+    return VK_FALSE;
+}
+#endif  // APP_USE_VULKAN_DEBUG_REPORT
+
+static bool IsExtensionAvailable(const ImVector<VkExtensionProperties> &properties, const char *extension)
+{
+    for (const VkExtensionProperties &p : properties)
+        if (strcmp(p.extensionName, extension) == 0)
+            return true;
+    return false;
+}
+
+static VkPhysicalDevice SetupVulkan_SelectPhysicalDevice()
+{
+    uint32_t gpu_count;
+    VkResult err = vkEnumeratePhysicalDevices(g_Instance, &gpu_count, nullptr);
+    check_vk_result(err);
+    IM_ASSERT(gpu_count > 0);
+
+    ImVector<VkPhysicalDevice> gpus;
+    gpus.resize(gpu_count);
+    err = vkEnumeratePhysicalDevices(g_Instance, &gpu_count, gpus.Data);
+    check_vk_result(err);
+
+    // If a number >1 of GPUs got reported, find discrete GPU if present, or use first one available. This covers
+    // most common cases (multi-gpu/integrated+dedicated graphics). Handling more complicated setups (multiple
+    // dedicated GPUs) is out of scope of this sample.
+    for (VkPhysicalDevice &device : gpus)
+    {
+        VkPhysicalDeviceProperties properties;
+        vkGetPhysicalDeviceProperties(device, &properties);
+        if (properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU)
+            return device;
+    }
+
+    // Use first GPU (Integrated) is a Discrete one is not available.
+    if (gpu_count > 0)
+        return gpus[0];
+    return VK_NULL_HANDLE;
+}
+
+static void SetupVulkan(ImVector<const char *> instance_extensions)
+{
+    VkResult err;
+#ifdef IMGUI_IMPL_VULKAN_USE_VOLK
+    volkInitialize();
+#endif
+
+    // Create Vulkan Instance
+    {
+        VkInstanceCreateInfo create_info = {};
+        create_info.sType                = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
+
+        // Enumerate available extensions
+        uint32_t                        properties_count;
+        ImVector<VkExtensionProperties> properties;
+        vkEnumerateInstanceExtensionProperties(nullptr, &properties_count, nullptr);
+        properties.resize(properties_count);
+        err = vkEnumerateInstanceExtensionProperties(nullptr, &properties_count, properties.Data);
+        check_vk_result(err);
+
+        // Enable required extensions
+        if (IsExtensionAvailable(properties, VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME))
+            instance_extensions.push_back(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME);
+#ifdef VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME
+        if (IsExtensionAvailable(properties, VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME))
+        {
+            instance_extensions.push_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME);
+            create_info.flags |= VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR;
+        }
+#endif
+
+        // Enabling validation layers
+#ifdef APP_USE_VULKAN_DEBUG_REPORT
+        const char *layers[]            = {"VK_LAYER_KHRONOS_validation"};
+        create_info.enabledLayerCount   = 1;
+        create_info.ppEnabledLayerNames = layers;
+        instance_extensions.push_back("VK_EXT_debug_report");
+#endif
+
+        // Create Vulkan Instance
+        create_info.enabledExtensionCount   = (uint32_t)instance_extensions.Size;
+        create_info.ppEnabledExtensionNames = instance_extensions.Data;
+        err                                 = vkCreateInstance(&create_info, g_Allocator, &g_Instance);
+        check_vk_result(err);
+#ifdef IMGUI_IMPL_VULKAN_USE_VOLK
+        volkLoadInstance(g_Instance);
+#endif
+
+        // Setup the debug report callback
+#ifdef APP_USE_VULKAN_DEBUG_REPORT
+        auto f_vkCreateDebugReportCallbackEXT = (PFN_vkCreateDebugReportCallbackEXT)vkGetInstanceProcAddr(g_Instance, "vkCreateDebugReportCallbackEXT");
+        IM_ASSERT(f_vkCreateDebugReportCallbackEXT != nullptr);
+        VkDebugReportCallbackCreateInfoEXT debug_report_ci = {};
+        debug_report_ci.sType                              = VK_STRUCTURE_TYPE_DEBUG_REPORT_CALLBACK_CREATE_INFO_EXT;
+        debug_report_ci.flags                              = VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT | VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT;
+        debug_report_ci.pfnCallback                        = debug_report;
+        debug_report_ci.pUserData                          = nullptr;
+        err                                                = f_vkCreateDebugReportCallbackEXT(g_Instance, &debug_report_ci, g_Allocator, &g_DebugReport);
+        check_vk_result(err);
+#endif
+    }
+
+    // Select Physical Device (GPU)
+    g_PhysicalDevice = SetupVulkan_SelectPhysicalDevice();
+
+    // Select graphics queue family
+    {
+        uint32_t count;
+        vkGetPhysicalDeviceQueueFamilyProperties(g_PhysicalDevice, &count, nullptr);
+        VkQueueFamilyProperties *queues = (VkQueueFamilyProperties *)malloc(sizeof(VkQueueFamilyProperties) * count);
+        vkGetPhysicalDeviceQueueFamilyProperties(g_PhysicalDevice, &count, queues);
+        for (uint32_t i = 0; i < count; i++)
+            if (queues[i].queueFlags & VK_QUEUE_GRAPHICS_BIT)
+            {
+                g_QueueFamily = i;
+                break;
+            }
+        free(queues);
+        IM_ASSERT(g_QueueFamily != (uint32_t)-1);
+    }
+
+    // Create Logical Device (with 1 queue)
+    {
+        ImVector<const char *> device_extensions;
+        device_extensions.push_back("VK_KHR_swapchain");
+
+        // Enumerate physical device extension
+        uint32_t                        properties_count;
+        ImVector<VkExtensionProperties> properties;
+        vkEnumerateDeviceExtensionProperties(g_PhysicalDevice, nullptr, &properties_count, nullptr);
+        properties.resize(properties_count);
+        vkEnumerateDeviceExtensionProperties(g_PhysicalDevice, nullptr, &properties_count, properties.Data);
+#ifdef VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME
+        if (IsExtensionAvailable(properties, VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME))
+            device_extensions.push_back(VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME);
+#endif
+
+        const float             queue_priority[] = {1.0f};
+        VkDeviceQueueCreateInfo queue_info[1]    = {};
+        queue_info[0].sType                      = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
+        queue_info[0].queueFamilyIndex           = g_QueueFamily;
+        queue_info[0].queueCount                 = 1;
+        queue_info[0].pQueuePriorities           = queue_priority;
+        VkDeviceCreateInfo create_info           = {};
+        create_info.sType                        = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
+        create_info.queueCreateInfoCount         = sizeof(queue_info) / sizeof(queue_info[0]);
+        create_info.pQueueCreateInfos            = queue_info;
+        create_info.enabledExtensionCount        = (uint32_t)device_extensions.Size;
+        create_info.ppEnabledExtensionNames      = device_extensions.Data;
+        err                                      = vkCreateDevice(g_PhysicalDevice, &create_info, g_Allocator, &g_Device);
+        check_vk_result(err);
+        vkGetDeviceQueue(g_Device, g_QueueFamily, 0, &g_Queue);
+    }
+
+    // Create Descriptor Pool
+    // The example only requires a single combined image sampler descriptor for the font image and only uses one descriptor set (for that)
+    // If you wish to load e.g. additional textures you may need to alter pools sizes.
+    {
+        VkDescriptorPoolSize pool_sizes[] =
+            {
+                {VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1},
+            };
+        VkDescriptorPoolCreateInfo pool_info = {};
+        pool_info.sType                      = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
+        pool_info.flags                      = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT;
+        pool_info.maxSets                    = 1;
+        pool_info.poolSizeCount              = (uint32_t)IM_ARRAYSIZE(pool_sizes);
+        pool_info.pPoolSizes                 = pool_sizes;
+        err                                  = vkCreateDescriptorPool(g_Device, &pool_info, g_Allocator, &g_DescriptorPool);
+        check_vk_result(err);
+    }
+}
+
+// All the ImGui_ImplVulkanH_XXX structures/functions are optional helpers used by the demo.
+// Your real engine/app may not use them.
+static void SetupVulkanWindow(ImGui_ImplVulkanH_Window *wd, VkSurfaceKHR surface, int width, int height)
+{
+    wd->Surface = surface;
+
+    // Check for WSI support
+    VkBool32 res;
+    vkGetPhysicalDeviceSurfaceSupportKHR(g_PhysicalDevice, g_QueueFamily, wd->Surface, &res);
+    if (res != VK_TRUE)
+    {
+        fprintf(stderr, "Error no WSI support on physical device 0\n");
+        exit(-1);
+    }
+
+    // Select Surface Format
+    const VkFormat        requestSurfaceImageFormat[] = {VK_FORMAT_B8G8R8A8_UNORM, VK_FORMAT_R8G8B8A8_UNORM, VK_FORMAT_B8G8R8_UNORM, VK_FORMAT_R8G8B8_UNORM};
+    const VkColorSpaceKHR requestSurfaceColorSpace    = VK_COLORSPACE_SRGB_NONLINEAR_KHR;
+    wd->SurfaceFormat                                 = ImGui_ImplVulkanH_SelectSurfaceFormat(g_PhysicalDevice, wd->Surface, requestSurfaceImageFormat, (size_t)IM_ARRAYSIZE(requestSurfaceImageFormat), requestSurfaceColorSpace);
+
+    // Select Present Mode
+#ifdef APP_USE_UNLIMITED_FRAME_RATE
+    VkPresentModeKHR present_modes[] = {VK_PRESENT_MODE_MAILBOX_KHR, VK_PRESENT_MODE_IMMEDIATE_KHR, VK_PRESENT_MODE_FIFO_KHR};
+#else
+    VkPresentModeKHR present_modes[] = {VK_PRESENT_MODE_FIFO_KHR};
+#endif
+    wd->PresentMode = ImGui_ImplVulkanH_SelectPresentMode(g_PhysicalDevice, wd->Surface, &present_modes[0], IM_ARRAYSIZE(present_modes));
+    // printf("[vulkan] Selected PresentMode = %d\n", wd->PresentMode);
+
+    // Create SwapChain, RenderPass, Framebuffer, etc.
+    IM_ASSERT(g_MinImageCount >= 2);
+    ImGui_ImplVulkanH_CreateOrResizeWindow(g_Instance, g_PhysicalDevice, g_Device, wd, g_QueueFamily, g_Allocator, width, height, g_MinImageCount);
+}
+
+static void CleanupVulkan()
+{
+    vkDestroyDescriptorPool(g_Device, g_DescriptorPool, g_Allocator);
+
+#ifdef APP_USE_VULKAN_DEBUG_REPORT
+    // Remove the debug report callback
+    auto f_vkDestroyDebugReportCallbackEXT = (PFN_vkDestroyDebugReportCallbackEXT)vkGetInstanceProcAddr(g_Instance, "vkDestroyDebugReportCallbackEXT");
+    f_vkDestroyDebugReportCallbackEXT(g_Instance, g_DebugReport, g_Allocator);
+#endif  // APP_USE_VULKAN_DEBUG_REPORT
+
+    vkDestroyDevice(g_Device, g_Allocator);
+    vkDestroyInstance(g_Instance, g_Allocator);
+}
+
+static void CleanupVulkanWindow()
+{
+    ImGui_ImplVulkanH_DestroyWindow(g_Instance, g_Device, &g_MainWindowData, g_Allocator);
+}
+
+static void FrameRender(ImGui_ImplVulkanH_Window *wd, ImDrawData *draw_data)
+{
+    VkResult err;
+
+    VkSemaphore image_acquired_semaphore  = wd->FrameSemaphores[wd->SemaphoreIndex].ImageAcquiredSemaphore;
+    VkSemaphore render_complete_semaphore = wd->FrameSemaphores[wd->SemaphoreIndex].RenderCompleteSemaphore;
+    err                                   = vkAcquireNextImageKHR(g_Device, wd->Swapchain, UINT64_MAX, image_acquired_semaphore, VK_NULL_HANDLE, &wd->FrameIndex);
+    if (err == VK_ERROR_OUT_OF_DATE_KHR || err == VK_SUBOPTIMAL_KHR)
+    {
+        g_SwapChainRebuild = true;
+        return;
+    }
+    check_vk_result(err);
+
+    ImGui_ImplVulkanH_Frame *fd = &wd->Frames[wd->FrameIndex];
+    {
+        err = vkWaitForFences(g_Device, 1, &fd->Fence, VK_TRUE, UINT64_MAX);  // wait indefinitely instead of periodically checking
+        check_vk_result(err);
+
+        err = vkResetFences(g_Device, 1, &fd->Fence);
+        check_vk_result(err);
+    }
+    {
+        err = vkResetCommandPool(g_Device, fd->CommandPool, 0);
+        check_vk_result(err);
+        VkCommandBufferBeginInfo info = {};
+        info.sType                    = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
+        info.flags |= VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
+        err = vkBeginCommandBuffer(fd->CommandBuffer, &info);
+        check_vk_result(err);
+    }
+    {
+        VkRenderPassBeginInfo info    = {};
+        info.sType                    = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
+        info.renderPass               = wd->RenderPass;
+        info.framebuffer              = fd->Framebuffer;
+        info.renderArea.extent.width  = wd->Width;
+        info.renderArea.extent.height = wd->Height;
+        info.clearValueCount          = 1;
+        info.pClearValues             = &wd->ClearValue;
+        vkCmdBeginRenderPass(fd->CommandBuffer, &info, VK_SUBPASS_CONTENTS_INLINE);
+    }
+
+    // Record dear imgui primitives into command buffer
+    ImGui_ImplVulkan_RenderDrawData(draw_data, fd->CommandBuffer);
+
+    // Submit command buffer
+    vkCmdEndRenderPass(fd->CommandBuffer);
+    {
+        VkPipelineStageFlags wait_stage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+        VkSubmitInfo         info       = {};
+        info.sType                      = VK_STRUCTURE_TYPE_SUBMIT_INFO;
+        info.waitSemaphoreCount         = 1;
+        info.pWaitSemaphores            = &image_acquired_semaphore;
+        info.pWaitDstStageMask          = &wait_stage;
+        info.commandBufferCount         = 1;
+        info.pCommandBuffers            = &fd->CommandBuffer;
+        info.signalSemaphoreCount       = 1;
+        info.pSignalSemaphores          = &render_complete_semaphore;
+
+        err = vkEndCommandBuffer(fd->CommandBuffer);
+        check_vk_result(err);
+        err = vkQueueSubmit(g_Queue, 1, &info, fd->Fence);
+        check_vk_result(err);
+    }
+}
+
+static void
+FramePresent(ImGui_ImplVulkanH_Window *wd)
+{
+    if (g_SwapChainRebuild)
+        return;
+    VkSemaphore      render_complete_semaphore = wd->FrameSemaphores[wd->SemaphoreIndex].RenderCompleteSemaphore;
+    VkPresentInfoKHR info                      = {};
+    info.sType                                 = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
+    info.waitSemaphoreCount                    = 1;
+    info.pWaitSemaphores                       = &render_complete_semaphore;
+    info.swapchainCount                        = 1;
+    info.pSwapchains                           = &wd->Swapchain;
+    info.pImageIndices                         = &wd->FrameIndex;
+    VkResult err                               = vkQueuePresentKHR(g_Queue, &info);
+    if (err == VK_ERROR_OUT_OF_DATE_KHR || err == VK_SUBOPTIMAL_KHR)
+    {
+        g_SwapChainRebuild = true;
+        return;
+    }
+    check_vk_result(err);
+    wd->SemaphoreIndex = (wd->SemaphoreIndex + 1) % wd->SemaphoreCount;  // Now we can use the next set of semaphores
+}
+
+// Main code
+int main(int, char **)
+{
+    glfwSetErrorCallback(glfw_error_callback);
+    if (!glfwInit())
+        return 1;
+
+    // 设置 offscreen context 的标志位
+    glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);
+
+    // Create window with Vulkan context
+    glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
+    GLFWwindow *window = glfwCreateWindow(1280, 720, "atom", nullptr, nullptr);
+    if (!glfwVulkanSupported())
+    {
+        printf("GLFW: Vulkan Not Supported\n");
+        return 1;
+    }
+
+    ImVector<const char *> extensions;
+    uint32_t               extensions_count = 0;
+    const char           **glfw_extensions  = glfwGetRequiredInstanceExtensions(&extensions_count);
+    for (uint32_t i = 0; i < extensions_count; i++)
+        extensions.push_back(glfw_extensions[i]);
+    SetupVulkan(extensions);
+
+    // Create Window Surface
+    VkSurfaceKHR surface;
+    VkResult     err = glfwCreateWindowSurface(g_Instance, window, g_Allocator, &surface);
+    check_vk_result(err);
+
+    // Create Framebuffers
+    int w, h;
+    glfwGetFramebufferSize(window, &w, &h);
+    ImGui_ImplVulkanH_Window *wd = &g_MainWindowData;
+    SetupVulkanWindow(wd, surface, w, h);
+
+    // Setup Dear ImGui context
+    IMGUI_CHECKVERSION();
+    ImGui::CreateContext();
+    ImGuiIO &io = ImGui::GetIO();
+    (void)io;
+    io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;  // Enable Keyboard Controls
+    io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad;   // Enable Gamepad Controls
+    io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;      // Enable Docking
+    io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable;    // Enable Multi-Viewport / Platform Windows
+    io.ConfigViewportsNoAutoMerge = true;
+    // io.ConfigViewportsNoTaskBarIcon = true;
+
+    // Setup Dear ImGui style
+    ImGui::StyleColorsDark();
+    // ImGui::StyleColorsLight();
+
+    bool window_is_open = true;
+
+    // When viewports are enabled we tweak WindowRounding/WindowBg so platform windows can look identical to regular ones.
+    ImGuiStyle &style = ImGui::GetStyle();
+    InitStyle(style);
+
+    // Setup Platform/Renderer backends
+    ImGui_ImplGlfw_InitForVulkan(window, true);
+    ImGui_ImplVulkan_InitInfo init_info = {};
+    init_info.Instance                  = g_Instance;
+    init_info.PhysicalDevice            = g_PhysicalDevice;
+    init_info.Device                    = g_Device;
+    init_info.QueueFamily               = g_QueueFamily;
+    init_info.Queue                     = g_Queue;
+    init_info.PipelineCache             = g_PipelineCache;
+    init_info.DescriptorPool            = g_DescriptorPool;
+    init_info.RenderPass                = wd->RenderPass;
+    init_info.Subpass                   = 0;
+    init_info.MinImageCount             = g_MinImageCount;
+    init_info.ImageCount                = wd->ImageCount;
+    init_info.MSAASamples               = VK_SAMPLE_COUNT_1_BIT;
+    init_info.Allocator                 = g_Allocator;
+    init_info.CheckVkResultFn           = check_vk_result;
+    ImGui_ImplVulkan_Init(&init_info);
+
+    // Load Fonts
+    io.Fonts->AddFontFromFileTTF("../HYWenHei-85W.ttf", 36.0, nullptr, io.Fonts->GetGlyphRangesChineseFull());
+
+    // Main loop
+    while (window_is_open)
+    {
+        // Poll and handle events (inputs, window resize, etc.)
+        // You can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if dear imgui wants to use your inputs.
+        // - When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application, or clear/overwrite your copy of the mouse data.
+        // - When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to your main application, or clear/overwrite your copy of the keyboard data.
+        // Generally you may always pass all inputs to dear imgui, and hide them from your application based on those two flags.
+        glfwPollEvents();
+
+        // Resize swap chain?
+        int fb_width, fb_height;
+        glfwGetFramebufferSize(window, &fb_width, &fb_height);
+        if (fb_width > 0 && fb_height > 0 && (g_SwapChainRebuild || g_MainWindowData.Width != fb_width || g_MainWindowData.Height != fb_height))
+        {
+            ImGui_ImplVulkan_SetMinImageCount(g_MinImageCount);
+            ImGui_ImplVulkanH_CreateOrResizeWindow(g_Instance, g_PhysicalDevice, g_Device, &g_MainWindowData, g_QueueFamily, g_Allocator, fb_width, fb_height, g_MinImageCount);
+            g_MainWindowData.FrameIndex = 0;
+            g_SwapChainRebuild          = false;
+        }
+        if (glfwGetWindowAttrib(window, GLFW_ICONIFIED) != 0)
+        {
+            ImGui_ImplGlfw_Sleep(10);
+            continue;
+        }
+
+        // Start the Dear ImGui frame
+        ImGui_ImplVulkan_NewFrame();
+        ImGui_ImplGlfw_NewFrame();
+        ImGui::NewFrame();
+        // ImGui::DockSpaceOverViewport();
+
+        ImGuiWindowFlags window_flags = 0;
+        window_flags |= ImGuiWindowFlags_NoTitleBar;
+
+        // 获取主显示器
+        GLFWmonitor *primary_monitor = glfwGetPrimaryMonitor();
+        if (primary_monitor)
+        {
+            // 获取显示器的工作区大小
+            int workarea_x, workarea_y, workarea_width, workarea_height;
+            glfwGetMonitorWorkarea(primary_monitor, &workarea_x, &workarea_y, &workarea_width, &workarea_height);
+
+            // 获取显示器的分辨率
+            const GLFWvidmode *mode = glfwGetVideoMode(primary_monitor);
+
+            // 设置窗口的最小和最大大小
+            ImVec2 min_window_size = ImVec2(400, 300);                                       // 最小窗口大小
+            ImVec2 max_window_size = ImVec2(workarea_width * 0.8f, workarea_height * 0.8f);  // 最大窗口大小
+            ImGui::SetNextWindowSizeConstraints(min_window_size, max_window_size);
+        }
+
+        // 2. Show a simple window that we create ourselves. We use a Begin/End pair to create a named window.
+        {
+            ImGui::Begin("atom", nullptr, window_flags);  // Create a window called "Hello, world!" and append into it.
+            auto str = "Atom";
+
+            DrawTitleBar(str, window_is_open);
+
+            static float left_pane_width = 200.0f;                                             // 初始左侧窗口宽度
+            float        min_pane_width  = 100.0f;                                             // 最小窗口宽度
+            float        max_pane_width  = ImGui::GetContentRegionAvail().x - min_pane_width;  // 最大窗口宽度
+
+            static float right_pane_height = 200.0f;                                              // 初始右侧下窗口高度
+            float        min_pane_height   = 100.0f;                                              // 最小窗口高度
+            float        max_pane_height   = ImGui::GetContentRegionAvail().y - min_pane_height;  // 最大窗口高度
+
+            ImGui::BeginGroup();  // 开始一个组
+            // 左侧的可滑动列表
+            ImGui::BeginChild("Left Pane", ImVec2(left_pane_width, 0), true, ImGuiWindowFlags_NoMove);
+            static int                      selected_left = 0;
+            static std::vector<std::string> items_left    = {"Item 1", "Item 2", "Item 3", "Item 4", "Item 5"};
+            for (int i = 0; i < items_left.size(); i++)
+            {
+                if (ImGui::Selectable(items_left[i].c_str(), selected_left == i))
+                    selected_left = i;
+            }
+            // 添加一个加号按钮
+            if (ImGui::Button("+", ImVec2(ImGui::GetContentRegionAvail().x, 0)))
+            {
+                items_left.push_back("New Item " + std::to_string(items_left.size() + 1));
+            }
+            ImGui::EndChild();
+            ImGui::EndGroup();  // 结束组
+
+            ImGui::SameLine();
+
+            ImGui::BeginGroup();
+            // 分割器
+            ImGui::InvisibleButton("splitter_vertical", ImVec2(1.0f, -1.0f));
+            if (ImGui::IsItemHovered())
+            {
+                ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW);  // 更改鼠标光标
+            }
+            if (ImGui::IsItemActive())
+            {
+                left_pane_width += ImGui::GetIO().MouseDelta.x;
+                if (left_pane_width < min_pane_width)
+                    left_pane_width = min_pane_width;
+                if (left_pane_width > max_pane_width)
+                    left_pane_width = max_pane_width;
+            }
+            ImGui::EndGroup();
+
+            ImGui::SameLine();
+
+            ImGui::BeginGroup();  // 开始一个组
+            // 右侧的可滑动列表
+            auto temp = ImGui::GetContentRegionAvail().y - right_pane_height - 12.0f;
+            ImGui::BeginChild("Right Pane", ImVec2(0, temp), true, ImGuiWindowFlags_NoMove);
+            static int               selected_right = 0;
+            std::vector<std::string> items_right    = {"Item A", "Item B", "Item C", "Item D", "Item E"};
+            for (int i = 0; i < items_right.size(); i++)
+            {
+                if (ImGui::Selectable(items_right[i].c_str(), selected_right == i, ImGuiSelectableFlags_AllowDoubleClick))
+                    if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(0))
+                    {
+                        // Do stuff on Selectable() double click.
+                        selected_right = i;
+                        ImGui::SetClipboardText(items_right[i].c_str());
+                        selected_right = -1;
+                    }
+            }
+            ImGui::EndChild();
+            // 添加一个输入框
+
+            // 分割器
+            ImGui::InvisibleButton("splitter_horizontal", ImVec2(-1.0f, 4.0f));
+            if (ImGui::IsItemHovered())
+            {
+                ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeNS);  // 更改鼠标光标
+            }
+            if (ImGui::IsItemActive())
+            {
+                right_pane_height -= ImGui::GetIO().MouseDelta.y;
+                if (right_pane_height < min_pane_height)
+                    right_pane_height = min_pane_height;
+                if (right_pane_height > max_pane_height)
+                    right_pane_height = max_pane_height;
+            }
+
+            // 设置输入框高度
+            ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(4.0f, right_pane_height / 2.0f - ImGui::GetFontSize() / 2.0f));
+
+            // 添加一个输入框
+            static char input_text[1024] = "";
+            ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);  // 设置输入框宽度
+            ImGui::InputText("##input", input_text, IM_ARRAYSIZE(input_text), ImGuiInputTextFlags_EnterReturnsTrue);
+            ImGui::PopStyleVar();  // 恢复默认样式
+            ImGui::EndGroup();     // 结束组
+
+            ImGui::End();
+        }
+
+        // Rendering
+        ImGui::Render();
+        ImDrawData *main_draw_data    = ImGui::GetDrawData();
+        const bool  main_is_minimized = (main_draw_data->DisplaySize.x <= 0.0f || main_draw_data->DisplaySize.y <= 0.0f);
+        if (!main_is_minimized)
+            FrameRender(wd, main_draw_data);
+
+        // Update and Render additional Platform Windows
+        if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable)
+        {
+            ImGui::UpdatePlatformWindows();
+            ImGui::RenderPlatformWindowsDefault();
+        }
+
+        // Present Main Platform Window
+        if (!main_is_minimized)
+            FramePresent(wd);
+    }
+
+    // Cleanup
+    err = vkDeviceWaitIdle(g_Device);
+    check_vk_result(err);
+    ImGui_ImplVulkan_Shutdown();
+    ImGui_ImplGlfw_Shutdown();
+    ImGui::DestroyContext();
+
+    CleanupVulkanWindow();
+    CleanupVulkan();
+
+    glfwDestroyWindow(window);
+    glfwTerminate();
+
+    return 0;
+}
diff --git a/readme.md b/readme.md
new file mode 100644
index 0000000..1d2f8d7
--- /dev/null
+++ b/readme.md
@@ -0,0 +1,5 @@
+'''
+mkdir build && cd build
+cmake ..
+make
+'''
\ No newline at end of file