Skip to content

Instantly share code, notes, and snippets.

@Pikachuxxxx
Last active May 30, 2024 13:40
Show Gist options
  • Save Pikachuxxxx/75154c41a34aa5ec1fece92c3649c1d8 to your computer and use it in GitHub Desktop.
Save Pikachuxxxx/75154c41a34aa5ec1fece92c3649c1d8 to your computer and use it in GitHub Desktop.
Dixy12 - Learning DirectX12 (https://www.3dgep.com/learning-directx-12-1)
#pragma once
// C Headers
#include <assert.h>
#include <stdarg.h>
#include <stdio.h>
#include <time.h>
// Define log levels
typedef enum
{
INFO,
WARNING,
ERR,
IMPORTANT,
TIMESTAMP = IMPORTANT
} LogLevel;
#ifdef _DEBUG
// Function to log the message with time, date, and color
void log_message(LogLevel level, const char *format, ...)
{
// Get the current time
time_t t = time(NULL);
struct tm tm;
// Use localtime_s to safely get the local time
errno_t err = localtime_s(&tm, &t);
assert(err == 0 && "Failed to get local time");
char s[64];
size_t ret = strftime(s, sizeof(s), "%c", &tm);
assert(ret);
// Set the color based on the log level
const char *color;
switch (level) {
case INFO:
color = "\033[0;37m"; // White
break;
case WARNING:
color = "\033[0;33m"; // Yellow
break;
case ERR:
color = "\033[0;31m"; // Red
break;
case TIMESTAMP:
color = "\033[0;32m"; // Green
break;
default:
color = "\033[0m"; // Reset
break;
}
// Print the time and date
printf("%s %s :: [Dixy12] ", color, s);
// Print the user-defined message
va_list args;
va_start(args, format);
vprintf(format, args);
va_end(args);
// Reset the color
printf("\033[0m\n");
}
// Define the LOG macro
#define LOG(level, format, ...) log_message(level, format, ##__VA_ARGS__)
#define LOG_INFO(format, ...) log_message(LogLevel::INFO, format, ##__VA_ARGS__)
#define LOG_WARN(format, ...) log_message(LogLevel::WARNING, format, ##__VA_ARGS__)
#define LOG_ERR(format, ...) log_message(LogLevel::ERR, format, ##__VA_ARGS__)
#define LOG_IMP(format, ...) log_message(LogLevel::IMPORTANT, format, ##__VA_ARGS__)
#else
#define LOG(level, format, ...)
#define LOG_INFO(format, ...)
#define LOG_WARN(format, ...)
#define LOG_ERR(format, ...)
#define LOG_IMP(format, ...)
#endif
// STL Headers
#include <algorithm>
#include <cassert>
#include <chrono>
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <shellapi.h> // For CommandLineToArgvW
// The min/max macros conflict with like-named member functions.
// Only use std::min and std::max defined in <algorithm>.
#if defined(min)
#undef min
#endif
#if defined(max)
#undef max
#endif
// In order to define a function called CreateWindow, the Windows macro needs to
// be undefined.
#if defined(CreateWindow)
#undef CreateWindow
#endif
// Windows Runtime Library. Needed for Microsoft::WRL::ComPtr<> template class.
#include <wrl.h>
using namespace Microsoft::WRL;
#include <windows.h>
// DirectX 12 specific headers.
#include <d3d12.h>
// The Microsoft DirectX Graphics Infrastructure(DXGI)
// is used to manage the low - level tasks such as enumerating GPU adapters,
// presenting the rendered image to the screen, and handling full - screen transitions,
// that are not necessarily part of the DirectX rendering API.
#include <DirectXMath.h>
#include <d3dcompiler.h>
#include <dxgi1_6.h>
// From DXSampleHelper.h
// Source: https://github.com/Microsoft/DirectX-Graphics-Samples
inline void ThrowIfFailed(HRESULT hr)
{
if (FAILED(hr)) {
LOG_ERR("Invalid Handle!");
throw std::exception();
}
}
#ifdef _DEBUG
#define TIME_STAMP_BEGIN(name) \
const char *Key = name; \
auto start = std::chrono::high_resolution_clock::now();
#define TIME_STAMP_END() \
auto stop = std::chrono::high_resolution_clock::now(); \
std::chrono::duration<float, std::milli> ms_f32 = (stop - start); \
log_message(TIMESTAMP, "[TimeStamp] %s : %5.2f ms", Key, ms_f32.count());
#else
#define TIME_STAMP_BEGIN(name)
#define TIME_STAMP_END()
#endif
/**
* Dixy12 - DirectX12 learning sandbox
* Source: https://www.3dgep.com/learning-directx-12-1
*/
#include "common.h"
//----------------------------------------------------------------
constexpr uint8_t g_NumFrames = 3; // The number of swap chain back buffers. 2 Back buffers + 1 presentation image = 3
//----------------------------------------------------------------
namespace AppGlobalSettings {
bool g_UseWarp = false; // Use WARP adapter. Software Rasterizer for older GPUs
uint32_t g_ClientWidth = 1280; // App initial window width
uint32_t g_ClientHeight = 720; // App initial window height
bool g_IsInitialized = false; // Set to true once the DX12 objects have been initialized
bool g_VSync = false; // Whether or not to cap the framerate to refresh rate of the screen
bool g_TearingSupported = false; // Allows uncapped frame rate
bool g_Fullscreen = false; // Whether or not to use full screen
} // namespace AppGlobalSettings
//----------------------------------------------------------------
namespace DXHandles {
HWND g_HWND = NULL; // Window Handle
RECT g_WindowRect = {}; // Window Rect
ComPtr<ID3D12Device10> g_Device; // DirectX 12 device is used to create resources
ComPtr<ID3D12CommandQueue> g_CommandQueue; // Graphics Command queue to record command onto
ComPtr<IDXGISwapChain4> g_SwapChain; // Swapchain to manage rendering and presentation of back buffers
ComPtr<ID3D12Resource> g_BackBuffers[g_NumFrames]; // Back buffer texture resources
ComPtr<ID3D12GraphicsCommandList> g_CommandList; // Command buffer to record commands onto
ComPtr<ID3D12CommandAllocator> g_CommandAllocators[g_NumFrames]; // Command allocator to manage command lists per back buffer render frame
ComPtr<ID3D12DescriptorHeap> g_RTVDescriptorHeap; // Descriptor heap to allocator back buffer resources from
UINT g_RTVDescriptorSize = 0; // Descriptor heap size
UINT g_CurrentBackBufferIndex = 0; // Index of the current back buffer image to submit to execute the command queue onto
} // namespace DXHandles
//----------------------------------------------------------------
/*
CPU GPU
Signal ID3D12Fence::Signal ID3D12CommandQueue::Signal
Wait ID3D12Fence::SetEventOnCompletion, ID3D12CommandQueue::Wait
WaitForSingleObject
*/
namespace FrameSyncData {
ComPtr<ID3D12Fence> g_Fence; // Fence is used to store the Handle to API object
uint64_t g_FenceValue = 0; // The next fence value to signal the command queue next
uint64_t g_FrameFenceValues[g_NumFrames] = {}; // Each frame has it's own value to wait on this to avoid overwriting
HANDLE g_FenceEvent = NULL; // OS event handle to get notification of fence reaching the signaled value
} // namespace FrameSyncData
//----------------------------------------------------------------
// Forward Declaration - App
namespace App {
void Run();
void Resize(uint32_t width, uint32_t height);
} // namespace App
namespace OSUtil {
void ParseCommandLineArguments()
{
int argc;
wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc);
for (size_t i = 0; i < argc; ++i) {
if (::wcscmp(argv[i], L"-w") == 0 || ::wcscmp(argv[i], L"--width") == 0) {
AppGlobalSettings::g_ClientWidth = ::wcstol(argv[++i], nullptr, 10);
}
if (::wcscmp(argv[i], L"-h") == 0 || ::wcscmp(argv[i], L"--height") == 0) {
AppGlobalSettings::g_ClientHeight = ::wcstol(argv[++i], nullptr, 10);
}
if (::wcscmp(argv[i], L"-warp") == 0 || ::wcscmp(argv[i], L"--warp") == 0) {
AppGlobalSettings::g_UseWarp = true;
}
if (::wcscmp(argv[i], L"-vsync") == 0) {
AppGlobalSettings::g_VSync = true;
}
if (::wcscmp(argv[i], L"-f") == 0 || ::wcscmp(argv[i], L"--fullscreen") == 0) {
AppGlobalSettings::g_Fullscreen = true;
}
}
// Free memory allocated by CommandLineToArgvW
::LocalFree(argv);
}
//----------------------------------------------------------------
void SetFullscreen(bool fullscreen);
// Window callback function.
LRESULT CALLBACK WinProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
if (AppGlobalSettings::g_IsInitialized) {
switch (message) {
case WM_PAINT:
App::Run();
break;
case WM_SYSKEYDOWN:
case WM_KEYDOWN: {
bool alt = (::GetAsyncKeyState(VK_MENU) & 0x8000) != 0;
switch (wParam) {
case 'V':
AppGlobalSettings::g_VSync = !AppGlobalSettings::g_VSync;
break;
case VK_ESCAPE:
::PostQuitMessage(0);
break;
case VK_RETURN:
if (alt) {
case VK_F11:
SetFullscreen(!AppGlobalSettings::g_Fullscreen);
}
break;
}
} break;
// The default window procedure will play a system notification sound
// when pressing the Alt+Enter keyboard combination if this message is
// not handled.
case WM_SYSCHAR:
break;
case WM_SIZE: {
RECT clientRect = {};
::GetClientRect(hWnd, &clientRect);
int width = clientRect.right - clientRect.left;
int height = clientRect.bottom - clientRect.top;
App::Resize(width, height);
} break;
case WM_DESTROY:
::PostQuitMessage(0);
break;
default:
return ::DefWindowProcW(hWnd, message, wParam, lParam);
}
} else {
return ::DefWindowProcW(hWnd, message, wParam, lParam);
}
return 0;
}
//----------------------------------------------------------------
const wchar_t* g_WindowClassName = L"Dixty12WindowClass";
void RegisterWindowClass(HINSTANCE hInst)
{
// Register a window class for creating our render window with.
WNDCLASSEXW windowClass = {};
windowClass.cbSize = sizeof(WNDCLASSEX);
windowClass.style = CS_HREDRAW | CS_VREDRAW;
windowClass.lpfnWndProc = WinProc;
windowClass.cbClsExtra = 0;
windowClass.cbWndExtra = 0;
windowClass.hInstance = hInst;
windowClass.hIcon = ::LoadIcon(hInst, NULL);
windowClass.hCursor = ::LoadCursor(NULL, IDC_ARROW);
windowClass.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1);
windowClass.lpszMenuName = NULL;
windowClass.lpszClassName = g_WindowClassName;
windowClass.hIconSm = ::LoadIcon(hInst, NULL);
static ATOM atom = ::RegisterClassExW(&windowClass);
assert(atom > 0);
}
//----------------------------------------------------------------
HWND CreateWindow(HINSTANCE hInst, const wchar_t* windowTitle, uint32_t width, uint32_t height)
{
int screenWidth = ::GetSystemMetrics(SM_CXSCREEN);
int screenHeight = ::GetSystemMetrics(SM_CYSCREEN);
RECT windowRect = {0, 0, static_cast<LONG>(width), static_cast<LONG>(height)};
::AdjustWindowRect(&windowRect, WS_OVERLAPPEDWINDOW, FALSE);
int windowWidth = windowRect.right - windowRect.left;
int windowHeight = windowRect.bottom - windowRect.top;
// Center the window within the screen. Clamp to 0, 0 for the top-left corner.
int windowX = std::max<int>(0, (screenWidth - windowWidth) / 2);
int windowY = std::max<int>(0, (screenHeight - windowHeight) / 2);
HWND hWnd = ::CreateWindowExW(
NULL,
g_WindowClassName,
windowTitle,
WS_OVERLAPPEDWINDOW,
windowX,
windowY,
windowWidth,
windowHeight,
NULL,
NULL,
hInst,
nullptr);
assert(hWnd && "Failed to create window!");
return hWnd;
}
//----------------------------------------------------------------
void SetFullscreen(bool fullscreen)
{
if (AppGlobalSettings::g_Fullscreen != fullscreen) {
AppGlobalSettings::g_Fullscreen = fullscreen;
if (AppGlobalSettings::g_Fullscreen) // Switching to fullscreen.
{
// Store the current window dimensions so they can be restored
// when switching out of fullscreen state.
::GetWindowRect(DXHandles::g_HWND, &DXHandles::g_WindowRect);
// Set the window style to a borderless window so the client area fills
// the entire screen.
UINT windowStyle = WS_OVERLAPPEDWINDOW & ~(WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX);
::SetWindowLongW(DXHandles::g_HWND, GWL_STYLE, windowStyle);
// Query the name of the nearest display device for the window.
// This is required to set the fullscreen dimensions of the window
// when using a multi-monitor setup.
HMONITOR hMonitor = ::MonitorFromWindow(DXHandles::g_HWND, MONITOR_DEFAULTTONEAREST);
MONITORINFOEX monitorInfo = {};
monitorInfo.cbSize = sizeof(MONITORINFOEX);
::GetMonitorInfo(hMonitor, &monitorInfo);
::SetWindowPos(DXHandles::g_HWND, HWND_TOP, monitorInfo.rcMonitor.left, monitorInfo.rcMonitor.top, monitorInfo.rcMonitor.right - monitorInfo.rcMonitor.left, monitorInfo.rcMonitor.bottom - monitorInfo.rcMonitor.top, SWP_FRAMECHANGED | SWP_NOACTIVATE);
::ShowWindow(DXHandles::g_HWND, SW_MAXIMIZE);
} else {
// Restore all the window decorators.
::SetWindowLong(DXHandles::g_HWND, GWL_STYLE, WS_OVERLAPPEDWINDOW);
::SetWindowPos(DXHandles::g_HWND, HWND_NOTOPMOST, DXHandles::g_WindowRect.left, DXHandles::g_WindowRect.top, DXHandles::g_WindowRect.right - DXHandles::g_WindowRect.left, DXHandles::g_WindowRect.bottom - DXHandles::g_WindowRect.top, SWP_FRAMECHANGED | SWP_NOACTIVATE);
::ShowWindow(DXHandles::g_HWND, SW_NORMAL);
}
}
}
//----------------------------------------------------------------
void SetDixyWindowTitle(const char* title, ...)
{
// Allocates storage
char* windowTitle = (char*) malloc(100 * sizeof(char));
va_list args;
va_start(args, title);
vsprintf_s(windowTitle, 100, title, args);
va_end(args);
SetWindowTextA(DXHandles::g_HWND, windowTitle);
}
} // namespace OSUtil
namespace DXUtil {
void EnableDebugLayer()
{
#if defined(_DEBUG)
// Always enable the debug layer before doing anything DX12 related
// so all possible errors generated while creating DX12 objects
// are caught by the debug layer.
ComPtr<ID3D12Debug> debugInterface;
ThrowIfFailed(D3D12GetDebugInterface(IID_PPV_ARGS(&debugInterface)));
debugInterface->EnableDebugLayer();
#endif
}
//----------------------------------------------------------------
ComPtr<IDXGIAdapter4> SelectBestGPUAdapter(bool useWarp)
{
ComPtr<IDXGIFactory4> dxgiFactory;
UINT createFactoryFlags = 0;
#if defined(_DEBUG)
createFactoryFlags = DXGI_CREATE_FACTORY_DEBUG;
#endif
ThrowIfFailed(CreateDXGIFactory2(createFactoryFlags, IID_PPV_ARGS(&dxgiFactory)));
ComPtr<IDXGIAdapter1> dxgiAdapter1;
ComPtr<IDXGIAdapter4> dxgiAdapter4;
if (useWarp) {
ThrowIfFailed(dxgiFactory->EnumWarpAdapter(IID_PPV_ARGS(&dxgiAdapter1)));
ThrowIfFailed(dxgiAdapter1.As(&dxgiAdapter4));
} else {
// 1. Select GPU with highest VRAM
// 2. Min Feature level: DirectX version >= 12.2
SIZE_T maxDedicatedVideoMemory = 0;
for (UINT i = 0; dxgiFactory->EnumAdapters1(i, &dxgiAdapter1) != DXGI_ERROR_NOT_FOUND; ++i) {
DXGI_ADAPTER_DESC1 dxgiAdapterDesc1;
dxgiAdapter1->GetDesc1(&dxgiAdapterDesc1);
// Since only hardware adapters should be considered, WARP adapters that have the DXGI_ADAPTER_FLAG_SOFTWARE flag set, should be ignored.
if ((dxgiAdapterDesc1.Flags & DXGI_ADAPTER_FLAG_SOFTWARE) == 0 &&
// Fake device (nullptr) too see if the min feature level will be supported, Min Feature level: DirectX version >= 12.2
SUCCEEDED(D3D12CreateDevice(dxgiAdapter1.Get(), D3D_FEATURE_LEVEL_12_2, __uuidof(ID3D12Device), nullptr)) &&
// Select GPU with highest VRAM
dxgiAdapterDesc1.DedicatedVideoMemory > maxDedicatedVideoMemory) {
maxDedicatedVideoMemory = dxgiAdapterDesc1.DedicatedVideoMemory;
ThrowIfFailed(dxgiAdapter1.As(&dxgiAdapter4));
}
}
}
// TODO: Print GPU Adapter details
LOG_INFO("Selected Adapter Info: ");
DXGI_ADAPTER_DESC3 adapterDesc{};
dxgiAdapter4->GetDesc3(&adapterDesc);
LOG_IMP("\t -> Name: %ls", adapterDesc.Description);
LOG_IMP("\t -> VendorID: %d", adapterDesc.VendorId);
LOG_IMP("\t -> DeviceId: %d", adapterDesc.DeviceId);
LOG_IMP("\t -> SubSysId: %d", adapterDesc.SubSysId);
LOG_IMP("\t -> Revision: %d", adapterDesc.Revision);
LOG_IMP("\t -> DedicatedVideoMemory: %d", adapterDesc.DedicatedVideoMemory);
LOG_IMP("\t -> DedicatedSystemMemory: %d", adapterDesc.DedicatedSystemMemory);
LOG_IMP("\t -> SharedSystemMemory: %d", adapterDesc.SharedSystemMemory);
// For querying props IDXGIAdapter1 is needed but for creating the latest ID3D12Device we will need IDXGIAdapter4 or whatever number is latest
return dxgiAdapter4;
}
//----------------------------------------------------------------
ComPtr<ID3D12Device10> CreateDeviceFromAdapter(ComPtr<IDXGIAdapter4> adapter)
{
ComPtr<ID3D12Device10> device10;
ThrowIfFailed(D3D12CreateDevice(adapter.Get(), D3D_FEATURE_LEVEL_12_2, IID_PPV_ARGS(&device10)));
// Enable debug messages in debug mode.
#if defined(_DEBUG)
ComPtr<ID3D12InfoQueue> pInfoQueue;
if (SUCCEEDED(device10.As(&pInfoQueue))) {
pInfoQueue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_CORRUPTION, TRUE);
pInfoQueue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_ERROR, TRUE);
pInfoQueue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_WARNING, TRUE);
// Suppress whole categories of messages
//D3D12_MESSAGE_CATEGORY Categories[] = {};
// Suppress messages based on their severity level
D3D12_MESSAGE_SEVERITY Severities[] =
{
D3D12_MESSAGE_SEVERITY_INFO};
// Suppress individual messages by their ID
D3D12_MESSAGE_ID DenyIds[] = {
D3D12_MESSAGE_ID_CLEARRENDERTARGETVIEW_MISMATCHINGCLEARVALUE, // I'm really not sure how to avoid this message.
D3D12_MESSAGE_ID_MAP_INVALID_NULLRANGE, // This warning occurs when using capture frame while graphics debugging.
D3D12_MESSAGE_ID_UNMAP_INVALID_NULLRANGE, // This warning occurs when using capture frame while graphics debugging.
};
D3D12_INFO_QUEUE_FILTER NewFilter = {};
//NewFilter.DenyList.NumCategories = _countof(Categories);
//NewFilter.DenyList.pCategoryList = Categories;
NewFilter.DenyList.NumSeverities = _countof(Severities);
NewFilter.DenyList.pSeverityList = Severities;
NewFilter.DenyList.NumIDs = _countof(DenyIds);
NewFilter.DenyList.pIDList = DenyIds;
ThrowIfFailed(pInfoQueue->PushStorageFilter(&NewFilter));
}
#endif
return device10;
}
//----------------------------------------------------------------
ComPtr<ID3D12DescriptorHeap> CreateDescriptorHeap(D3D12_DESCRIPTOR_HEAP_TYPE type, uint32_t numDescriptors)
{
ComPtr<ID3D12DescriptorHeap> descriptorHeap;
D3D12_DESCRIPTOR_HEAP_DESC desc = {};
desc.NumDescriptors = numDescriptors;
desc.Type = type;
ThrowIfFailed(DXHandles::g_Device->CreateDescriptorHeap(&desc, IID_PPV_ARGS(&descriptorHeap)));
return descriptorHeap;
}
//----------------------------------------------------------------
ComPtr<ID3D12CommandAllocator> CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE type)
{
ComPtr<ID3D12CommandAllocator> commandAllocator;
ThrowIfFailed(DXHandles::g_Device->CreateCommandAllocator(type, IID_PPV_ARGS(&commandAllocator)));
return commandAllocator;
}
//----------------------------------------------------------------
ComPtr<ID3D12GraphicsCommandList> CreateCommandList(ComPtr<ID3D12CommandAllocator> commandAllocator, D3D12_COMMAND_LIST_TYPE type)
{
LOG(INFO, "Creating command list...");
ComPtr<ID3D12GraphicsCommandList> commandList;
ThrowIfFailed(DXHandles::g_Device->CreateCommandList(0, type, commandAllocator.Get(), nullptr, IID_PPV_ARGS(&commandList)));
ThrowIfFailed(commandList->Close());
return commandList;
}
//----------------------------------------------------------------
ComPtr<IDXGISwapChain4> CreateSwapchain(HWND hWnd, ComPtr<ID3D12CommandQueue> commandQueue, uint32_t width, uint32_t height, uint32_t bufferCount)
{
ComPtr<IDXGISwapChain4> dxgiSwapChain4;
ComPtr<IDXGIFactory4> dxgiFactory4;
UINT createFactoryFlags = 0;
#if defined(_DEBUG)
createFactoryFlags = DXGI_CREATE_FACTORY_DEBUG;
#endif
ThrowIfFailed(CreateDXGIFactory2(createFactoryFlags, IID_PPV_ARGS(&dxgiFactory4)));
DXGI_SWAP_CHAIN_DESC1 swapChainDesc = {};
swapChainDesc.Width = width;
swapChainDesc.Height = height;
swapChainDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
swapChainDesc.Stereo = FALSE;
swapChainDesc.SampleDesc = {1, 0};
swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
swapChainDesc.BufferCount = bufferCount;
swapChainDesc.Scaling = DXGI_SCALING_STRETCH;
swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
swapChainDesc.AlphaMode = DXGI_ALPHA_MODE_UNSPECIFIED;
// It is recommended to always allow tearing if tearing support is available.
swapChainDesc.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING;
ComPtr<IDXGISwapChain1> swapChain1;
ThrowIfFailed(dxgiFactory4->CreateSwapChainForHwnd(
commandQueue.Get(),
hWnd,
&swapChainDesc,
nullptr,
nullptr,
&swapChain1));
// Disable the Alt+Enter fullscreen toggle feature. Switching to fullscreen
// will be handled manually.
ThrowIfFailed(dxgiFactory4->MakeWindowAssociation(hWnd, DXGI_MWA_NO_ALT_ENTER));
ThrowIfFailed(swapChain1.As(&dxgiSwapChain4));
return dxgiSwapChain4;
}
//----------------------------------------------------------------
void UpdateSwapchainRTVs()
{
// To iterate the descriptors in a descriptor heap, a handle to the first descriptor in the heap is retrieved
D3D12_CPU_DESCRIPTOR_HANDLE rtvStartHandle = DXHandles::g_RTVDescriptorHeap->GetCPUDescriptorHandleForHeapStart();
for (int i = 0; i < g_NumFrames; ++i) {
ComPtr<ID3D12Resource> backBuffer;
ThrowIfFailed(DXHandles::g_SwapChain->GetBuffer(i, IID_PPV_ARGS(&backBuffer)));
// A NULL description is used to create a default descriptor for the resource
// In this case, the resource's internal description is used to create the RTV
// Since the Resource is allocated from swapchain and not by user it has the required internal desc
DXHandles::g_Device->CreateRenderTargetView(backBuffer.Get(), nullptr, rtvStartHandle);
DXHandles::g_BackBuffers[i] = backBuffer;
rtvStartHandle.ptr += DXHandles::g_RTVDescriptorSize;
}
}
//----------------------------------------------------------------
ComPtr<ID3D12Fence> CreateFence()
{
ComPtr<ID3D12Fence> fence;
ThrowIfFailed(DXHandles::g_Device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&fence)));
return fence;
}
HANDLE CreateFenceEventHandle()
{
HANDLE fenceEvent;
fenceEvent = ::CreateEvent(NULL, FALSE, FALSE, NULL);
assert(fenceEvent && "Failed to create fence event.");
return fenceEvent;
}
uint64_t Signal(ComPtr<ID3D12CommandQueue> commandQueue, ComPtr<ID3D12Fence> fence, uint64_t& fenceValue)
{
// The GPU will wait on a value, if current value is 0 the next set of command to wait on a different value
// So we will increment and ask the GPU to wait on this until the next set of commands are done till the signal command
// So, We return the incremented value that the fence should wait on for next batch of commands to be completed.
uint64_t fenceValueForSignal = ++fenceValue;
ThrowIfFailed(commandQueue->Signal(fence.Get(), fenceValueForSignal));
return fenceValueForSignal;
}
void StallCPUForFenceValue(ComPtr<ID3D12Fence> fence, uint64_t fenceValue, HANDLE fenceEvent, std::chrono::milliseconds duration = std::chrono::milliseconds::max())
{
if (fence->GetCompletedValue() < fenceValue) {
ThrowIfFailed(fence->SetEventOnCompletion(fenceValue, fenceEvent));
::WaitForSingleObject(fenceEvent, static_cast<DWORD>(duration.count()));
}
}
//----------------------------------------------------------------
void Flush(ComPtr<ID3D12CommandQueue> commandQueue, ComPtr<ID3D12Fence> fence, uint64_t& fenceValue, HANDLE fenceEvent)
{
uint64_t fenceValueForSignal = Signal(commandQueue, fence, fenceValue);
StallCPUForFenceValue(fence, fenceValueForSignal, fenceEvent);
}
//----------------------------------------------------------------
void InitOffsetted(_Out_ D3D12_CPU_DESCRIPTOR_HANDLE& handle, INT offsetInDescriptors, UINT descriptorIncrementSize) noexcept
{
handle.ptr = SIZE_T(INT64(handle.ptr) + INT64(offsetInDescriptors) * INT64(descriptorIncrementSize));
}
} // namespace DXUtil
namespace App {
static uint64_t g_FrameCounter = 0;
static double g_ElapsedSeconds = 0.0;
static float g_DeltaTimeMs = 0.0f;
static uint32_t g_FPS = 0;
void Update(float deltaTime)
{
OSUtil::SetDixyWindowTitle("Welcome to Dixy12! | FPS: %d", g_FPS);
}
void Render()
{
auto commandAllocator = DXHandles::g_CommandAllocators[DXHandles::g_CurrentBackBufferIndex];
auto backBuffer = DXHandles::g_BackBuffers[DXHandles::g_CurrentBackBufferIndex];
commandAllocator->Reset();
// Begin recording
DXHandles::g_CommandList->Reset(commandAllocator.Get(), nullptr);
// Clear the Render Target
{
// Transition from Present to RT state
{
D3D12_RESOURCE_BARRIER barrier{};
barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
barrier.Transition.pResource = backBuffer.Get();
barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_PRESENT;
barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_RENDER_TARGET;
barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
DXHandles::g_CommandList->ResourceBarrier(1, &barrier);
}
FLOAT clearColor[] = {0.9f * (float) cos(g_ElapsedSeconds), 0.6f * (float) sin(g_ElapsedSeconds), 0.46f, 1.0f};
// The handle is offset from the beginning of the descriptor heap based on the current back buffer index and the size of the descriptor
D3D12_CPU_DESCRIPTOR_HANDLE rtv = DXHandles::g_RTVDescriptorHeap->GetCPUDescriptorHandleForHeapStart();
DXUtil::InitOffsetted(rtv, DXHandles::g_CurrentBackBufferIndex, DXHandles::g_RTVDescriptorSize);
DXHandles::g_CommandList->ClearRenderTargetView(rtv, clearColor, 0, nullptr);
}
// Present
{
// Transition from RT to Present
{
D3D12_RESOURCE_BARRIER barrier{};
barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
barrier.Transition.pResource = backBuffer.Get();
barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET;
barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_PRESENT;
barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
DXHandles::g_CommandList->ResourceBarrier(1, &barrier);
}
// End recording.
ThrowIfFailed(DXHandles::g_CommandList->Close());
// Submit queue for execution.
ID3D12CommandList* const commandLists[] = {
DXHandles::g_CommandList.Get()};
DXHandles::g_CommandQueue->ExecuteCommandLists(_countof(commandLists), commandLists);
// Present the rendered image.
UINT syncInterval = AppGlobalSettings::g_VSync ? 1 : 0;
UINT presentFlags = AppGlobalSettings::g_TearingSupported && !AppGlobalSettings::g_VSync ? DXGI_PRESENT_ALLOW_TEARING : 0;
ThrowIfFailed(DXHandles::g_SwapChain->Present(syncInterval, presentFlags));
// Frame synchronization - signal on current back buffer to finish presentation, to wait on using these resources in next frame.
FrameSyncData::g_FrameFenceValues[DXHandles::g_CurrentBackBufferIndex] = DXUtil::Signal(DXHandles::g_CommandQueue, FrameSyncData::g_Fence, FrameSyncData::g_FenceValue);
// Acquire the current back buffer of the swapchain
DXHandles::g_CurrentBackBufferIndex = DXHandles::g_SwapChain->GetCurrentBackBufferIndex();
// Before overwriting the contents of the current back buffer with the contents of the next frame we stall CPU for previous commands to finish.
DXUtil::StallCPUForFenceValue(FrameSyncData::g_Fence, FrameSyncData::g_FrameFenceValues[DXHandles::g_CurrentBackBufferIndex], FrameSyncData::g_FenceEvent);
}
}
void Resize(uint32_t width, uint32_t height)
{
if (AppGlobalSettings::g_ClientWidth != width || AppGlobalSettings::g_ClientHeight != height) {
// Don't allow 0 size swap chain back buffers.
AppGlobalSettings::g_ClientWidth = std::max(1u, width);
AppGlobalSettings::g_ClientHeight = std::max(1u, height);
// Flush the GPU queue to make sure the swap chain's back buffers are not being referenced by an in-flight command list.
DXUtil::Flush(DXHandles::g_CommandQueue, FrameSyncData::g_Fence, FrameSyncData::g_FenceValue, FrameSyncData::g_FenceEvent);
for (int i = 0; i < g_NumFrames; ++i) {
// Any references to the back buffers must be released before the swap chain can be resized.
DXHandles::g_BackBuffers[i].Reset();
FrameSyncData::g_FrameFenceValues[i] = FrameSyncData::g_FrameFenceValues[DXHandles::g_CurrentBackBufferIndex];
}
// Re-create the swapchain
DXGI_SWAP_CHAIN_DESC swapChainDesc = {};
ThrowIfFailed(DXHandles::g_SwapChain->GetDesc(&swapChainDesc));
ThrowIfFailed(DXHandles::g_SwapChain->ResizeBuffers(g_NumFrames, AppGlobalSettings::g_ClientWidth, AppGlobalSettings::g_ClientHeight, swapChainDesc.BufferDesc.Format, swapChainDesc.Flags));
// Update the new back buffer to render onto
DXHandles::g_CurrentBackBufferIndex = DXHandles::g_SwapChain->GetCurrentBackBufferIndex();
// Update the RTVs
DXUtil::UpdateSwapchainRTVs();
}
}
void Run()
{
auto start = std::chrono::high_resolution_clock::now();
Update(g_DeltaTimeMs);
Render();
g_FrameCounter++;
auto stop = std::chrono::high_resolution_clock::now();
std::chrono::duration<float, std::milli> deltaTime = (stop - start);
g_DeltaTimeMs = deltaTime.count();
g_ElapsedSeconds += g_DeltaTimeMs / 1000.0f;
g_FPS = uint32_t(1000 / g_DeltaTimeMs);
}
} // namespace App
//----------------------------------------------------------------
int main(int argc, char** argv)
{
TIME_STAMP_BEGIN("App total execution time");
{
OSUtil::ParseCommandLineArguments();
HINSTANCE hinstance = GetModuleHandle(NULL);
OSUtil::RegisterWindowClass(hinstance);
DXHandles::g_HWND = OSUtil::CreateWindow(hinstance, L"Welcome to Dixy12!", AppGlobalSettings::g_ClientWidth, AppGlobalSettings::g_ClientHeight);
// Initialize the global window rect variable.
::GetWindowRect(DXHandles::g_HWND, &DXHandles::g_WindowRect);
// Debug Layer
#ifdef _DEBUG
LOG(WARNING, "Enabling Debug Validation Layer...");
DXUtil::EnableDebugLayer();
#endif
// Select the Best Physical Device (adapter) and create the actual Device (similar to VK Physical Device and Device)
LOG(INFO, "Selecting Best GPU... \n \t with DX12.2 and highest VRAM (Dedicated GPU is preferred)!");
ComPtr<IDXGIAdapter4> bestGPUAdapter = DXUtil::SelectBestGPUAdapter(AppGlobalSettings::g_UseWarp);
// Create the device now
LOG(INFO, "Creating D3D12 Device...");
DXHandles::g_Device = DXUtil::CreateDeviceFromAdapter(bestGPUAdapter);
// Create the command queue
D3D12_COMMAND_QUEUE_DESC desc = {};
desc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
desc.Priority = D3D12_COMMAND_QUEUE_PRIORITY_NORMAL;
desc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
desc.NodeMask = 0;
ThrowIfFailed(DXHandles::g_Device->CreateCommandQueue(&desc, IID_PPV_ARGS(&DXHandles::g_CommandQueue)));
// Create swapchain
{
DXHandles::g_SwapChain = DXUtil::CreateSwapchain(DXHandles::g_HWND, DXHandles::g_CommandQueue, AppGlobalSettings::g_ClientWidth, AppGlobalSettings::g_ClientHeight, g_NumFrames);
// Get the first back buffer to render onto.
DXHandles::g_CurrentBackBufferIndex = DXHandles::g_SwapChain->GetCurrentBackBufferIndex();
}
// Create the RTVs using the descriptor heaps
{
DXHandles::g_RTVDescriptorHeap = DXUtil::CreateDescriptorHeap(D3D12_DESCRIPTOR_HEAP_TYPE_RTV, g_NumFrames); // One per swap chain image
DXHandles::g_RTVDescriptorSize = DXHandles::g_Device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
DXUtil::UpdateSwapchainRTVs();
}
// Create the command allocator to allocate command list from
for (int i = 0; i < g_NumFrames; ++i)
DXHandles::g_CommandAllocators[i] = DXUtil::CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT);
// First create from first allocator, later when we reset the apt allocator is used as the right memory backing.
DXHandles::g_CommandList = DXUtil::CreateCommandList(DXHandles::g_CommandAllocators[DXHandles::g_CurrentBackBufferIndex], D3D12_COMMAND_LIST_TYPE_DIRECT);
// Create Sync Primitives
{
FrameSyncData::g_Fence = DXUtil::CreateFence();
FrameSyncData::g_FenceEvent = DXUtil::CreateFenceEventHandle();
}
AppGlobalSettings::g_IsInitialized = true;
::ShowWindow(DXHandles::g_HWND, SW_SHOW);
// Start the App life cycle
MSG msg = {};
while (msg.message != WM_QUIT) {
if (::PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
::TranslateMessage(&msg);
::DispatchMessage(&msg);
}
}
}
TIME_STAMP_END();
// Make sure the command queue has finished all commands before closing.
DXUtil::Flush(DXHandles::g_CommandQueue, FrameSyncData::g_Fence, FrameSyncData::g_FenceValue, FrameSyncData::g_FenceEvent);
::CloseHandle(FrameSyncData::g_FenceEvent);
return EXIT_SUCCESS;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment