ImGui 1.92.6 + DirectX 12 ile Borderless UI: Visual Studio 2022’de Adım Adım Kurulum

Burada hedef net: Windows’un klasik başlık çubuğu ve çerçevesi olmadan (frameless/borderless) bir masaüstü pencere açıp, arayüzü ImGui 1.92.6 ile çizmek ve pencerenin kapatma / küçültme / büyütme işlemlerini de yine ImGui üzerindeki butonlarla yönetmek. Rendering tarafında DirectX 12 kullanıyoruz ve her adımı Visual Studio 2022 akışına göre ilerletiyoruz.

Hedef mimari: “frameless + DX12 + ImGui”

  • Pencere WS_POPUP ile açılır: Windows çerçevesi yok, başlık yok.
  • Minimize/Maximize/Close butonları ImGui üzerinde çizilir ve Win32’e komut gönderilir.
  • Taşıma (drag) ve boyutlandırma (resize) işlemleri ImGui “invisible” alanlarıyla yapılır.
  • DX12 swapchain resize, tek güvenli noktadan ve GPU senkronizasyonu ile yapılır (ResizeBuffers güvenli).

Visual Studio 2022 proje kurulumu

  1. Yeni proje: “Windows Desktop Application” ya da “Empty Project (C++)” oluşturulur.
  2. Platform: x64 seçilir (DX12 için pratikte standart).
  3. Entry point: wWinMain kullanacağımız için Subsystem “Windows” mantığıyla ilerlenir.
  4. main.cpp: tek dosyadan ilerlemek için projeye main.cpp eklenir.

ImGui 1.92.6 dosyalarını projeye ekleme

ImGui’yi “kütüphane” gibi değil, çoğunlukla kaynak kod olarak projeye eklemek en rahat yöntemdir. ImGui 1.92.6 klasöründen aşağıdaki dosyaları projeye dahil ederiz:

Projeye eklenecek temel ImGui dosyaları
  • imgui.cpp, imgui_draw.cpp, imgui_tables.cpp, imgui_widgets.cpp
  • (İsteğe bağlı) imgui_demo.cpp
  • Backend: backends/imgui_impl_win32.cpp, backends/imgui_impl_dx12.cpp
  • Header: imgui.h, imgui_internal.h, backend header’ları

Ardından include path için: Project Properties → C/C++ → General → Additional Include Directories içine ImGui kök klasörü ve backends klasörü eklenir.

Linker ayarları: d3d12.lib, dxgi.lib, d3dcompiler.lib

DX12 ve DXGI kullandığımız için linker tarafında kütüphaneleri eklemek gerekir. Bunu Visual Studio üzerinden net şekilde yaparız:

  1. Project Properties açılır.
  2. Linker → Input → Additional Dependencies alanına şunlar eklenir: d3d12.lib; dxgi.lib; d3dcompiler.lib;
  3. Apply ve OK ile kaydedilir.
Kısa not

Kod içinde #pragma comment(lib, ...) ile de link yapılabilir; ancak projeyi taşırken/ayarlarken en temiz yöntem “Additional Dependencies” üzerinden yönetmektir.

Borderless pencere: WS_POPUP

Windows çerçevesini kaldırmak için pencereyi WS_POPUP stiliyle açarız. Bu stil; başlık çubuğunu, kenarlıkları ve standart kontrol butonlarını (min/max/close) devre dışı bırakır. Bu noktadan sonra pencereyi taşımak ve boyutlandırmak için kendi UI katmanımızı (ImGui) kullanmamız gerekir.

DWORD style = WS_POPUP;          // Çerçevesiz pencere
DWORD exStyle = WS_EX_APPWINDOW; // Taskbar'da görünsün

g_hWnd = CreateWindowExW(exStyle, wc.lpszClassName, L"ImGui DX12 Borderless",
    style, 100, 100, 854, 480,
    nullptr, nullptr, wc.hInstance, nullptr);

DX12 device + swapchain ayağa kaldırma

DX12 tarafında temel parçalar şunlardır: Device, CommandQueue, CommandAllocator, CommandList, Fence ve SwapChain. Swapchain için “flip model” yaklaşımıyla DXGI_SWAP_EFFECT_FLIP_DISCARD kullanırız.

Neden flip-discard?

Modern Windows’ta flip model swapchain, daha düşük latency ve daha iyi present davranışı sağlar. Ayrıca ImGui + DX12 örneklerinin standart yaklaşımıdır.

ImGui DX12 backend (1.92+) InitInfo ve SRV allocator

ImGui 1.92+ ile DX12 backend tarafında ImGui_ImplDX12_InitInfo üzerinden daha net bir init akışı kullanırız. Burada kritik konu, shader-visible bir CBV/SRV/UAV descriptor heap (SRV heap) yönetimidir. Kodda bunun için küçük bir SrvAllocator yapısı bulunur:

SRV heap neden gerekiyor?

  • ImGui, font atlası ve UI texture’ları için GPU tarafında SRV descriptor ister.
  • Heap shader-visible olmalı (D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE).
  • Alloc/Free fonksiyonları ile ImGui’ye SRV alanı sağlanır.

ImGui ile başlık çubuğu: drag + minimize/maximize/close

Windows çerçevesi olmadığından, başlık çubuğunu ImGui ile çizeriz. Burada iki kritik parça vardır:

  • Drag alanı: InvisibleButton ile tıklanınca pencereyi sürükleme moduna alır.
  • Butonlar: “-”, “□/❐”, “X” ile minimize / maximize-restore / close komutları üretilir.
// Title bar içinde:
ImGui::InvisibleButton("##drag_area", ImVec2(xButtons - xMin, barH));
if (ImGui::IsItemHovered() && ImGui::IsMouseClicked(ImGuiMouseButton_Left))
    BeginDragWindow();

// Butonlar:
if (ImGui::Button("-", ImVec2(btnW, barH - 6))) g_PendingWinCmd = PendingWinCmd::Minimize;
ImGui::SameLine();
if (ImGui::Button(isMax ? "❐" : "□", ImVec2(btnW, barH - 6))) g_PendingWinCmd = PendingWinCmd::ToggleMaxRestore;
ImGui::SameLine();
if (ImGui::Button("X", ImVec2(btnW, barH - 6))) g_PendingWinCmd = PendingWinCmd::Close;

Butonlara basınca pencereyi anında oynatmak yerine bir “pending command” üretiriz (g_PendingWinCmd). Bu komutu frame başında güvenli şekilde Win32’ye uygularız: ShowWindow(SW_MINIMIZE/SW_MAXIMIZE/SW_RESTORE) veya PostMessage(WM_CLOSE). Böylece ImGui input state’i ile Win32 state’i birbirine girmez.

ImGui ile resize (sağ-alt grip) ve güvenli swapchain resize

Borderless pencerede resize için sağ-alt köşeye bir “grip” çizeriz. Bu grip, tıklanınca resize moduna geçer; mouse hareketine göre SetWindowPos ile pencere boyutu güncellenir.

En kritik nokta: SwapChain ResizeBuffers

Pencere boyutu değişince swapchain’in backbuffer boyutları da değişmelidir. DX12’de ResizeBuffers çağrısı “yanlış yerde” yapılırsa device removed/reset veya crash görülebilir. Bu yüzden kodda resize tek bir güvenli noktadan yapılır: frame başında TryPerformSwapResize(). Üstelik GPU idle beklenir (WaitForGpuIdle()) ve throttle uygulanır.

	    // Resize isteğini biriktir:
RequestSwapResize((UINT)newW, (UINT)newH, false);

// Frame başında güvenli resize:
TryPerformSwapResize();

Render döngüsü: frame sync + present

Render akışında üç önemli güvenlik katmanı var:

  • Frame resources: her frame için allocator/fence takibi (WaitForNextFrameResources).
  • Resource barrier: present → render_target → present geçişleri.
  • Descriptor heap: ImGui çizimi için SRV heap set edilir.
Occluded (görünmez) durum

Present sonucu DXGI_STATUS_OCCLUDED dönerse pencere görünmüyor olabilir. Bu durumda render döngüsünü daha hafifletmek (sleep/throttle) istenebilir. Kod, occluded bilgisini takip edecek şekilde hazırlandı.

Tam main.cpp (kopyala-çalıştır)

main.cpp (ImGui 1.92.6 + DX12 + Borderless + ImGui Titlebar/Resize) — Tam Kod
// main.cpp
#define WIN32_LEAN_AND_MEAN
#define NOMINMAX
#include <windows.h>

#include <d3d12.h>
#include <dxgi1_4.h>
#include <wrl/client.h>

#include <cstdint>
#include <vector>
#include <string>
#include <stdexcept>
#include <algorithm>
#include <cmath>

#include "imgui.h"
#include "imgui_impl_win32.h"
#include "imgui_impl_dx12.h"
#include <imgui_internal.h>

using Microsoft::WRL::ComPtr;

#pragma comment(lib, "d3d12.lib")
#pragma comment(lib, "dxgi.lib")

// -----------------------------
static const int NUM_FRAMES_IN_FLIGHT = 3;
static const int NUM_BACK_BUFFERS = 3;
static const DXGI_FORMAT RTV_FORMAT = DXGI_FORMAT_R8G8B8A8_UNORM;

static HWND g_hWnd = nullptr;
static bool g_Running = true;

// -----------------------------
// Pending window commands
enum class PendingWinCmd { None, Minimize, ToggleMaxRestore, Close };
static PendingWinCmd g_PendingWinCmd = PendingWinCmd::None;

// -----------------------------
// Drag / Resize
static bool  g_Dragging = false;
static POINT g_DragMouseStart{};
static RECT  g_DragWindowStart{};

static bool  g_Resizing = false;
static POINT g_ResizeMouseStart{};
static RECT  g_ResizeWindowStart{};
static int   g_ResizeW0 = 0;
static int   g_ResizeH0 = 0;
static float g_ResizeAspect = 16.0f / 9.0f;

// -----------------------------
// SAFE swapchain resize request (tek noktadan)
static bool      g_SwapResizeRequested = false;
static bool      g_SwapResizeForceNow = false;
static UINT      g_SwapResizeW = 0;
static UINT      g_SwapResizeH = 0;
static UINT      g_SwapCurrentW = 0;
static UINT      g_SwapCurrentH = 0;
static ULONGLONG g_LastResizeTick = 0;
static const ULONGLONG kResizeThrottleMs = 33; // 30 fps (16 -> 60fps)
static bool      g_InSwapResize = false;

// -----------------------------
// DX12
struct FrameContext
{
    ComPtr<ID3D12CommandAllocator> CommandAllocator;
    UINT64 FenceValue = 0;
};

static FrameContext g_FrameCtx[NUM_FRAMES_IN_FLIGHT];
static UINT g_FrameIndex = 0;

static ComPtr<ID3D12Device>               g_Device;
static ComPtr<ID3D12CommandQueue>         g_CommandQueue;
static ComPtr<ID3D12GraphicsCommandList>  g_CommandList;
static ComPtr<IDXGISwapChain3>            g_SwapChain;

static ComPtr<ID3D12DescriptorHeap>       g_RtvHeap;
static UINT                               g_RtvDescriptorSize = 0;

static ComPtr<ID3D12Resource>             g_BackBuffer[NUM_BACK_BUFFERS];
static D3D12_CPU_DESCRIPTOR_HANDLE        g_BackBufferRtv[NUM_BACK_BUFFERS]{};

static ComPtr<ID3D12Fence>                g_Fence;
static HANDLE                             g_FenceEvent = nullptr;
static UINT64                             g_FenceLastSignaledValue = 0;

static bool                               g_SwapChainOccluded = false;

// -----------------------------
// ImGui SRV allocator (1.92+ InitInfo için)
struct SrvAllocator
{
    ComPtr<ID3D12DescriptorHeap> Heap;
    UINT DescriptorSize = 0;
    UINT Capacity = 0;
    UINT Next = 0;
    std::vector<UINT> FreeList;

    void Init(ID3D12Device* device, UINT capacity)
    {
        Capacity = capacity;
        Next = 0;
        FreeList.clear();

        D3D12_DESCRIPTOR_HEAP_DESC desc = {};
        desc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
        desc.NumDescriptors = capacity;
        desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
        desc.NodeMask = 1;

        if (FAILED(device->CreateDescriptorHeap(&desc, IID_PPV_ARGS(&Heap))))
            throw std::runtime_error("CreateDescriptorHeap(SRV) failed");

        DescriptorSize = device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
    }

    bool Alloc(D3D12_CPU_DESCRIPTOR_HANDLE* outCpu, D3D12_GPU_DESCRIPTOR_HANDLE* outGpu)
    {
        UINT idx;
        if (!FreeList.empty())
        {
            idx = FreeList.back();
            FreeList.pop_back();
        }
        else
        {
            if (Next >= Capacity) return false;
            idx = Next++;
        }

        D3D12_CPU_DESCRIPTOR_HANDLE cpu = Heap->GetCPUDescriptorHandleForHeapStart();
        D3D12_GPU_DESCRIPTOR_HANDLE gpu = Heap->GetGPU_DESCRIPTOR_HANDLE_FOR_HEAP_START();

        cpu.ptr += (SIZE_T)idx * DescriptorSize;
        gpu.ptr += (SIZE_T)idx * DescriptorSize;

        *outCpu = cpu;
        *outGpu = gpu;
        return true;
    }

    void Free(D3D12_CPU_DESCRIPTOR_HANDLE cpu, D3D12_GPU_DESCRIPTOR_HANDLE)
    {
        const SIZE_T base = Heap->GetCPUDescriptorHandleForHeapStart().ptr;
        const SIZE_T diff = cpu.ptr - base;
        const UINT idx = (UINT)(diff / DescriptorSize);
        if (idx < Capacity) FreeList.push_back(idx);
    }
};

static SrvAllocator g_SrvAllocator;

// -----------------------------
// Foreground / focus (donma/pasif input engeli için)
static void ForceForeground(HWND hwnd)
{
    if (!hwnd) return;

    HWND fg = GetForegroundWindow();
    DWORD fgThread = fg ? GetWindowThreadProcessId(fg, nullptr) : 0;
    DWORD thisThread = GetCurrentThreadId();

    if (fgThread && fgThread != thisThread)
    {
        AttachThreadInput(fgThread, thisThread, TRUE);
        SetForegroundWindow(hwnd);
        SetActiveWindow(hwnd);
        SetFocus(hwnd);
        BringWindowToTop(hwnd);
        AttachThreadInput(fgThread, thisThread, FALSE);
    }
    else
    {
        SetForegroundWindow(hwnd);
        SetActiveWindow(hwnd);
        SetFocus(hwnd);
        BringWindowToTop(hwnd);
    }
}

// -----------------------------
// Fence: GPU’yu tam idle yap (ResizeBuffers için en güvenlisi)
static void WaitForGpuIdle()
{
    const UINT64 fenceValue = ++g_FenceLastSignaledValue;
    g_CommandQueue->Signal(g_Fence.Get(), fenceValue);

    if (g_Fence->GetCompletedValue() < fenceValue)
    {
        g_Fence->SetEventOnCompletion(fenceValue, g_FenceEvent);
        WaitForSingleObject(g_FenceEvent, INFINITE);
    }
}

// Frame sync (normal render için)
static FrameContext* WaitForNextFrameResources()
{
    g_FrameIndex++;
    FrameContext* frameCtx = &g_FrameCtx[g_FrameIndex % NUM_FRAMES_IN_FLIGHT];

    UINT64 fenceValue = frameCtx->FenceValue;
    if (fenceValue != 0)
    {
        frameCtx->FenceValue = 0;
        if (g_Fence->GetCompletedValue() < fenceValue)
        {
            g_Fence->SetEventOnCompletion(fenceValue, g_FenceEvent);
            WaitForSingleObject(g_FenceEvent, INFINITE);
        }
    }
    return frameCtx;
}

static void SignalFenceForFrame()
{
    const UINT64 fenceValue = ++g_FenceLastSignaledValue;
    g_CommandQueue->Signal(g_Fence.Get(), fenceValue);
    g_FrameCtx[g_FrameIndex % NUM_FRAMES_IN_FLIGHT].FenceValue = fenceValue;
}

// -----------------------------
// RenderTargets
static void CleanupRenderTargets()
{
    for (UINT i = 0; i < NUM_BACK_BUFFERS; i++)
        g_BackBuffer[i].Reset();
}

static void CreateRenderTargets()
{
    D3D12_CPU_DESCRIPTOR_HANDLE rtvHandle = g_RtvHeap->GetCPUDescriptorHandleForHeapStart();
    for (UINT i = 0; i < NUM_BACK_BUFFERS; i++)
    {
        g_BackBufferRtv[i] = rtvHandle;

        ComPtr<ID3D12Resource> res;
        HRESULT hr = g_SwapChain->GetBuffer(i, IID_PPV_ARGS(&res));
        if (FAILED(hr))
            throw std::runtime_error("SwapChain->GetBuffer failed");

        g_BackBuffer[i] = res;
        g_Device->CreateRenderTargetView(g_BackBuffer[i].Get(), nullptr, rtvHandle);
        rtvHandle.ptr += g_RtvDescriptorSize;
    }
}

// -----------------------------
// SAFE swapchain resize request
static void RequestSwapResize(UINT w, UINT h, bool forceNow)
{
    if (w < 64 || h < 64) return;
    g_SwapResizeRequested = true;
    g_SwapResizeW = w;
    g_SwapResizeH = h;
    if (forceNow) g_SwapResizeForceNow = true;
}

// Tek güvenli yer: frame başında swapchain resize
static void TryPerformSwapResize()
{
    if (!g_SwapResizeRequested || g_InSwapResize) return;
    if (IsIconic(g_hWnd)) return;

    ULONGLONG now = GetTickCount64();
    if (!g_SwapResizeForceNow && (now - g_LastResizeTick) < kResizeThrottleMs)
        return;

    if (g_SwapResizeW == g_SwapCurrentW && g_SwapResizeH == g_SwapCurrentH)
    {
        g_SwapResizeRequested = false;
        g_SwapResizeForceNow = false;
        return;
    }

    g_InSwapResize = true;
    g_LastResizeTick = now;

    WaitForGpuIdle();
    CleanupRenderTargets();

    HRESULT hr = g_SwapChain->ResizeBuffers(NUM_BACK_BUFFERS, g_SwapResizeW, g_SwapResizeH, RTV_FORMAT, 0);

    if (FAILED(hr))
    {
        try { CreateRenderTargets(); } catch (...) {}
        if (hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET)
        {
            MessageBoxW(g_hWnd, L"DXGI device removed/reset. Uygulama kapatiliyor.", L"DX12 Error", MB_OK | MB_ICONERROR);
            g_Running = false;
        }

        g_SwapResizeForceNow = false;
        g_InSwapResize = false;
        return;
    }

    CreateRenderTargets();
    g_SwapCurrentW = g_SwapResizeW;
    g_SwapCurrentH = g_SwapResizeH;

    g_SwapResizeRequested = false;
    g_SwapResizeForceNow = false;
    g_SwapChainOccluded = false;
    g_InSwapResize = false;
}

// -----------------------------
// Drag / Resize (pencereyi ImGui ile)
static void BeginDragWindow()
{
    g_Dragging = true;
    SetCapture(g_hWnd);
    GetCursorPos(&g_DragMouseStart);
    GetWindowRect(g_hWnd, &g_DragWindowStart);
}
static void UpdateDragWindow()
{
    POINT p; GetCursorPos(&p);
    int dx = p.x - g_DragMouseStart.x;
    int dy = p.y - g_DragMouseStart.y;

    SetWindowPos(g_hWnd, nullptr,
        g_DragWindowStart.left + dx,
        g_DragWindowStart.top + dy,
        0, 0, SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);
}
static void EndDragWindow()
{
    g_Dragging = false;
    ReleaseCapture();
}

static void BeginResizeWindow()
{
    g_Resizing = true;
    SetCapture(g_hWnd);

    GetCursorPos(&g_ResizeMouseStart);
    GetWindowRect(g_hWnd, &g_ResizeWindowStart);

    g_ResizeW0 = (g_ResizeWindowStart.right - g_ResizeWindowStart.left);
    g_ResizeH0 = (g_ResizeWindowStart.bottom - g_ResizeWindowStart.top);
    g_ResizeAspect = (g_ResizeH0 > 0) ? (float)g_ResizeW0 / (float)g_ResizeH0 : (16.0f / 9.0f);
}

static void UpdateResizeWindow_Proportional()
{
    POINT p; GetCursorPos(&p);
    int dx = p.x - g_ResizeMouseStart.x;
    int dy = p.y - g_ResizeMouseStart.y;

    float scaleX = (float)(g_ResizeW0 + dx) / (float)g_ResizeW0;
    float scaleY = (float)(g_ResizeH0 + dy) / (float)g_ResizeH0;

    float diffX = std::fabs(scaleX - 1.0f);
    float diffY = std::fabs(scaleY - 1.0f);
    float scale = (diffX >= diffY) ? scaleX : scaleY;

    const int minW = 640;
    const int minH = 360;
    float minScale = std::max((float)minW / (float)g_ResizeW0, (float)minH / (float)g_ResizeH0);
    if (scale < minScale) scale = minScale;

    int newW = (int)std::lround((float)g_ResizeW0 * scale);
    int newH = (int)std::lround((float)newW / g_ResizeAspect);
    newW = std::max(newW, minW);
    newH = std::max(newH, minH);

    SetWindowPos(g_hWnd, nullptr,
        g_ResizeWindowStart.left, g_ResizeWindowStart.top,
        newW, newH,
        SWP_NOZORDER | SWP_NOACTIVATE);

    RequestSwapResize((UINT)newW, (UINT)newH, false);
}

static void EndResizeWindow()
{
    g_Resizing = false;
    ReleaseCapture();

    RECT rc{}; GetClientRect(g_hWnd, &rc);
    UINT w = (UINT)(rc.right - rc.left);
    UINT h = (UINT)(rc.bottom - rc.top);
    if (w > 0 && h > 0)
        RequestSwapResize(w, h, true);
}

static void UpdateWindowMoveResizeFromUI()
{
    if (g_Dragging)
    {
        if (GetAsyncKeyState(VK_LBUTTON) & 0x8000) UpdateDragWindow();
        else EndDragWindow();
    }

    if (g_Resizing)
    {
        if (GetAsyncKeyState(VK_LBUTTON) & 0x8000) UpdateResizeWindow_Proportional();
        else EndResizeWindow();
    }
}

// -----------------------------
// UI
static void BeginRootUI()
{
    ImGuiIO& io = ImGui::GetIO();
    ImGui::SetNextWindowPos(ImVec2(0, 0));
    ImGui::SetNextWindowSize(io.DisplaySize);

    ImGuiWindowFlags flags =
        ImGuiWindowFlags_NoDecoration |
        ImGuiWindowFlags_NoMove |
        ImGuiWindowFlags_NoResize |
        ImGuiWindowFlags_NoSavedSettings |
        ImGuiWindowFlags_NoBringToFrontOnFocus;

    ImGui::Begin("##Root", nullptr, flags);
}

static void DrawCustomTitleBar()
{
    const float barH = 36.0f;
    const float btnW = 42.0f;
    ImGuiStyle& style = ImGui::GetStyle();

    ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(10, 6));
    ImGui::BeginChild("##TitleBar", ImVec2(0, barH), false,
        ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse);

    {
        ImDrawList* dl = ImGui::GetWindowDrawList();
        ImVec2 a = ImGui::GetWindowPos();
        ImVec2 b = ImVec2(a.x + ImGui::GetWindowWidth(), a.y + barH);
        ImU32 bg = ImGui::GetColorU32(ImGuiCol_WindowBg);
        dl->AddRectFilled(a, b, bg);
        dl->AddLine(ImVec2(a.x, b.y - 1), ImVec2(b.x, b.y - 1), IM_COL32(255, 255, 255, 25), 1.0f);
    }

    const float xMin = ImGui::GetWindowContentRegionMin().x;
    const float xMax = ImGui::GetWindowContentRegionMax().x;
    const float btnAreaW = (btnW * 3.0f) + (style.ItemSpacing.x * 2.0f);
    const float xButtons = xMax - btnAreaW;

    ImGui::SetCursorPos(ImVec2(xMin, 0));
    ImGui::InvisibleButton("##drag_area", ImVec2(xButtons - xMin, barH));
    if (ImGui::IsItemHovered() && ImGui::IsMouseClicked(ImGuiMouseButton_Left))
        BeginDragWindow();

    ImGui::SetCursorPos(ImVec2(xMin, 6));
    ImGui::AlignTextToFramePadding();
    ImGui::TextUnformatted("ImGui DX12 Borderless App");

    ImGui::SetCursorPos(ImVec2(xButtons, 3));
    if (ImGui::Button("-", ImVec2(btnW, barH - 6))) g_PendingWinCmd = PendingWinCmd::Minimize;
    ImGui::SameLine();
    const bool isMax = IsZoomed(g_hWnd) != 0;
    if (ImGui::Button(isMax ? "❐" : "□", ImVec2(btnW, barH - 6))) g_PendingWinCmd = PendingWinCmd::ToggleMaxRestore;
    ImGui::SameLine();
    if (ImGui::Button("X", ImVec2(btnW, barH - 6))) g_PendingWinCmd = PendingWinCmd::Close;

    ImGui::EndChild();
    ImGui::PopStyleVar();
}

static void DrawResizeGripHatchedTriangle()
{
    if (IsZoomed(g_hWnd)) return;

    ImGuiIO& io = ImGui::GetIO();
    const float size = 28.0f;
    const float pad = 8.0f;

    ImVec2 pos = ImVec2(io.DisplaySize.x - size - pad, io.DisplaySize.y - size - pad);
    ImGui::SetCursorPos(pos);
    ImGui::InvisibleButton("##resize_grip_tri", ImVec2(size, size));

    if (ImGui::IsItemHovered())
        ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeNWSE);

    if (ImGui::IsItemHovered() && ImGui::IsMouseClicked(ImGuiMouseButton_Left))
        BeginResizeWindow();

    ImDrawList* dl = ImGui::GetWindowDrawList();
    ImVec2 a = ImGui::GetItemRectMin();
    ImVec2 b = ImGui::GetItemRectMax();

    ImVec2 p0 = ImVec2(b.x, b.y);
    ImVec2 p1 = ImVec2(a.x, b.y);
    ImVec2 p2 = ImVec2(b.x, a.y);

    ImVec4 text = ImGui::GetStyleColorVec4(ImGuiCol_Text);
    ImU32 outline = ImGui::GetColorU32(ImVec4(text.x, text.y, text.z, 0.85f));
    ImU32 hatch = ImGui::GetColorU32(ImVec4(text.x, text.y, text.z, 0.55f));
    ImU32 fill = ImGui::GetColorU32(ImVec4(text.x, text.y, text.z, 0.12f));

    if (ImGui::IsItemHovered())
    {
        outline = ImGui::GetColorU32(ImVec4(text.x, text.y, text.z, 0.95f));
        hatch = ImGui::GetColorU32(ImVec4(text.x, text.y, text.z, 0.70f));
        fill = ImGui::GetColorU32(ImVec4(text.x, text.y, text.z, 0.18f));
    }

    dl->AddTriangleFilled(p0, p1, p2, fill);
    dl->AddTriangle(p0, p1, p2, outline, 1.5f);

    const float step = 5.0f;
    for (float t = 0.0f; t <= size; t += step)
    {
        ImVec2 s = ImVec2(a.x + t, b.y);
        ImVec2 e = ImVec2(b.x, b.y - t);
        dl->AddLine(s, e, hatch, 1.2f);
    }
}

static void DrawContent()
{
    ImGui::Spacing();
    ImGui::Text("Safe live swapchain resize: GPU idle + throttle + tek noktadan ResizeBuffers.");
    ImGui::Separator();
}

static void EndRootUI() { ImGui::End(); }

// -----------------------------
// Win32
extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND, UINT, WPARAM, LPARAM);

static LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    if (ImGui_ImplWin32_WndProcHandler(hWnd, msg, wParam, lParam))
        return true;

    switch (msg)
    {
    case WM_DESTROY:
        g_Running = false;
        PostQuitMessage(0);
        return 0;
    }
    return DefWindowProcW(hWnd, msg, wParam, lParam);
}

// -----------------------------
// DX12 init
static bool CreateDeviceD3D(HWND hWnd)
{
    UINT dxgiFactoryFlags = 0;

#if defined(_DEBUG)
    {
        ComPtr<ID3D12Debug> debug;
        if (SUCCEEDED(D3D12GetDebugInterface(IID_PPV_ARGS(&debug))))
        {
            debug->EnableDebugLayer();
            dxgiFactoryFlags |= DXGI_CREATE_FACTORY_DEBUG;
        }
    }
#endif

    ComPtr<IDXGIFactory4> factory;
    if (FAILED(CreateDXGIFactory2(dxgiFactoryFlags, IID_PPV_ARGS(&factory))))
        return false;

    if (FAILED(D3D12CreateDevice(nullptr, D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&g_Device))))
        return false;

    {
        D3D12_COMMAND_QUEUE_DESC desc = {};
        desc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
        desc.NodeMask = 1;
        if (FAILED(g_Device->CreateCommandQueue(&desc, IID_PPV_ARGS(&g_CommandQueue))))
            return false;
    }

    {
        DXGI_SWAP_CHAIN_DESC1 sd = {};
        sd.BufferCount = NUM_BACK_BUFFERS;
        sd.Format = RTV_FORMAT;
        sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
        sd.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
        sd.SampleDesc.Count = 1;
        sd.Scaling = DXGI_SCALING_STRETCH;

        ComPtr<IDXGISwapChain1> swapChain1;
        if (FAILED(factory->CreateSwapChainForHwnd(g_CommandQueue.Get(), hWnd, &sd, nullptr, nullptr, &swapChain1)))
            return false;

        factory->MakeWindowAssociation(hWnd, DXGI_MWA_NO_ALT_ENTER);
        swapChain1.As(&g_SwapChain);
    }

    {
        D3D12_DESCRIPTOR_HEAP_DESC desc = {};
        desc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
        desc.NumDescriptors = NUM_BACK_BUFFERS;
        desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
        desc.NodeMask = 1;
        if (FAILED(g_Device->CreateDescriptorHeap(&desc, IID_PPV_ARGS(&g_RtvHeap))))
            return false;
        g_RtvDescriptorSize = g_Device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
    }

    for (int i = 0; i < NUM_FRAMES_IN_FLIGHT; i++)
        if (FAILED(g_Device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&g_FrameCtx[i].CommandAllocator))))
            return false;

    if (FAILED(g_Device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT,
        g_FrameCtx[0].CommandAllocator.Get(), nullptr, IID_PPV_ARGS(&g_CommandList))))
        return false;
    g_CommandList->Close();

    if (FAILED(g_Device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&g_Fence))))
        return false;
    g_FenceEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
    if (!g_FenceEvent) return false;

    g_SrvAllocator.Init(g_Device.Get(), 64);
    CreateRenderTargets();

    RECT rc{}; GetClientRect(hWnd, &rc);
    g_SwapCurrentW = (UINT)(rc.right - rc.left);
    g_SwapCurrentH = (UINT)(rc.bottom - rc.top);

    return true;
}

static void CleanupDeviceD3D()
{
    WaitForGpuIdle();
    CleanupRenderTargets();

    g_SwapChain.Reset();
    g_RtvHeap.Reset();
    g_SrvAllocator.Heap.Reset();

    g_CommandList.Reset();
    for (int i = 0; i < NUM_FRAMES_IN_FLIGHT; i++)
        g_FrameCtx[i].CommandAllocator.Reset();

    g_CommandQueue.Reset();
    g_Fence.Reset();
    g_Device.Reset();

    if (g_FenceEvent) { CloseHandle(g_FenceEvent); g_FenceEvent = nullptr; }
}

// -----------------------------
// Entry
int APIENTRY wWinMain(HINSTANCE hInstance, HINSTANCE, LPWSTR, int)
{
    WNDCLASSEXW wc = { sizeof(wc), CS_CLASSDC, WndProc, 0L, 0L, hInstance,
                       nullptr, nullptr, nullptr, nullptr, L"ImGuiDx12Borderless", nullptr };
    RegisterClassExW(&wc);

    DWORD style = WS_POPUP;
    DWORD exStyle = WS_EX_APPWINDOW;

    g_hWnd = CreateWindowExW(exStyle, wc.lpszClassName, L"ImGui DX12 Borderless",
        style, 100, 100, 854, 480,
        nullptr, nullptr, wc.hInstance, nullptr);

    if (!CreateDeviceD3D(g_hWnd))
    {
        CleanupDeviceD3D();
        UnregisterClassW(wc.lpszClassName, wc.hInstance);
        return 1;
    }

    ShowWindow(g_hWnd, SW_SHOWDEFAULT);
    UpdateWindow(g_hWnd);
    ForceForeground(g_hWnd);

    IMGUI_CHECKVERSION();
    ImGui::CreateContext();
    ImGui::StyleColorsLight();
    ImGui_ImplWin32_Init(g_hWnd);

    ImGui_ImplDX12_InitInfo init_info;
    init_info.Device = g_Device.Get();
    init_info.CommandQueue = g_CommandQueue.Get();
    init_info.NumFramesInFlight = NUM_FRAMES_IN_FLIGHT;
    init_info.RTVFormat = RTV_FORMAT;
    init_info.DSVFormat = DXGI_FORMAT_UNKNOWN;
    init_info.UserData = &g_SrvAllocator;
    init_info.SrvDescriptorHeap = g_SrvAllocator.Heap.Get();

    init_info.SrvDescriptorAllocFn = [](ImGui_ImplDX12_InitInfo* info,
        D3D12_CPU_DESCRIPTOR_HANDLE* out_cpu,
        D3D12_GPU_DESCRIPTOR_HANDLE* out_gpu)
        {
            auto* alloc = (SrvAllocator*)info->UserData;
            if (!alloc->Alloc(out_cpu, out_gpu))
                IM_ASSERT(false && "SRV heap dolu!");
        };

    init_info.SrvDescriptorFreeFn = [](ImGui_ImplDX12_InitInfo* info,
        D3D12_CPU_DESCRIPTOR_HANDLE cpu,
        D3D12_GPU_DESCRIPTOR_HANDLE gpu)
        {
            auto* alloc = (SrvAllocator*)info->UserData;
            alloc->Free(cpu, gpu);
        };

    ImGui_ImplDX12_Init(&init_info);

    MSG msg{};
    while (g_Running)
    {
        while (PeekMessageW(&msg, nullptr, 0U, 0U, PM_REMOVE))
        {
            TranslateMessage(&msg);
            DispatchMessageW(&msg);
            if (msg.message == WM_QUIT)
                g_Running = false;
        }
        if (!g_Running) break;

        if (g_PendingWinCmd != PendingWinCmd::None)
        {
            ImGui::ClearActiveID();
            ImGuiIO& io = ImGui::GetIO();
            io.AddMouseButtonEvent(0, false);
            io.AddMouseButtonEvent(1, false);
            io.AddMouseButtonEvent(2, false);

            if (g_PendingWinCmd == PendingWinCmd::Minimize) ShowWindow(g_hWnd, SW_MINIMIZE);
            else if (g_PendingWinCmd == PendingWinCmd::ToggleMaxRestore) ShowWindow(g_hWnd, IsZoomed(g_hWnd) ? SW_RESTORE : SW_MAXIMIZE);
            else if (g_PendingWinCmd == PendingWinCmd::Close) PostMessageW(g_hWnd, WM_CLOSE, 0, 0);

            ForceForeground(g_hWnd);
            g_PendingWinCmd = PendingWinCmd::None;

            RECT rc{}; GetClientRect(g_hWnd, &rc);
            UINT w = (UINT)(rc.right - rc.left);
            UINT h = (UINT)(rc.bottom - rc.top);
            RequestSwapResize(w, h, true);
        }

        if (IsIconic(g_hWnd))
        {
            Sleep(10);
            continue;
        }

        TryPerformSwapResize();
        if (!g_Running) break;

        FrameContext* frameCtx = WaitForNextFrameResources();
        UINT backBufferIdx = g_SwapChain->GetCurrentBackBufferIndex();

        frameCtx->CommandAllocator->Reset();
        g_CommandList->Reset(frameCtx->CommandAllocator.Get(), nullptr);

        ImGui_ImplDX12_NewFrame();
        ImGui_ImplWin32_NewFrame();
        ImGui::NewFrame();

        BeginRootUI();
        DrawCustomTitleBar();
        DrawContent();
        DrawResizeGripHatchedTriangle();
        UpdateWindowMoveResizeFromUI();
        EndRootUI();

        ImGui::Render();

        D3D12_RESOURCE_BARRIER barrier = {};
        barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
        barrier.Transition.pResource = g_BackBuffer[backBufferIdx].Get();
        barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
        barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_PRESENT;
        barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_RENDER_TARGET;
        g_CommandList->ResourceBarrier(1, &barrier);

        ImVec4 bg = ImGui::GetStyleColorVec4(ImGuiCol_WindowBg);
        const float clearColor[4] = { bg.x, bg.y, bg.z, 1.0f };

        g_CommandList->OMSetRenderTargets(1, &g_BackBufferRtv[backBufferIdx], FALSE, nullptr);
        g_CommandList->ClearRenderTargetView(g_BackBufferRtv[backBufferIdx], clearColor, 0, nullptr);

        ID3D12DescriptorHeap* heaps[] = { g_SrvAllocator.Heap.Get() };
        g_CommandList->SetDescriptorHeaps(1, heaps);
        ImGui_ImplDX12_RenderDrawData(ImGui::GetDrawData(), g_CommandList.Get());

        std::swap(barrier.Transition.StateBefore, barrier.Transition.StateAfter);
        g_CommandList->ResourceBarrier(1, &barrier);

        g_CommandList->Close();
        ID3D12CommandList* cmdLists[] = { g_CommandList.Get() };
        g_CommandQueue->ExecuteCommandLists(1, cmdLists);

        HRESULT hr = g_SwapChain->Present(1, 0);
        if (hr == DXGI_STATUS_OCCLUDED)
            g_SwapChainOccluded = true;
        else
            g_SwapChainOccluded = false;

        SignalFenceForFrame();
    }

    WaitForGpuIdle();

    ImGui_ImplDX12_Shutdown();
    ImGui_ImplWin32_Shutdown();
    ImGui::DestroyContext();

    CleanupDeviceD3D();
    DestroyWindow(g_hWnd);
    UnregisterClassW(wc.lpszClassName, wc.hInstance);
    return 0;
}
Yorum Bırakın

E-posta adresiniz yayınlanmayacaktır. Zorunlu alanlar * ile işaretlenmiştir