From 6765e41bdbd5a3b9cfcedee30f9864eb885fceb5 Mon Sep 17 00:00:00 2001 From: Eero Holmala Date: Wed, 26 Apr 2023 15:20:23 +0300 Subject: [PATCH] Add rest of dx12tutorial --- dx12tutorial/CMakeLists.txt | 9 + dx12tutorial/CMakePresets.json | 61 ++ dx12tutorial/dx12tutorial.rc | 69 ++ dx12tutorial/dx12tutorial/Application.cpp | 598 ++++++++++++++++++ dx12tutorial/dx12tutorial/Application.h | 124 ++++ dx12tutorial/dx12tutorial/CMakeLists.txt | 36 ++ dx12tutorial/dx12tutorial/CommandQueue.cpp | 136 ++++ dx12tutorial/dx12tutorial/CommandQueue.h | 56 ++ dx12tutorial/dx12tutorial/DX12LibPCH.h | 44 ++ dx12tutorial/dx12tutorial/DirectX-Headers | 1 + dx12tutorial/dx12tutorial/Events.h | 190 ++++++ dx12tutorial/dx12tutorial/Game.cpp | 94 +++ dx12tutorial/dx12tutorial/Game.h | 114 ++++ dx12tutorial/dx12tutorial/Helpers.h | 14 + .../dx12tutorial/HighResolutionClock.cpp | 63 ++ .../dx12tutorial/HighResolutionClock.h | 39 ++ dx12tutorial/dx12tutorial/Keycodes.h | 214 +++++++ .../dx12tutorial/Shaders/PixelShader.hlsl | 9 + .../dx12tutorial/Shaders/VertexShader.hlsl | 28 + dx12tutorial/dx12tutorial/Tutorial2.cpp | 456 +++++++++++++ dx12tutorial/dx12tutorial/Tutorial2.h | 99 +++ dx12tutorial/dx12tutorial/Window.cpp | 366 +++++++++++ dx12tutorial/dx12tutorial/Window.h | 163 +++++ dx12tutorial/dx12tutorial/dx12tutorial.h | 8 + dx12tutorial/dx12tutorial/main.cpp | 42 ++ dx12tutorial/resource.h | 16 + 26 files changed, 3049 insertions(+) create mode 100644 dx12tutorial/CMakeLists.txt create mode 100644 dx12tutorial/CMakePresets.json create mode 100644 dx12tutorial/dx12tutorial.rc create mode 100644 dx12tutorial/dx12tutorial/Application.cpp create mode 100644 dx12tutorial/dx12tutorial/Application.h create mode 100644 dx12tutorial/dx12tutorial/CMakeLists.txt create mode 100644 dx12tutorial/dx12tutorial/CommandQueue.cpp create mode 100644 dx12tutorial/dx12tutorial/CommandQueue.h create mode 100644 dx12tutorial/dx12tutorial/DX12LibPCH.h create mode 160000 dx12tutorial/dx12tutorial/DirectX-Headers create mode 100644 dx12tutorial/dx12tutorial/Events.h create mode 100644 dx12tutorial/dx12tutorial/Game.cpp create mode 100644 dx12tutorial/dx12tutorial/Game.h create mode 100644 dx12tutorial/dx12tutorial/Helpers.h create mode 100644 dx12tutorial/dx12tutorial/HighResolutionClock.cpp create mode 100644 dx12tutorial/dx12tutorial/HighResolutionClock.h create mode 100644 dx12tutorial/dx12tutorial/Keycodes.h create mode 100644 dx12tutorial/dx12tutorial/Shaders/PixelShader.hlsl create mode 100644 dx12tutorial/dx12tutorial/Shaders/VertexShader.hlsl create mode 100644 dx12tutorial/dx12tutorial/Tutorial2.cpp create mode 100644 dx12tutorial/dx12tutorial/Tutorial2.h create mode 100644 dx12tutorial/dx12tutorial/Window.cpp create mode 100644 dx12tutorial/dx12tutorial/Window.h create mode 100644 dx12tutorial/dx12tutorial/dx12tutorial.h create mode 100644 dx12tutorial/dx12tutorial/main.cpp create mode 100644 dx12tutorial/resource.h diff --git a/dx12tutorial/CMakeLists.txt b/dx12tutorial/CMakeLists.txt new file mode 100644 index 0000000..11f182c --- /dev/null +++ b/dx12tutorial/CMakeLists.txt @@ -0,0 +1,9 @@ +# CMakeList.txt : Top-level CMake project file, do global configuration +# and include sub-projects here. +# +cmake_minimum_required (VERSION 3.8) + +project ("dx12tutorial") + +# Include sub-projects. +add_subdirectory ("dx12tutorial") diff --git a/dx12tutorial/CMakePresets.json b/dx12tutorial/CMakePresets.json new file mode 100644 index 0000000..abf4065 --- /dev/null +++ b/dx12tutorial/CMakePresets.json @@ -0,0 +1,61 @@ +{ + "version": 3, + "configurePresets": [ + { + "name": "windows-base", + "hidden": true, + "generator": "Ninja", + "binaryDir": "${sourceDir}/out/build/${presetName}", + "installDir": "${sourceDir}/out/install/${presetName}", + "cacheVariables": { + "CMAKE_C_COMPILER": "cl.exe", + "CMAKE_CXX_COMPILER": "cl.exe" + }, + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Windows" + } + }, + { + "name": "x64-debug", + "displayName": "x64 Debug", + "inherits": "windows-base", + "architecture": { + "value": "x64", + "strategy": "external" + }, + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug" + } + }, + { + "name": "x64-release", + "displayName": "x64 Release", + "inherits": "x64-debug", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release" + } + }, + { + "name": "x86-debug", + "displayName": "x86 Debug", + "inherits": "windows-base", + "architecture": { + "value": "x86", + "strategy": "external" + }, + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug" + } + }, + { + "name": "x86-release", + "displayName": "x86 Release", + "inherits": "x86-debug", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release" + } + } + ] +} diff --git a/dx12tutorial/dx12tutorial.rc b/dx12tutorial/dx12tutorial.rc new file mode 100644 index 0000000..94d32f7 --- /dev/null +++ b/dx12tutorial/dx12tutorial.rc @@ -0,0 +1,69 @@ +// Microsoft Visual C++ generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN +"resource.h\0" +END + +2 TEXTINCLUDE +BEGIN +"#include ""winres.h""\r\n" +"\0" +END + +3 TEXTINCLUDE +BEGIN +"\r\n" +"\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +APP_ICON ICON "Resources\\Icon\\app_icon.ico" + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED \ No newline at end of file diff --git a/dx12tutorial/dx12tutorial/Application.cpp b/dx12tutorial/dx12tutorial/Application.cpp new file mode 100644 index 0000000..3ccf813 --- /dev/null +++ b/dx12tutorial/dx12tutorial/Application.cpp @@ -0,0 +1,598 @@ +#include "DX12LibPCH.h" +#include "Application.h" +//#include "..\resource.h" + +#include "Game.h" +#include "CommandQueue.h" +#include "Window.h" + +constexpr wchar_t WINDOW_CLASS_NAME[] = L"DX12RenderWindowClass"; + +using WindowPtr = std::shared_ptr; +using WindowMap = std::map< HWND, WindowPtr >; +using WindowNameMap = std::map< std::wstring, WindowPtr >; + +static Application* gs_pSingelton = nullptr; +static WindowMap gs_Windows; +static WindowNameMap gs_WindowByName; + +static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam); + +// A wrapper struct to allow shared pointers for the window class. +struct MakeWindow : public Window +{ + MakeWindow(HWND hWnd, const std::wstring& windowName, int clientWidth, int clientHeight, bool vSync) + : Window(hWnd, windowName, clientWidth, clientHeight, vSync) + {} +}; + +Application::Application(HINSTANCE hInst) + : m_hInstance(hInst) + , m_TearingSupported(false) +{ + // Windows 10 Creators update adds Per Monitor V2 DPI awareness context. + // Using this awareness context allows the client area of the window + // to achieve 100% scaling while still allowing non-client window content to + // be rendered in a DPI sensitive fashion. + SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2); + +#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 debugInterface; + ThrowIfFailed(D3D12GetDebugInterface(IID_PPV_ARGS(&debugInterface))); + debugInterface->EnableDebugLayer(); +#endif + + WNDCLASSEXW wndClass = { 0 }; + + wndClass.cbSize = sizeof(WNDCLASSEX); + wndClass.style = CS_HREDRAW | CS_VREDRAW; + wndClass.lpfnWndProc = &WndProc; + wndClass.hInstance = m_hInstance; + wndClass.hCursor = LoadCursor(nullptr, IDC_ARROW); + wndClass.hIcon = nullptr;//LoadIcon(m_hInstance, MAKEINTRESOURCE(APP_ICON)); + wndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); + wndClass.lpszMenuName = nullptr; + wndClass.lpszClassName = WINDOW_CLASS_NAME; + wndClass.hIconSm = nullptr;//LoadIcon(m_hInstance, MAKEINTRESOURCE(APP_ICON)); + + if (!RegisterClassExW(&wndClass)) + { + MessageBoxA(NULL, "Unable to register the window class.", "Error", MB_OK | MB_ICONERROR); + } + + m_dxgiAdapter = GetAdapter(false); + if (m_dxgiAdapter) + { + m_d3d12Device = CreateDevice(m_dxgiAdapter); + } + if (m_d3d12Device) + { + m_DirectCommandQueue = std::make_shared(m_d3d12Device, D3D12_COMMAND_LIST_TYPE_DIRECT); + m_ComputeCommandQueue = std::make_shared(m_d3d12Device, D3D12_COMMAND_LIST_TYPE_COMPUTE); + m_CopyCommandQueue = std::make_shared(m_d3d12Device, D3D12_COMMAND_LIST_TYPE_COPY); + + m_TearingSupported = CheckTearingSupport(); + } +} + +void Application::Create(HINSTANCE hInst) +{ + if (!gs_pSingelton) + { + gs_pSingelton = new Application(hInst); + } +} + +Application& Application::Get() +{ + assert(gs_pSingelton); + return *gs_pSingelton; +} + +void Application::Destroy() +{ + if (gs_pSingelton) + { + assert(gs_Windows.empty() && gs_WindowByName.empty() && + "All windows should be destroyed before destroying the application instance."); + + delete gs_pSingelton; + gs_pSingelton = nullptr; + } +} + +Application::~Application() +{ + Flush(); +} + +Microsoft::WRL::ComPtr Application::GetAdapter(bool bUseWarp) +{ + ComPtr dxgiFactory; + UINT createFactoryFlags = 0; +#if defined(_DEBUG) + createFactoryFlags = DXGI_CREATE_FACTORY_DEBUG; +#endif + + ThrowIfFailed(CreateDXGIFactory2(createFactoryFlags, IID_PPV_ARGS(&dxgiFactory))); + + ComPtr dxgiAdapter1; + ComPtr dxgiAdapter4; + + if (bUseWarp) + { + ThrowIfFailed(dxgiFactory->EnumWarpAdapter(IID_PPV_ARGS(&dxgiAdapter1))); + ThrowIfFailed(dxgiAdapter1.As(&dxgiAdapter4)); + } + else + { + SIZE_T maxDedicatedVideoMemory = 0; + for (UINT i = 0; dxgiFactory->EnumAdapters1(i, &dxgiAdapter1) != DXGI_ERROR_NOT_FOUND; ++i) + { + DXGI_ADAPTER_DESC1 dxgiAdapterDesc1; + dxgiAdapter1->GetDesc1(&dxgiAdapterDesc1); + + // Check to see if the adapter can create a D3D12 device without actually + // creating it. The adapter with the largest dedicated video memory + // is favored. + if ((dxgiAdapterDesc1.Flags & DXGI_ADAPTER_FLAG_SOFTWARE) == 0 && + SUCCEEDED(D3D12CreateDevice(dxgiAdapter1.Get(), + D3D_FEATURE_LEVEL_11_0, __uuidof(ID3D12Device), nullptr)) && + dxgiAdapterDesc1.DedicatedVideoMemory > maxDedicatedVideoMemory) + { + maxDedicatedVideoMemory = dxgiAdapterDesc1.DedicatedVideoMemory; + ThrowIfFailed(dxgiAdapter1.As(&dxgiAdapter4)); + } + } + } + + return dxgiAdapter4; +} +Microsoft::WRL::ComPtr Application::CreateDevice(Microsoft::WRL::ComPtr adapter) +{ + ComPtr d3d12Device2; + ThrowIfFailed(D3D12CreateDevice(adapter.Get(), D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&d3d12Device2))); + // NAME_D3D12_OBJECT(d3d12Device2); + + // Enable debug messages in debug mode. +#if defined(_DEBUG) + ComPtr pInfoQueue; + if (SUCCEEDED(d3d12Device2.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 d3d12Device2; +} + +bool Application::CheckTearingSupport() +{ + BOOL allowTearing = FALSE; + + // Rather than create the DXGI 1.5 factory interface directly, we create the + // DXGI 1.4 interface and query for the 1.5 interface. This is to enable the + // graphics debugging tools which will not support the 1.5 factory interface + // until a future update. + ComPtr factory4; + if (SUCCEEDED(CreateDXGIFactory1(IID_PPV_ARGS(&factory4)))) + { + ComPtr factory5; + if (SUCCEEDED(factory4.As(&factory5))) + { + factory5->CheckFeatureSupport(DXGI_FEATURE_PRESENT_ALLOW_TEARING, + &allowTearing, sizeof(allowTearing)); + } + } + + return allowTearing == TRUE; +} + +bool Application::IsTearingSupported() const +{ + return m_TearingSupported; +} + +std::shared_ptr Application::CreateRenderWindow(const std::wstring& windowName, int clientWidth, int clientHeight, bool vSync) +{ + // First check if a window with the given name already exists. + WindowNameMap::iterator windowIter = gs_WindowByName.find(windowName); + if (windowIter != gs_WindowByName.end()) + { + return windowIter->second; + } + + RECT windowRect = { 0, 0, clientWidth, clientHeight }; + AdjustWindowRect(&windowRect, WS_OVERLAPPEDWINDOW, FALSE); + + HWND hWnd = CreateWindowW(WINDOW_CLASS_NAME, windowName.c_str(), + WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, + windowRect.right - windowRect.left, + windowRect.bottom - windowRect.top, + nullptr, nullptr, m_hInstance, nullptr); + + if (!hWnd) + { + MessageBoxA(NULL, "Could not create the render window.", "Error", MB_OK | MB_ICONERROR); + return nullptr; + } + + WindowPtr pWindow = std::make_shared(hWnd, windowName, clientWidth, clientHeight, vSync); + + gs_Windows.insert(WindowMap::value_type(hWnd, pWindow)); + gs_WindowByName.insert(WindowNameMap::value_type(windowName, pWindow)); + + return pWindow; +} + +void Application::DestroyWindow(std::shared_ptr window) +{ + if (window) window->Destroy(); +} + +void Application::DestroyWindow(const std::wstring& windowName) +{ + WindowPtr pWindow = GetWindowByName(windowName); + if (pWindow) + { + DestroyWindow(pWindow); + } +} + +std::shared_ptr Application::GetWindowByName(const std::wstring& windowName) +{ + std::shared_ptr window; + WindowNameMap::iterator iter = gs_WindowByName.find(windowName); + if (iter != gs_WindowByName.end()) + { + window = iter->second; + } + + return window; +} + + +int Application::Run(std::shared_ptr pGame) +{ + if (!pGame->Initialize()) return 1; + if (!pGame->LoadContent()) return 2; + + MSG msg = { 0 }; + while (msg.message != WM_QUIT) + { + if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } + + // Flush any commands in the commands queues before quiting. + Flush(); + + pGame->UnloadContent(); + pGame->Destroy(); + + return static_cast(msg.wParam); +} + +void Application::Quit(int exitCode) +{ + PostQuitMessage(exitCode); +} + +Microsoft::WRL::ComPtr Application::GetDevice() const +{ + return m_d3d12Device; +} + +std::shared_ptr Application::GetCommandQueue(D3D12_COMMAND_LIST_TYPE type) const +{ + std::shared_ptr commandQueue; + switch (type) + { + case D3D12_COMMAND_LIST_TYPE_DIRECT: + commandQueue = m_DirectCommandQueue; + break; + case D3D12_COMMAND_LIST_TYPE_COMPUTE: + commandQueue = m_ComputeCommandQueue; + break; + case D3D12_COMMAND_LIST_TYPE_COPY: + commandQueue = m_CopyCommandQueue; + break; + default: + assert(false && "Invalid command queue type."); + } + + return commandQueue; +} + +void Application::Flush() +{ + m_DirectCommandQueue->Flush(); + m_ComputeCommandQueue->Flush(); + m_CopyCommandQueue->Flush(); +} + +Microsoft::WRL::ComPtr Application::CreateDescriptorHeap(UINT numDescriptors, D3D12_DESCRIPTOR_HEAP_TYPE type) +{ + D3D12_DESCRIPTOR_HEAP_DESC desc = {}; + desc.Type = type; + desc.NumDescriptors = numDescriptors; + desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE; + desc.NodeMask = 0; + + Microsoft::WRL::ComPtr descriptorHeap; + ThrowIfFailed(m_d3d12Device->CreateDescriptorHeap(&desc, IID_PPV_ARGS(&descriptorHeap))); + + return descriptorHeap; +} + +UINT Application::GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE type) const +{ + return m_d3d12Device->GetDescriptorHandleIncrementSize(type); +} + + +// Remove a window from our window lists. +static void RemoveWindow(HWND hWnd) +{ + WindowMap::iterator windowIter = gs_Windows.find(hWnd); + if (windowIter != gs_Windows.end()) + { + WindowPtr pWindow = windowIter->second; + gs_WindowByName.erase(pWindow->GetWindowName()); + gs_Windows.erase(windowIter); + } +} + +// Convert the message ID into a MouseButton ID +MouseButtonEventArgs::MouseButton DecodeMouseButton(UINT messageID) +{ + MouseButtonEventArgs::MouseButton mouseButton = MouseButtonEventArgs::None; + switch (messageID) + { + case WM_LBUTTONDOWN: + case WM_LBUTTONUP: + case WM_LBUTTONDBLCLK: + { + mouseButton = MouseButtonEventArgs::Left; + } + break; + case WM_RBUTTONDOWN: + case WM_RBUTTONUP: + case WM_RBUTTONDBLCLK: + { + mouseButton = MouseButtonEventArgs::Right; + } + break; + case WM_MBUTTONDOWN: + case WM_MBUTTONUP: + case WM_MBUTTONDBLCLK: + { + mouseButton = MouseButtonEventArgs::Middel; + } + break; + } + + return mouseButton; +} + +static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + WindowPtr pWindow; + { + WindowMap::iterator iter = gs_Windows.find(hwnd); + if (iter != gs_Windows.end()) + { + pWindow = iter->second; + } + } + + if (pWindow) + { + switch (message) + { + case WM_PAINT: + { + // Delta time will be filled in by the Window. + UpdateEventArgs updateEventArgs(0.0f, 0.0f); + pWindow->OnUpdate(updateEventArgs); + RenderEventArgs renderEventArgs(0.0f, 0.0f); + // Delta time will be filled in by the Window. + pWindow->OnRender(renderEventArgs); + } + break; + case WM_SYSKEYDOWN: + case WM_KEYDOWN: + { + MSG charMsg; + // Get the Unicode character (UTF-16) + unsigned int c = 0; + // For printable characters, the next message will be WM_CHAR. + // This message contains the character code we need to send the KeyPressed event. + // Inspired by the SDL 1.2 implementation. + if (PeekMessage(&charMsg, hwnd, 0, 0, PM_NOREMOVE) && charMsg.message == WM_CHAR) + { + GetMessage(&charMsg, hwnd, 0, 0); + c = static_cast(charMsg.wParam); + } + bool shift = (GetAsyncKeyState(VK_SHIFT) & 0x8000) != 0; + bool control = (GetAsyncKeyState(VK_CONTROL) & 0x8000) != 0; + bool alt = (GetAsyncKeyState(VK_MENU) & 0x8000) != 0; + KeyCode::Key key = (KeyCode::Key)wParam; + unsigned int scanCode = (lParam & 0x00FF0000) >> 16; + KeyEventArgs keyEventArgs(key, c, KeyEventArgs::Pressed, shift, control, alt); + pWindow->OnKeyPressed(keyEventArgs); + } + break; + case WM_SYSKEYUP: + case WM_KEYUP: + { + bool shift = (GetAsyncKeyState(VK_SHIFT) & 0x8000) != 0; + bool control = (GetAsyncKeyState(VK_CONTROL) & 0x8000) != 0; + bool alt = (GetAsyncKeyState(VK_MENU) & 0x8000) != 0; + KeyCode::Key key = (KeyCode::Key)wParam; + unsigned int c = 0; + unsigned int scanCode = (lParam & 0x00FF0000) >> 16; + + // Determine which key was released by converting the key code and the scan code + // to a printable character (if possible). + // Inspired by the SDL 1.2 implementation. + unsigned char keyboardState[256]; + GetKeyboardState(keyboardState); + wchar_t translatedCharacters[4]; + if (int result = ToUnicodeEx(static_cast(wParam), scanCode, keyboardState, translatedCharacters, 4, 0, NULL) > 0) + { + c = translatedCharacters[0]; + } + + KeyEventArgs keyEventArgs(key, c, KeyEventArgs::Released, shift, control, alt); + pWindow->OnKeyReleased(keyEventArgs); + } + 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_MOUSEMOVE: + { + bool lButton = (wParam & MK_LBUTTON) != 0; + bool rButton = (wParam & MK_RBUTTON) != 0; + bool mButton = (wParam & MK_MBUTTON) != 0; + bool shift = (wParam & MK_SHIFT) != 0; + bool control = (wParam & MK_CONTROL) != 0; + + int x = ((int)(short)LOWORD(lParam)); + int y = ((int)(short)HIWORD(lParam)); + + MouseMotionEventArgs mouseMotionEventArgs(lButton, mButton, rButton, control, shift, x, y); + pWindow->OnMouseMoved(mouseMotionEventArgs); + } + break; + case WM_LBUTTONDOWN: + case WM_RBUTTONDOWN: + case WM_MBUTTONDOWN: + { + bool lButton = (wParam & MK_LBUTTON) != 0; + bool rButton = (wParam & MK_RBUTTON) != 0; + bool mButton = (wParam & MK_MBUTTON) != 0; + bool shift = (wParam & MK_SHIFT) != 0; + bool control = (wParam & MK_CONTROL) != 0; + + int x = ((int)(short)LOWORD(lParam)); + int y = ((int)(short)HIWORD(lParam)); + + MouseButtonEventArgs mouseButtonEventArgs(DecodeMouseButton(message), MouseButtonEventArgs::Pressed, lButton, mButton, rButton, control, shift, x, y); + pWindow->OnMouseButtonPressed(mouseButtonEventArgs); + } + break; + case WM_LBUTTONUP: + case WM_RBUTTONUP: + case WM_MBUTTONUP: + { + bool lButton = (wParam & MK_LBUTTON) != 0; + bool rButton = (wParam & MK_RBUTTON) != 0; + bool mButton = (wParam & MK_MBUTTON) != 0; + bool shift = (wParam & MK_SHIFT) != 0; + bool control = (wParam & MK_CONTROL) != 0; + + int x = ((int)(short)LOWORD(lParam)); + int y = ((int)(short)HIWORD(lParam)); + + MouseButtonEventArgs mouseButtonEventArgs(DecodeMouseButton(message), MouseButtonEventArgs::Released, lButton, mButton, rButton, control, shift, x, y); + pWindow->OnMouseButtonReleased(mouseButtonEventArgs); + } + break; + case WM_MOUSEWHEEL: + { + // The distance the mouse wheel is rotated. + // A positive value indicates the wheel was rotated to the right. + // A negative value indicates the wheel was rotated to the left. + float zDelta = ((int)(short)HIWORD(wParam)) / (float)WHEEL_DELTA; + short keyStates = (short)LOWORD(wParam); + + bool lButton = (keyStates & MK_LBUTTON) != 0; + bool rButton = (keyStates & MK_RBUTTON) != 0; + bool mButton = (keyStates & MK_MBUTTON) != 0; + bool shift = (keyStates & MK_SHIFT) != 0; + bool control = (keyStates & MK_CONTROL) != 0; + + int x = ((int)(short)LOWORD(lParam)); + int y = ((int)(short)HIWORD(lParam)); + + // Convert the screen coordinates to client coordinates. + POINT clientToScreenPoint; + clientToScreenPoint.x = x; + clientToScreenPoint.y = y; + ScreenToClient(hwnd, &clientToScreenPoint); + + MouseWheelEventArgs mouseWheelEventArgs(zDelta, lButton, mButton, rButton, control, shift, (int)clientToScreenPoint.x, (int)clientToScreenPoint.y); + pWindow->OnMouseWheel(mouseWheelEventArgs); + } + break; + case WM_SIZE: + { + int width = ((int)(short)LOWORD(lParam)); + int height = ((int)(short)HIWORD(lParam)); + + ResizeEventArgs resizeEventArgs(width, height); + pWindow->OnResize(resizeEventArgs); + } + break; + case WM_DESTROY: + { + // If a window is being destroyed, remove it from the + // window maps. + RemoveWindow(hwnd); + + if (gs_Windows.empty()) + { + // If there are no more windows, quit the application. + PostQuitMessage(0); + } + } + break; + default: + return DefWindowProcW(hwnd, message, wParam, lParam); + } + } + else + { + return DefWindowProcW(hwnd, message, wParam, lParam); + } + + return 0; +} \ No newline at end of file diff --git a/dx12tutorial/dx12tutorial/Application.h b/dx12tutorial/dx12tutorial/Application.h new file mode 100644 index 0000000..98cd8c1 --- /dev/null +++ b/dx12tutorial/dx12tutorial/Application.h @@ -0,0 +1,124 @@ +/** +* The application class is used to create windows for our application. +*/ +#pragma once + +#include +#include +#include + +#include +#include + +class Window; +class Game; +class CommandQueue; + +class Application +{ +public: + + /** + * Create the application singleton with the application instance handle. + */ + static void Create(HINSTANCE hInst); + + /** + * Destroy the application instance and all windows created by this application instance. + */ + static void Destroy(); + /** + * Get the application singleton. + */ + static Application& Get(); + + /** + * Check to see if VSync-off is supported. + */ + bool IsTearingSupported() const; + + /** + * Create a new DirectX11 render window instance. + * @param windowName The name of the window. This name will appear in the title bar of the window. This name should be unique. + * @param clientWidth The width (in pixels) of the window's client area. + * @param clientHeight The height (in pixels) of the window's client area. + * @param vSync Should the rendering be synchronized with the vertical refresh rate of the screen. + * @param windowed If true, the window will be created in windowed mode. If false, the window will be created full-screen. + * @returns The created window instance. If an error occurred while creating the window an invalid + * window instance is returned. If a window with the given name already exists, that window will be + * returned. + */ + std::shared_ptr CreateRenderWindow(const std::wstring& windowName, int clientWidth, int clientHeight, bool vSync = true); + + /** + * Destroy a window given the window name. + */ + void DestroyWindow(const std::wstring& windowName); + /** + * Destroy a window given the window reference. + */ + void DestroyWindow(std::shared_ptr window); + + /** + * Find a window by the window name. + */ + std::shared_ptr GetWindowByName(const std::wstring& windowName); + + /** + * Run the application loop and message pump. + * @return The error code if an error occurred. + */ + int Run(std::shared_ptr pGame); + + /** + * Request to quit the application and close all windows. + * @param exitCode The error code to return to the invoking process. + */ + void Quit(int exitCode = 0); + + /** + * Get the Direct3D 12 device + */ + Microsoft::WRL::ComPtr GetDevice() const; + /** + * Get a command queue. Valid types are: + * - D3D12_COMMAND_LIST_TYPE_DIRECT : Can be used for draw, dispatch, or copy commands. + * - D3D12_COMMAND_LIST_TYPE_COMPUTE: Can be used for dispatch or copy commands. + * - D3D12_COMMAND_LIST_TYPE_COPY : Can be used for copy commands. + */ + std::shared_ptr GetCommandQueue(D3D12_COMMAND_LIST_TYPE type = D3D12_COMMAND_LIST_TYPE_DIRECT) const; + + // Flush all command queues. + void Flush(); + + Microsoft::WRL::ComPtr CreateDescriptorHeap(UINT numDescriptors, D3D12_DESCRIPTOR_HEAP_TYPE type); + UINT GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE type) const; + +protected: + + // Create an application instance. + Application(HINSTANCE hInst); + // Destroy the application instance and all windows associated with this application. + virtual ~Application(); + + Microsoft::WRL::ComPtr GetAdapter(bool bUseWarp); + Microsoft::WRL::ComPtr CreateDevice(Microsoft::WRL::ComPtr adapter); + bool CheckTearingSupport(); + +private: + Application(const Application& copy) = delete; + Application& operator=(const Application& other) = delete; + + // The application instance handle that this application was created with. + HINSTANCE m_hInstance; + + Microsoft::WRL::ComPtr m_dxgiAdapter; + Microsoft::WRL::ComPtr m_d3d12Device; + + std::shared_ptr m_DirectCommandQueue; + std::shared_ptr m_ComputeCommandQueue; + std::shared_ptr m_CopyCommandQueue; + + bool m_TearingSupported; + +}; \ No newline at end of file diff --git a/dx12tutorial/dx12tutorial/CMakeLists.txt b/dx12tutorial/dx12tutorial/CMakeLists.txt new file mode 100644 index 0000000..373396b --- /dev/null +++ b/dx12tutorial/dx12tutorial/CMakeLists.txt @@ -0,0 +1,36 @@ +# CMakeList.txt : CMake project for dx12tutorial, include source and define +# project specific logic here. +# +cmake_minimum_required (VERSION 3.8) + +set( SHADER_FILES + Shaders/VertexShader.hlsl + Shaders/PixelShader.hlsl +) + +source_group( "Resources\\Shaders" FILES ${SHADER_FILES} ) + +set_source_files_properties( Shaders/VertexShader.hlsl PROPERTIES + VS_SHADER_TYPE Vertex + VS_SHADER_MODEL 5.1 +) + +set_source_files_properties( Shaders/PixelShader.hlsl PROPERTIES + VS_SHADER_TYPE Pixel + VS_SHADER_MODEL 5.1 +) + +add_subdirectory("DirectX-Headers") + +# Add source to this project's executable. +add_executable (dx12tutorial WIN32 "main.cpp" "Helpers.h" "CommandQueue.h" "CommandQueue.cpp" "Game.h" "Game.cpp" "Tutorial2.h" "Tutorial2.cpp" "Application.h" "Application.cpp" "Window.h" "Window.cpp" "Events.h" "Keycodes.h" "DX12LibPCH.h" "HighResolutionClock.h" "HighResolutionClock.cpp" ${SHADER_FILES}) + +target_include_directories(dx12tutorial PRIVATE "./DirectX-Headers/include/directx") +target_link_libraries(dx12tutorial PRIVATE Microsoft::DirectX-Guids Microsoft::DirectX-Headers) +target_link_libraries(dx12tutorial PRIVATE d3d12.lib dxgi.lib d3dcompiler.lib dxguid.lib Pathcch.lib Shlwapi.lib) + +if (CMAKE_VERSION VERSION_GREATER 3.12) + set_property(TARGET dx12tutorial PROPERTY CXX_STANDARD 20) +endif() + +# TODO: Add tests and install targets if needed. diff --git a/dx12tutorial/dx12tutorial/CommandQueue.cpp b/dx12tutorial/dx12tutorial/CommandQueue.cpp new file mode 100644 index 0000000..2b86d01 --- /dev/null +++ b/dx12tutorial/dx12tutorial/CommandQueue.cpp @@ -0,0 +1,136 @@ +#include "DX12LibPCH.h" + +#include "CommandQueue.h" + +CommandQueue::CommandQueue(Microsoft::WRL::ComPtr device, D3D12_COMMAND_LIST_TYPE type) + : m_FenceValue(0) + , m_CommandListType(type) + , m_d3d12Device(device) +{ + D3D12_COMMAND_QUEUE_DESC desc = {}; + desc.Type = type; + desc.Priority = D3D12_COMMAND_QUEUE_PRIORITY_NORMAL; + desc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE; + desc.NodeMask = 0; + + ThrowIfFailed(m_d3d12Device->CreateCommandQueue(&desc, IID_PPV_ARGS(&m_d3d12CommandQueue))); + ThrowIfFailed(m_d3d12Device->CreateFence(m_FenceValue, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&m_d3d12Fence))); + + m_FenceEvent = ::CreateEvent(NULL, FALSE, FALSE, NULL); + assert(m_FenceEvent && "Failed to create fence event handle."); +} + +CommandQueue::~CommandQueue() +{ +} + +uint64_t CommandQueue::Signal() +{ + uint64_t fenceValue = ++m_FenceValue; + m_d3d12CommandQueue->Signal(m_d3d12Fence.Get(), fenceValue); + return fenceValue; +} + +bool CommandQueue::IsFenceComplete(uint64_t fenceValue) +{ + return m_d3d12Fence->GetCompletedValue() >= fenceValue; +} + +void CommandQueue::WaitForFenceValue(uint64_t fenceValue) +{ + if (!IsFenceComplete(fenceValue)) + { + m_d3d12Fence->SetEventOnCompletion(fenceValue, m_FenceEvent); + ::WaitForSingleObject(m_FenceEvent, DWORD_MAX); + } +} + +void CommandQueue::Flush() +{ + WaitForFenceValue(Signal()); +} + +Microsoft::WRL::ComPtr CommandQueue::CreateCommandAllocator() +{ + Microsoft::WRL::ComPtr commandAllocator; + ThrowIfFailed(m_d3d12Device->CreateCommandAllocator(m_CommandListType, IID_PPV_ARGS(&commandAllocator))); + + return commandAllocator; +} + +Microsoft::WRL::ComPtr CommandQueue::CreateCommandList(Microsoft::WRL::ComPtr allocator) +{ + Microsoft::WRL::ComPtr commandList; + ThrowIfFailed(m_d3d12Device->CreateCommandList(0, m_CommandListType, allocator.Get(), nullptr, IID_PPV_ARGS(&commandList))); + + return commandList; +} + +Microsoft::WRL::ComPtr CommandQueue::GetCommandList() +{ + Microsoft::WRL::ComPtr commandAllocator; + Microsoft::WRL::ComPtr commandList; + + if (!m_CommandAllocatorQueue.empty() && IsFenceComplete(m_CommandAllocatorQueue.front().fenceValue)) + { + commandAllocator = m_CommandAllocatorQueue.front().commandAllocator; + m_CommandAllocatorQueue.pop(); + + ThrowIfFailed(commandAllocator->Reset()); + } + else + { + commandAllocator = CreateCommandAllocator(); + } + + if (!m_CommandListQueue.empty()) + { + commandList = m_CommandListQueue.front(); + m_CommandListQueue.pop(); + + ThrowIfFailed(commandList->Reset(commandAllocator.Get(), nullptr)); + } + else + { + commandList = CreateCommandList(commandAllocator); + } + + // Associate the command allocator with the command list so that it can be + // retrieved when the command list is executed. + ThrowIfFailed(commandList->SetPrivateDataInterface(__uuidof(ID3D12CommandAllocator), commandAllocator.Get())); + + return commandList; +} + +// Execute a command list. +// Returns the fence value to wait for for this command list. +uint64_t CommandQueue::ExecuteCommandList(Microsoft::WRL::ComPtr commandList) +{ + commandList->Close(); + + ID3D12CommandAllocator* commandAllocator; + UINT dataSize = sizeof(commandAllocator); + ThrowIfFailed(commandList->GetPrivateData(__uuidof(ID3D12CommandAllocator), &dataSize, &commandAllocator)); + + ID3D12CommandList* const ppCommandLists[] = { + commandList.Get() + }; + + m_d3d12CommandQueue->ExecuteCommandLists(1, ppCommandLists); + uint64_t fenceValue = Signal(); + + m_CommandAllocatorQueue.emplace(CommandAllocatorEntry{ fenceValue, commandAllocator }); + m_CommandListQueue.push(commandList); + + // The ownership of the command allocator has been transferred to the ComPtr + // in the command allocator queue. It is safe to release the reference + // in this temporary COM pointer here. + commandAllocator->Release(); + + return fenceValue; +} + +Microsoft::WRL::ComPtr CommandQueue::GetD3D12CommandQueue() const +{ + return m_d3d12CommandQueue; +} \ No newline at end of file diff --git a/dx12tutorial/dx12tutorial/CommandQueue.h b/dx12tutorial/dx12tutorial/CommandQueue.h new file mode 100644 index 0000000..ed66160 --- /dev/null +++ b/dx12tutorial/dx12tutorial/CommandQueue.h @@ -0,0 +1,56 @@ +/** + * Wrapper class for a ID3D12CommandQueue. + */ + +#pragma once + +#include // For ID3D12CommandQueue, ID3D12Device2, and ID3D12Fence +#include // For Microsoft::WRL::ComPtr + +#include // For uint64_t +#include // For std::queue + +class CommandQueue +{ +public: + CommandQueue(Microsoft::WRL::ComPtr device, D3D12_COMMAND_LIST_TYPE type); + virtual ~CommandQueue(); + + // Get an available command list from the command queue. + Microsoft::WRL::ComPtr GetCommandList(); + + // Execute a command list. + // Returns the fence value to wait for for this command list. + uint64_t ExecuteCommandList(Microsoft::WRL::ComPtr commandList); + + uint64_t Signal(); + bool IsFenceComplete(uint64_t fenceValue); + void WaitForFenceValue(uint64_t fenceValue); + void Flush(); + + Microsoft::WRL::ComPtr GetD3D12CommandQueue() const; +protected: + + Microsoft::WRL::ComPtr CreateCommandAllocator(); + Microsoft::WRL::ComPtr CreateCommandList(Microsoft::WRL::ComPtr allocator); +private: + // Keep track of command allocators that are "in-flight" + struct CommandAllocatorEntry + { + uint64_t fenceValue; + Microsoft::WRL::ComPtr commandAllocator; + }; + + using CommandAllocatorQueue = std::queue; + using CommandListQueue = std::queue< Microsoft::WRL::ComPtr >; + + D3D12_COMMAND_LIST_TYPE m_CommandListType; + Microsoft::WRL::ComPtr m_d3d12Device; + Microsoft::WRL::ComPtr m_d3d12CommandQueue; + Microsoft::WRL::ComPtr m_d3d12Fence; + HANDLE m_FenceEvent; + uint64_t m_FenceValue; + + CommandAllocatorQueue m_CommandAllocatorQueue; + CommandListQueue m_CommandListQueue; +}; \ No newline at end of file diff --git a/dx12tutorial/dx12tutorial/DX12LibPCH.h b/dx12tutorial/dx12tutorial/DX12LibPCH.h new file mode 100644 index 0000000..adc144e --- /dev/null +++ b/dx12tutorial/dx12tutorial/DX12LibPCH.h @@ -0,0 +1,44 @@ +#pragma once + +#define WIN32_LEAN_AND_MEAN +#include +#include // For CommandLineToArgvW + +// The min/max macros conflict with like-named member functions. +// Only use std::min and std::max defined in . +#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 +using namespace Microsoft::WRL; + +// DirectX 12 specific headers. +#include +#include +#include +#include + +// D3D12 extension library. +#include + +// STL Headers +#include +#include +#include +#include +#include + +// Helper functions +#include "Helpers.h" \ No newline at end of file diff --git a/dx12tutorial/dx12tutorial/DirectX-Headers b/dx12tutorial/dx12tutorial/DirectX-Headers new file mode 160000 index 0000000..2b8cd08 --- /dev/null +++ b/dx12tutorial/dx12tutorial/DirectX-Headers @@ -0,0 +1 @@ +Subproject commit 2b8cd08a930a39ce27245d072ab4baf54a696d12 diff --git a/dx12tutorial/dx12tutorial/Events.h b/dx12tutorial/dx12tutorial/Events.h new file mode 100644 index 0000000..4b330bd --- /dev/null +++ b/dx12tutorial/dx12tutorial/Events.h @@ -0,0 +1,190 @@ +#pragma once +#include "KeyCodes.h" + +// Base class for all event args +class EventArgs +{ +public: + EventArgs() + {} + +}; + +class KeyEventArgs : public EventArgs +{ +public: + enum KeyState + { + Released = 0, + Pressed = 1 + }; + + typedef EventArgs base; + KeyEventArgs(KeyCode::Key key, unsigned int c, KeyState state, bool control, bool shift, bool alt) + : Key(key) + , Char(c) + , State(state) + , Control(control) + , Shift(shift) + , Alt(alt) + {} + + KeyCode::Key Key; // The Key Code that was pressed or released. + unsigned int Char; // The 32-bit character code that was pressed. This value will be 0 if it is a non-printable character. + KeyState State; // Was the key pressed or released? + bool Control;// Is the Control modifier pressed + bool Shift; // Is the Shift modifier pressed + bool Alt; // Is the Alt modifier pressed +}; + +class MouseMotionEventArgs : public EventArgs +{ +public: + typedef EventArgs base; + MouseMotionEventArgs(bool leftButton, bool middleButton, bool rightButton, bool control, bool shift, int x, int y) + : LeftButton(leftButton) + , MiddleButton(middleButton) + , RightButton(rightButton) + , Control(control) + , Shift(shift) + , X(x) + , Y(y) + {} + + bool LeftButton; // Is the left mouse button down? + bool MiddleButton; // Is the middle mouse button down? + bool RightButton; // Is the right mouse button down? + bool Control; // Is the CTRL key down? + bool Shift; // Is the Shift key down? + + int X; // The X-position of the cursor relative to the upper-left corner of the client area. + int Y; // The Y-position of the cursor relative to the upper-left corner of the client area. + int RelX; // How far the mouse moved since the last event. + int RelY; // How far the mouse moved since the last event. + +}; + +class MouseButtonEventArgs : public EventArgs +{ +public: + enum MouseButton + { + None = 0, + Left = 1, + Right = 2, + Middel = 3 + }; + enum ButtonState + { + Released = 0, + Pressed = 1 + }; + + typedef EventArgs base; + MouseButtonEventArgs(MouseButton buttonID, ButtonState state, bool leftButton, bool middleButton, bool rightButton, bool control, bool shift, int x, int y) + : Button(buttonID) + , State(state) + , LeftButton(leftButton) + , MiddleButton(middleButton) + , RightButton(rightButton) + , Control(control) + , Shift(shift) + , X(x) + , Y(y) + {} + + MouseButton Button; // The mouse button that was pressed or released. + ButtonState State; // Was the button pressed or released? + bool LeftButton; // Is the left mouse button down? + bool MiddleButton; // Is the middle mouse button down? + bool RightButton; // Is the right mouse button down? + bool Control; // Is the CTRL key down? + bool Shift; // Is the Shift key down? + + int X; // The X-position of the cursor relative to the upper-left corner of the client area. + int Y; // The Y-position of the cursor relative to the upper-left corner of the client area. +}; + +class MouseWheelEventArgs : public EventArgs +{ +public: + typedef EventArgs base; + MouseWheelEventArgs(float wheelDelta, bool leftButton, bool middleButton, bool rightButton, bool control, bool shift, int x, int y) + : WheelDelta(wheelDelta) + , LeftButton(leftButton) + , MiddleButton(middleButton) + , RightButton(rightButton) + , Control(control) + , Shift(shift) + , X(x) + , Y(y) + {} + + float WheelDelta; // How much the mouse wheel has moved. A positive value indicates that the wheel was moved to the right. A negative value indicates the wheel was moved to the left. + bool LeftButton; // Is the left mouse button down? + bool MiddleButton; // Is the middle mouse button down? + bool RightButton; // Is the right mouse button down? + bool Control; // Is the CTRL key down? + bool Shift; // Is the Shift key down? + + int X; // The X-position of the cursor relative to the upper-left corner of the client area. + int Y; // The Y-position of the cursor relative to the upper-left corner of the client area. + +}; + +class ResizeEventArgs : public EventArgs +{ +public: + typedef EventArgs base; + ResizeEventArgs(int width, int height) + : Width(width) + , Height(height) + {} + + // The new width of the window + int Width; + // The new height of the window. + int Height; + +}; + +class UpdateEventArgs : public EventArgs +{ +public: + typedef EventArgs base; + UpdateEventArgs(double fDeltaTime, double fTotalTime) + : ElapsedTime(fDeltaTime) + , TotalTime(fTotalTime) + {} + + double ElapsedTime; + double TotalTime; +}; + +class RenderEventArgs : public EventArgs +{ +public: + typedef EventArgs base; + RenderEventArgs(double fDeltaTime, double fTotalTime) + : ElapsedTime(fDeltaTime) + , TotalTime(fTotalTime) + {} + + double ElapsedTime; + double TotalTime; +}; + +class UserEventArgs : public EventArgs +{ +public: + typedef EventArgs base; + UserEventArgs(int code, void* data1, void* data2) + : Code(code) + , Data1(data1) + , Data2(data2) + {} + + int Code; + void* Data1; + void* Data2; +}; \ No newline at end of file diff --git a/dx12tutorial/dx12tutorial/Game.cpp b/dx12tutorial/dx12tutorial/Game.cpp new file mode 100644 index 0000000..f5f68df --- /dev/null +++ b/dx12tutorial/dx12tutorial/Game.cpp @@ -0,0 +1,94 @@ +#include "DX12LibPCH.h" + +#include "Application.h" +#include "Game.h" +#include "Window.h" + +Game::Game(const std::wstring& name, int width, int height, bool vSync) + : m_Name(name) + , m_Width(width) + , m_Height(height) + , m_vSync(vSync) +{ +} + +Game::~Game() +{ + assert(!m_pWindow && "Use Game::Destroy() before destruction."); +} + +bool Game::Initialize() +{ + // Check for DirectX Math library support. + if (!DirectX::XMVerifyCPUSupport()) + { + MessageBoxA(NULL, "Failed to verify DirectX Math library support.", "Error", MB_OK | MB_ICONERROR); + return false; + } + + m_pWindow = Application::Get().CreateRenderWindow(m_Name, m_Width, m_Height, m_vSync); + m_pWindow->RegisterCallbacks(shared_from_this()); + m_pWindow->Show(); + + return true; +} + +void Game::Destroy() +{ + Application::Get().DestroyWindow(m_pWindow); + m_pWindow.reset(); +} + +void Game::OnUpdate(UpdateEventArgs& e) +{ + +} + +void Game::OnRender(RenderEventArgs& e) +{ + +} + +void Game::OnKeyPressed(KeyEventArgs& e) +{ + // By default, do nothing. +} + +void Game::OnKeyReleased(KeyEventArgs& e) +{ + // By default, do nothing. +} + +void Game::OnMouseMoved(class MouseMotionEventArgs& e) +{ + // By default, do nothing. +} + +void Game::OnMouseButtonPressed(MouseButtonEventArgs& e) +{ + // By default, do nothing. +} + +void Game::OnMouseButtonReleased(MouseButtonEventArgs& e) +{ + // By default, do nothing. +} + +void Game::OnMouseWheel(MouseWheelEventArgs& e) +{ + // By default, do nothing. +} + +void Game::OnResize(ResizeEventArgs& e) +{ + m_Width = e.Width; + m_Height = e.Height; +} + +void Game::OnWindowDestroy() +{ + // If the Window which we are registered to is + // destroyed, then any resources which are associated + // to the window must be released. + UnloadContent(); +} \ No newline at end of file diff --git a/dx12tutorial/dx12tutorial/Game.h b/dx12tutorial/dx12tutorial/Game.h new file mode 100644 index 0000000..32edd4a --- /dev/null +++ b/dx12tutorial/dx12tutorial/Game.h @@ -0,0 +1,114 @@ +/** + * @brief The Game class is the abstract base class for DirecX 12 demos. + */ +#pragma once + +#include "Events.h" + +#include +#include + +class Window; + +class Game : public std::enable_shared_from_this +{ +public: + /** + * Create the DirectX demo using the specified window dimensions. + */ + Game(const std::wstring& name, int width, int height, bool vSync); + virtual ~Game(); + + int GetClientWidth() const + { + return m_Width; + } + + int GetClientHeight() const + { + return m_Height; + } + + /** + * Initialize the DirectX Runtime. + */ + virtual bool Initialize(); + + /** + * Load content required for the demo. + */ + virtual bool LoadContent() = 0; + + /** + * Unload demo specific content that was loaded in LoadContent. + */ + virtual void UnloadContent() = 0; + + /** + * Destroy any resource that are used by the game. + */ + virtual void Destroy(); + +protected: + friend class Window; + + /** + * Update the game logic. + */ + virtual void OnUpdate(UpdateEventArgs& e); + + /** + * Render stuff. + */ + virtual void OnRender(RenderEventArgs& e); + + /** + * Invoked by the registered window when a key is pressed + * while the window has focus. + */ + virtual void OnKeyPressed(KeyEventArgs& e); + + /** + * Invoked when a key on the keyboard is released. + */ + virtual void OnKeyReleased(KeyEventArgs& e); + + /** + * Invoked when the mouse is moved over the registered window. + */ + virtual void OnMouseMoved(MouseMotionEventArgs& e); + + /** + * Invoked when a mouse button is pressed over the registered window. + */ + virtual void OnMouseButtonPressed(MouseButtonEventArgs& e); + + /** + * Invoked when a mouse button is released over the registered window. + */ + virtual void OnMouseButtonReleased(MouseButtonEventArgs& e); + + /** + * Invoked when the mouse wheel is scrolled while the registered window has focus. + */ + virtual void OnMouseWheel(MouseWheelEventArgs& e); + + /** + * Invoked when the attached window is resized. + */ + virtual void OnResize(ResizeEventArgs& e); + + /** + * Invoked when the registered window instance is destroyed. + */ + virtual void OnWindowDestroy(); + + std::shared_ptr m_pWindow; + +private: + std::wstring m_Name; + int m_Width; + int m_Height; + bool m_vSync; + +}; \ No newline at end of file diff --git a/dx12tutorial/dx12tutorial/Helpers.h b/dx12tutorial/dx12tutorial/Helpers.h new file mode 100644 index 0000000..1287a45 --- /dev/null +++ b/dx12tutorial/dx12tutorial/Helpers.h @@ -0,0 +1,14 @@ +#pragma once + +#define WIN32_LEAN_AND_MEAN +#include // For HRESULT + +// From DXSampleHelper.h +// Source: https://github.com/Microsoft/DirectX-Graphics-Samples +inline void ThrowIfFailed(HRESULT hr) +{ + if (FAILED(hr)) + { + throw std::exception(); + } +} \ No newline at end of file diff --git a/dx12tutorial/dx12tutorial/HighResolutionClock.cpp b/dx12tutorial/dx12tutorial/HighResolutionClock.cpp new file mode 100644 index 0000000..2d0370c --- /dev/null +++ b/dx12tutorial/dx12tutorial/HighResolutionClock.cpp @@ -0,0 +1,63 @@ +#include "DX12LibPCH.h" +#include "HighResolutionClock.h" + +HighResolutionClock::HighResolutionClock() + : m_DeltaTime(0) + , m_TotalTime(0) +{ + m_T0 = std::chrono::high_resolution_clock::now(); +} + +void HighResolutionClock::Tick() +{ + auto t1 = std::chrono::high_resolution_clock::now(); + m_DeltaTime = t1 - m_T0; + m_TotalTime += m_DeltaTime; + m_T0 = t1; +} + +void HighResolutionClock::Reset() +{ + m_T0 = std::chrono::high_resolution_clock::now(); + m_DeltaTime = std::chrono::high_resolution_clock::duration(); + m_TotalTime = std::chrono::high_resolution_clock::duration(); +} + +double HighResolutionClock::GetDeltaNanoseconds() const +{ + return m_DeltaTime.count() * 1.0; +} +double HighResolutionClock::GetDeltaMicroseconds() const +{ + return m_DeltaTime.count() * 1e-3; +} + +double HighResolutionClock::GetDeltaMilliseconds() const +{ + return m_DeltaTime.count() * 1e-6; +} + +double HighResolutionClock::GetDeltaSeconds() const +{ + return m_DeltaTime.count() * 1e-9; +} + +double HighResolutionClock::GetTotalNanoseconds() const +{ + return m_TotalTime.count() * 1.0; +} + +double HighResolutionClock::GetTotalMicroseconds() const +{ + return m_TotalTime.count() * 1e-3; +} + +double HighResolutionClock::GetTotalMilliSeconds() const +{ + return m_TotalTime.count() * 1e-6; +} + +double HighResolutionClock::GetTotalSeconds() const +{ + return m_TotalTime.count() * 1e-9; +} \ No newline at end of file diff --git a/dx12tutorial/dx12tutorial/HighResolutionClock.h b/dx12tutorial/dx12tutorial/HighResolutionClock.h new file mode 100644 index 0000000..7ca9166 --- /dev/null +++ b/dx12tutorial/dx12tutorial/HighResolutionClock.h @@ -0,0 +1,39 @@ +/** + * High resolution clock used for performing timings. + */ + +#pragma once + +#include + +class HighResolutionClock +{ +public: + HighResolutionClock(); + + // Tick the high resolution clock. + // Tick the clock before reading the delta time for the first time. + // Only tick the clock once per frame. + // Use the Get* functions to return the elapsed time between ticks. + void Tick(); + + // Reset the clock. + void Reset(); + + double GetDeltaNanoseconds() const; + double GetDeltaMicroseconds() const; + double GetDeltaMilliseconds() const; + double GetDeltaSeconds() const; + + double GetTotalNanoseconds() const; + double GetTotalMicroseconds() const; + double GetTotalMilliSeconds() const; + double GetTotalSeconds() const; + +private: + // Initial time point. + std::chrono::high_resolution_clock::time_point m_T0; + // Time since last tick. + std::chrono::high_resolution_clock::duration m_DeltaTime; + std::chrono::high_resolution_clock::duration m_TotalTime; +}; \ No newline at end of file diff --git a/dx12tutorial/dx12tutorial/Keycodes.h b/dx12tutorial/dx12tutorial/Keycodes.h new file mode 100644 index 0000000..ec11fa0 --- /dev/null +++ b/dx12tutorial/dx12tutorial/Keycodes.h @@ -0,0 +1,214 @@ +#pragma once + +// Key code values for Windows +namespace KeyCode +{ + enum Key + { + None = 0x00, // No key was pressed + LButton = 0x01, // Left mouse button + RButton = 0x02, // Right mouse button + Cancel = 0x03, // Cancel key + MButton = 0x04, // Middle mouse button + XButton1 = 0x05, // X1 mouse button + XButton2 = 0x06, // X2 mouse button + // 0x07 is undefined + Back = 0x08, + Tab = 0x09, + // 0x0A-0B are reserved + Clear = 0x0c, // The CLEAR key + Enter = 0x0d, // The Enter key + // 0x0E-0F are undefined + ShiftKey = 0x10, // The Shift key + ControlKey = 0x11, // The Ctrl key + AltKey = 0x12, // The Alt key + Pause = 0x13, // The Pause key + Capital = 0x14, // The Caps Lock key + CapsLock = 0x14, // The Caps Lock key + KanaMode = 0x15, // IMI Kana mode + HanguelMode = 0x15, // IMI Hanguel mode (Use HangulMode) + HangulMode = 0x15, // IMI Hangul mode + // 0x16 is undefined + JunjaMode = 0x17, // IMI Janja mode + FinalMode = 0x18, // IMI Final mode + HanjaMode = 0x19, // IMI Hanja mode + KanjiMode = 0x19, // IMI Kanji mode + // 0x1A is undefined + Escape = 0x1b, // The ESC key + IMEConvert = 0x1c, // IMI convert key + IMINoConvert = 0x1d, // IMI noconvert key + IMEAccept = 0x1e, // IMI accept key + IMIModeChange = 0x1f, // IMI mode change key + Space = 0x20, // The Space key + Prior = 0x21, // The Page Up key + PageUp = 0x21, // The Page Up key + Next = 0x22, // The Page Down key + PageDown = 0x22, // The Page Down key + End = 0x23, // The End key + Home = 0x24, // The Home key + Left = 0x25, // The Left arrow key + Up = 0x26, // The Up arrow key + Right = 0x27, // The Right arrow key + Down = 0x28, // The Down arrow key + Select = 0x29, // The Select key + Print = 0x2a, // The Print key + Execute = 0x2b, // The Execute key + PrintScreen = 0x2c, // The Print Screen key + Snapshot = 0x2c, // The Print Screen key + Insert = 0x2d, // The Insert key + Delete = 0x2e, // The Delete key + Help = 0x2f, // The Help key + D0 = 0x30, // 0 + D1 = 0x31, // 1 + D2 = 0x32, // 2 + D3 = 0x33, // 3 + D4 = 0x34, // 4 + D5 = 0x35, // 5 + D6 = 0x36, // 6 + D7 = 0x37, // 7 + D8 = 0x38, // 8 + D9 = 0x39, // 9 + // 0x3A - 40 are undefined + A = 0x41, // A + B = 0x42, // B + C = 0x43, // C + D = 0x44, // D + E = 0x45, // E + F = 0x46, // F + G = 0x47, // G + H = 0x48, // H + I = 0x49, // I + J = 0x4a, // J + K = 0x4b, // K + L = 0x4c, // L + M = 0x4d, // M + N = 0x4e, // N + O = 0x4f, // O + P = 0x50, // P + Q = 0x51, // Q + R = 0x52, // R + S = 0x53, // S + T = 0x54, // T + U = 0x55, // U + V = 0x56, // V + W = 0x57, // W + X = 0x58, // X + Y = 0x59, // Y + Z = 0x5a, // Z + LWin = 0x5b, // Left Windows key + RWin = 0x5c, // Right Windows key + Apps = 0x5d, // Apps key + // 0x5E is reserved + Sleep = 0x5f, // The Sleep key + NumPad0 = 0x60, // The Numeric keypad 0 key + NumPad1 = 0x61, // The Numeric keypad 1 key + NumPad2 = 0x62, // The Numeric keypad 2 key + NumPad3 = 0x63, // The Numeric keypad 3 key + NumPad4 = 0x64, // The Numeric keypad 4 key + NumPad5 = 0x65, // The Numeric keypad 5 key + NumPad6 = 0x66, // The Numeric keypad 6 key + NumPad7 = 0x67, // The Numeric keypad 7 key + NumPad8 = 0x68, // The Numeric keypad 8 key + NumPad9 = 0x69, // The Numeric keypad 9 key + Multiply = 0x6a, // The Multiply key + Add = 0x6b, // The Add key + Separator = 0x6c, // The Separator key + Subtract = 0x6d, // The Subtract key + Decimal = 0x6e, // The Decimal key + Divide = 0x6f, // The Divide key + F1 = 0x70, // The F1 key + F2 = 0x71, // The F2 key + F3 = 0x72, // The F3 key + F4 = 0x73, // The F4 key + F5 = 0x74, // The F5 key + F6 = 0x75, // The F6 key + F7 = 0x76, // The F7 key + F8 = 0x77, // The F8 key + F9 = 0x78, // The F9 key + F10 = 0x79, // The F10 key + F11 = 0x7a, // The F11 key + F12 = 0x7b, // The F12 key + F13 = 0x7c, // The F13 key + F14 = 0x7d, // The F14 key + F15 = 0x7e, // The F15 key + F16 = 0x7f, // The F16 key + F17 = 0x80, // The F17 key + F18 = 0x81, // The F18 key + F19 = 0x82, // The F19 key + F20 = 0x83, // The F20 key + F21 = 0x84, // The F21 key + F22 = 0x85, // The F22 key + F23 = 0x86, // The F23 key + F24 = 0x87, // The F24 key + // 0x88 - 0x8f are unassigned + NumLock = 0x90, // The Num Lock key + Scroll = 0x91, // The Scroll Lock key + // 0x92 - 96 are OEM specific + // 0x97 - 9f are unassigned + LShiftKey = 0xa0, // The Left Shift key + RShiftKey = 0xa1, // The Right Shift key + LControlKey = 0xa2, // The Left Control key + RControlKey = 0xa3, // The Right Control key + LMenu = 0xa4, // The Left Alt key + RMenu = 0xa5, // The Right Alt key + BrowserBack = 0xa6, // The Browser Back key + BrowserForward = 0xa7, // The Browser Forward key + BrowserRefresh = 0xa8, // The Browser Refresh key + BrowserStop = 0xa9, // The Browser Stop key + BrowserSearch = 0xaa, // The Browser Search key + BrowserFavorites = 0xab, // The Browser Favorites key + BrowserHome = 0xac, // The Browser Home key + VolumeMute = 0xad, // The Volume Mute key + VolumeDown = 0xae, // The Volume Down key + VolumeUp = 0xaf, // The Volume Up key + MediaNextTrack = 0xb0, // The Next Track key + MediaPreviousTrack = 0xb1, // The Previous Track key + MediaStop = 0xb2, // The Stop Media key + MediaPlayPause = 0xb3, // The Play/Pause Media key + LaunchMail = 0xb4, // The Start Mail key + SelectMedia = 0xb5, // The Select Media key + LaunchApplication1 = 0xb6, // The Launch Application 1 key. + LaunchApplication2 = 0xb7, // The Launch Application 2 key. + // 0xB8 - B9 are reserved + OemSemicolon = 0xba, // Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the ';:' key + Oem1 = 0xba, // Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the ';:' key + OemPlus = 0xbb, // For any country/region, the '+' key + OemComma = 0xbc, // For any country/region, the ',' key + OemMinus = 0xbd, // For any country/region, the '-' key + OemPeriod = 0xbe, // For any country/region, the '.' key + OemQuestion = 0xbf, // Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the '/?' key + Oem2 = 0xbf, // Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the '/?' key + OemTilde = 0xc0, // Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the '`~' key + Oem3 = 0xc0, // Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the '`~' key + // 0xC1 - D7 are reserved + // 0xD8 - DA are unassigned + OemOpenBrackets = 0xdb, // Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the '[{' key + Oem4 = 0xdb, // Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the '[{' key + OemPipe = 0xdc, // Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the '\|' key + Oem5 = 0xdc, // Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the '\|' key + OemCloseBrackets = 0xdd, // Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the ']}' key + Oem6 = 0xdd, // Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the ']}' key + OemQuotes = 0xde, // Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the 'single-quote/double-quote' key + Oem7 = 0xde, // Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the 'single-quote/double-quote' key + Oem8 = 0xdf, // Used for miscellaneous characters; it can vary by keyboard. + // 0xE0 is reserved + // 0xE1 is OEM specific + OemBackslash = 0xe2, // Either the angle bracket key or the backslash key on the RT 102-key keyboard + Oem102 = 0xe2, // Either the angle bracket key or the backslash key on the RT 102-key keyboard + // 0xE3 - E4 OEM specific + ProcessKey = 0xe5, // IME Process key + // 0xE6 is OEM specific + Packet = 0xe7, // Used to pass Unicode characters as if they were keystrokes. The Packet key value is the low word of a 32-bit virtual-key value used for non-keyboard input methods. + // 0xE8 is unassigned + // 0xE9 - F5 OEM specific + Attn = 0xf6, // The Attn key + CrSel = 0xf7, // The CrSel key + ExSel = 0xf8, // The ExSel key + EraseEof = 0xf9, // The Erase EOF key + Play = 0xfa, // The Play key + Zoom = 0xfb, // The Zoom key + NoName = 0xfc, // Reserved + Pa1 = 0xfd, // The PA1 key + OemClear = 0xfe, // The Clear key + }; +} \ No newline at end of file diff --git a/dx12tutorial/dx12tutorial/Shaders/PixelShader.hlsl b/dx12tutorial/dx12tutorial/Shaders/PixelShader.hlsl new file mode 100644 index 0000000..2278151 --- /dev/null +++ b/dx12tutorial/dx12tutorial/Shaders/PixelShader.hlsl @@ -0,0 +1,9 @@ +struct PixelShaderInput +{ + float4 Color : COLOR; +}; + +float4 main(PixelShaderInput IN) : SV_Target +{ + return IN.Color; +} \ No newline at end of file diff --git a/dx12tutorial/dx12tutorial/Shaders/VertexShader.hlsl b/dx12tutorial/dx12tutorial/Shaders/VertexShader.hlsl new file mode 100644 index 0000000..2cd0c01 --- /dev/null +++ b/dx12tutorial/dx12tutorial/Shaders/VertexShader.hlsl @@ -0,0 +1,28 @@ +struct VertexPosColor +{ + float3 Position : POSITION; + float3 Color : COLOR; +}; + +struct ModelViewProjection +{ + matrix MVP; +}; + +ConstantBuffer ModelViewProjectionCB : register(b0); + +struct VertexShaderOutput +{ + float4 Color : COLOR; + float4 Position : SV_Position; +}; + +VertexShaderOutput main(VertexPosColor IN) +{ + VertexShaderOutput OUT; + + OUT.Position = mul(ModelViewProjectionCB.MVP, float4(IN.Position, 1.0f)); + OUT.Color = float4(IN.Color, 1.0f); + + return OUT; +} \ No newline at end of file diff --git a/dx12tutorial/dx12tutorial/Tutorial2.cpp b/dx12tutorial/dx12tutorial/Tutorial2.cpp new file mode 100644 index 0000000..20e7b22 --- /dev/null +++ b/dx12tutorial/dx12tutorial/Tutorial2.cpp @@ -0,0 +1,456 @@ +#include "Tutorial2.h" +#include "Helpers.h" + +#include "Application.h" +#include "CommandQueue.h" +#include "Helpers.h" +#include "Window.h" +#include "Game.h" + +#include +#include +using namespace Microsoft::WRL; + +#include +#include + +#include // For std::min and std::max. +#if defined(min) +#undef min +#endif + +#if defined(max) +#undef max +#endif + +using namespace DirectX; + +// Clamp a value between a min and max range. +template +constexpr const T& clamp(const T& val, const T& min, const T& max) +{ + return val < min ? min : val > max ? max : val; +} + +// Vertex data for a colored cube. +struct VertexPosColor +{ + XMFLOAT3 Position; + XMFLOAT3 Color; +}; + +static VertexPosColor g_Vertices[8] = { + { XMFLOAT3(-1.0f, -1.0f, -1.0f), XMFLOAT3(0.0f, 0.0f, 0.0f) }, // 0 + { XMFLOAT3(-1.0f, 1.0f, -1.0f), XMFLOAT3(0.0f, 1.0f, 0.0f) }, // 1 + { XMFLOAT3(1.0f, 1.0f, -1.0f), XMFLOAT3(1.0f, 1.0f, 0.0f) }, // 2 + { XMFLOAT3(1.0f, -1.0f, -1.0f), XMFLOAT3(1.0f, 0.0f, 0.0f) }, // 3 + { XMFLOAT3(-1.0f, -1.0f, 1.0f), XMFLOAT3(0.0f, 0.0f, 1.0f) }, // 4 + { XMFLOAT3(-1.0f, 1.0f, 1.0f), XMFLOAT3(0.0f, 1.0f, 1.0f) }, // 5 + { XMFLOAT3(1.0f, 1.0f, 1.0f), XMFLOAT3(1.0f, 1.0f, 1.0f) }, // 6 + { XMFLOAT3(1.0f, -1.0f, 1.0f), XMFLOAT3(1.0f, 0.0f, 1.0f) } // 7 +}; + +static WORD g_Indicies[36] = +{ + 0, 1, 2, 0, 2, 3, + 4, 6, 5, 4, 7, 6, + 4, 5, 1, 4, 1, 0, + 3, 2, 6, 3, 6, 7, + 1, 5, 6, 1, 6, 2, + 4, 0, 3, 4, 3, 7 +}; + +Tutorial2::Tutorial2(const std::wstring& name, int width, int height, bool vSync) + : super(name, width, height, vSync) + , m_ScissorRect(CD3DX12_RECT(0, 0, LONG_MAX, LONG_MAX)) + , m_Viewport(CD3DX12_VIEWPORT(0.0f, 0.0f, static_cast(width), static_cast(height))) + , m_FoV(45.0) + , m_ContentLoaded(false) +{ +} + +void Tutorial2::UpdateBufferResource( + ComPtr commandList, + ID3D12Resource** pDestinationResource, + ID3D12Resource** pIntermediateResource, + size_t numElements, size_t elementSize, const void* bufferData, + D3D12_RESOURCE_FLAGS flags) +{ + auto device = Application::Get().GetDevice(); + + size_t bufferSize = numElements * elementSize; + + auto constantHeapBufferPropertiesTypeDefault = CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT); + auto constantResourceDescriptorBufferWithFlags = CD3DX12_RESOURCE_DESC::Buffer(bufferSize, flags); + // Create a committed resource for the GPU resource in a default heap. + ThrowIfFailed(device->CreateCommittedResource( + &constantHeapBufferPropertiesTypeDefault, + D3D12_HEAP_FLAG_NONE, + &constantResourceDescriptorBufferWithFlags, + D3D12_RESOURCE_STATE_COPY_DEST, + nullptr, + IID_PPV_ARGS(pDestinationResource))); + // Create an committed resource for the upload. + if (bufferData) + { + auto constantHeapBufferPropertiesTypeUpload = CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD); + auto constantResourceDescriptorBuffer = CD3DX12_RESOURCE_DESC::Buffer(bufferSize); + ThrowIfFailed(device->CreateCommittedResource( + &constantHeapBufferPropertiesTypeUpload, + D3D12_HEAP_FLAG_NONE, + &constantResourceDescriptorBuffer, + D3D12_RESOURCE_STATE_GENERIC_READ, + nullptr, + IID_PPV_ARGS(pIntermediateResource))); + D3D12_SUBRESOURCE_DATA subresourceData = {}; + subresourceData.pData = bufferData; + subresourceData.RowPitch = bufferSize; + subresourceData.SlicePitch = subresourceData.RowPitch; + + UpdateSubresources(commandList.Get(), + *pDestinationResource, *pIntermediateResource, + 0, 0, 1, &subresourceData); + } +} + +bool Tutorial2::LoadContent() +{ + auto device = Application::Get().GetDevice(); + auto commandQueue = Application::Get().GetCommandQueue(D3D12_COMMAND_LIST_TYPE_COPY); + auto commandList = commandQueue->GetCommandList(); + + // Upload vertex buffer data. + ComPtr intermediateVertexBuffer; + UpdateBufferResource(commandList, + &m_VertexBuffer, &intermediateVertexBuffer, + _countof(g_Vertices), sizeof(VertexPosColor), g_Vertices); + + // Create the vertex buffer view. + m_VertexBufferView.BufferLocation = m_VertexBuffer->GetGPUVirtualAddress(); + m_VertexBufferView.SizeInBytes = sizeof(g_Vertices); + m_VertexBufferView.StrideInBytes = sizeof(VertexPosColor); + + // Upload index buffer data. + ComPtr intermediateIndexBuffer; + UpdateBufferResource(commandList, + &m_IndexBuffer, &intermediateIndexBuffer, + _countof(g_Indicies), sizeof(WORD), g_Indicies); + + // Create index buffer view. + m_IndexBufferView.BufferLocation = m_IndexBuffer->GetGPUVirtualAddress(); + m_IndexBufferView.Format = DXGI_FORMAT_R16_UINT; + m_IndexBufferView.SizeInBytes = sizeof(g_Indicies); + + // Create the descriptor heap for the depth-stencil view. + D3D12_DESCRIPTOR_HEAP_DESC dsvHeapDesc = {}; + dsvHeapDesc.NumDescriptors = 1; + dsvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_DSV; + dsvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE; + ThrowIfFailed(device->CreateDescriptorHeap(&dsvHeapDesc, IID_PPV_ARGS(&m_DSVHeap))); + + //HRESULT D3DCompileFromFile( + // [in] LPCWSTR pFileName, + // [in, optional] const D3D_SHADER_MACRO * pDefines, + // [in, optional] ID3DInclude * pInclude, + // [in] LPCSTR pEntrypoint, + // [in] LPCSTR pTarget, + // [in] UINT Flags1, + // [in] UINT Flags2, + // [out] ID3DBlob * *ppCode, + // [out, optional] ID3DBlob * *ppErrorMsgs + //); + +#if defined(_DEBUG) + // Enable better shader debugging with the graphics debugging tools. + UINT compileFlags = D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION; +#else + UINT compileFlags = 0; +#endif + + // Load the vertex shader. + ComPtr vertexShaderBlob; + ComPtr vertexShaderBlobErrors; + + ThrowIfFailed(D3DCompileFromFile(L"Shaders\\VertexShader.hlsl", NULL, D3D_COMPILE_STANDARD_FILE_INCLUDE, "main", "vs_5_1", compileFlags, 0, &vertexShaderBlob, &vertexShaderBlobErrors)); + + // Load the pixel shader. + ComPtr pixelShaderBlob; + ComPtr pixelShaderBlobErrors; + + ThrowIfFailed(D3DCompileFromFile(L"Shaders\\PixelShader.hlsl", NULL, D3D_COMPILE_STANDARD_FILE_INCLUDE, "main", "ps_5_1", compileFlags, 0, &pixelShaderBlob, &pixelShaderBlobErrors)); + + // Create the vertex input layout + D3D12_INPUT_ELEMENT_DESC inputLayout[] = { + { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, D3D12_APPEND_ALIGNED_ELEMENT, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }, + { "COLOR", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, D3D12_APPEND_ALIGNED_ELEMENT, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }, + }; + + // Create a root signature. + D3D12_FEATURE_DATA_ROOT_SIGNATURE featureData = {}; + featureData.HighestVersion = D3D_ROOT_SIGNATURE_VERSION_1_1; + if (FAILED(device->CheckFeatureSupport(D3D12_FEATURE_ROOT_SIGNATURE, &featureData, sizeof(featureData)))) + { + featureData.HighestVersion = D3D_ROOT_SIGNATURE_VERSION_1_0; + } + + // Allow input layout and deny unnecessary access to certain pipeline stages. + D3D12_ROOT_SIGNATURE_FLAGS rootSignatureFlags = + D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT | + D3D12_ROOT_SIGNATURE_FLAG_DENY_HULL_SHADER_ROOT_ACCESS | + D3D12_ROOT_SIGNATURE_FLAG_DENY_DOMAIN_SHADER_ROOT_ACCESS | + D3D12_ROOT_SIGNATURE_FLAG_DENY_GEOMETRY_SHADER_ROOT_ACCESS | + D3D12_ROOT_SIGNATURE_FLAG_DENY_PIXEL_SHADER_ROOT_ACCESS; + + // A single 32-bit constant root parameter that is used by the vertex shader. + CD3DX12_ROOT_PARAMETER1 rootParameters[1]; + rootParameters[0].InitAsConstants(sizeof(XMMATRIX) / 4, 0, 0, D3D12_SHADER_VISIBILITY_VERTEX); + + CD3DX12_VERSIONED_ROOT_SIGNATURE_DESC rootSignatureDescription; + rootSignatureDescription.Init_1_1(_countof(rootParameters), rootParameters, 0, nullptr, rootSignatureFlags); + + // Serialize the root signature. + ComPtr rootSignatureBlob; + ComPtr errorBlob; + ThrowIfFailed(D3DX12SerializeVersionedRootSignature(&rootSignatureDescription, + featureData.HighestVersion, &rootSignatureBlob, &errorBlob)); + // Create the root signature. + ThrowIfFailed(device->CreateRootSignature(0, rootSignatureBlob->GetBufferPointer(), + rootSignatureBlob->GetBufferSize(), IID_PPV_ARGS(&m_RootSignature))); + + struct PipelineStateStream + { + CD3DX12_PIPELINE_STATE_STREAM_ROOT_SIGNATURE pRootSignature; + CD3DX12_PIPELINE_STATE_STREAM_INPUT_LAYOUT InputLayout; + CD3DX12_PIPELINE_STATE_STREAM_PRIMITIVE_TOPOLOGY PrimitiveTopologyType; + CD3DX12_PIPELINE_STATE_STREAM_VS VS; + CD3DX12_PIPELINE_STATE_STREAM_PS PS; + CD3DX12_PIPELINE_STATE_STREAM_DEPTH_STENCIL_FORMAT DSVFormat; + CD3DX12_PIPELINE_STATE_STREAM_RENDER_TARGET_FORMATS RTVFormats; + } pipelineStateStream; + + D3D12_RT_FORMAT_ARRAY rtvFormats = {}; + rtvFormats.NumRenderTargets = 1; + rtvFormats.RTFormats[0] = DXGI_FORMAT_R8G8B8A8_UNORM; + + pipelineStateStream.pRootSignature = m_RootSignature.Get(); + pipelineStateStream.InputLayout = { inputLayout, _countof(inputLayout) }; + pipelineStateStream.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE; + pipelineStateStream.VS = CD3DX12_SHADER_BYTECODE(vertexShaderBlob.Get()); + pipelineStateStream.PS = CD3DX12_SHADER_BYTECODE(pixelShaderBlob.Get()); + pipelineStateStream.DSVFormat = DXGI_FORMAT_D32_FLOAT; + pipelineStateStream.RTVFormats = rtvFormats; + + D3D12_PIPELINE_STATE_STREAM_DESC pipelineStateStreamDesc = { + sizeof(PipelineStateStream), &pipelineStateStream + }; + ThrowIfFailed(device->CreatePipelineState(&pipelineStateStreamDesc, IID_PPV_ARGS(&m_PipelineState))); + + auto fenceValue = commandQueue->ExecuteCommandList(commandList); + commandQueue->WaitForFenceValue(fenceValue); + + m_ContentLoaded = true; + + // Resize/Create the depth buffer. + ResizeDepthBuffer(GetClientWidth(), GetClientHeight()); + + return true; +} + +void Tutorial2::UnloadContent() +{ + m_ContentLoaded = false; +} + +void Tutorial2::ResizeDepthBuffer(int width, int height) +{ + if (m_ContentLoaded) + { + // Flush any GPU commands that might be referencing the depth buffer. + Application::Get().Flush(); + + width = std::max(1, width); + height = std::max(1, height); + + auto device = Application::Get().GetDevice(); + // Resize screen dependent resources. + // Create a depth buffer. + D3D12_CLEAR_VALUE optimizedClearValue = {}; + optimizedClearValue.Format = DXGI_FORMAT_D32_FLOAT; + optimizedClearValue.DepthStencil = { 1.0f, 0 }; + + auto heapPropertiesTypeDefault = CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT); + auto resourceDescriptor = CD3DX12_RESOURCE_DESC::Tex2D(DXGI_FORMAT_D32_FLOAT, width, height, + 1, 0, 1, 0, D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL); + + ThrowIfFailed(device->CreateCommittedResource( + &heapPropertiesTypeDefault, + D3D12_HEAP_FLAG_NONE, + &resourceDescriptor, + D3D12_RESOURCE_STATE_DEPTH_WRITE, + &optimizedClearValue, + IID_PPV_ARGS(&m_DepthBuffer) + )); + // Update the depth-stencil view. + D3D12_DEPTH_STENCIL_VIEW_DESC dsv = {}; + dsv.Format = DXGI_FORMAT_D32_FLOAT; + dsv.ViewDimension = D3D12_DSV_DIMENSION_TEXTURE2D; + dsv.Texture2D.MipSlice = 0; + dsv.Flags = D3D12_DSV_FLAG_NONE; + + device->CreateDepthStencilView(m_DepthBuffer.Get(), &dsv, + m_DSVHeap->GetCPUDescriptorHandleForHeapStart()); + } +} + +void Tutorial2::OnMouseWheel(MouseWheelEventArgs& e) +{ + m_FoV -= e.WheelDelta; + m_FoV = clamp(m_FoV, 12.0f, 90.0f); + + char buffer[256]; + sprintf_s(buffer, "FoV: %f\n", m_FoV); + OutputDebugStringA(buffer); +} + +void Tutorial2::OnResize(ResizeEventArgs& e) +{ + if (e.Width != GetClientWidth() || e.Height != GetClientHeight()) + { + super::OnResize(e); + + m_Viewport = CD3DX12_VIEWPORT(0.0f, 0.0f, + static_cast(e.Width), static_cast(e.Height)); + + ResizeDepthBuffer(e.Width, e.Height); + } +} + +void Tutorial2::OnUpdate(UpdateEventArgs& e) +{ + static uint64_t frameCount = 0; + static double totalTime = 0.0; + + super::OnUpdate(e); + + totalTime += e.ElapsedTime; + frameCount++; + + if (totalTime > 1.0) + { + double fps = frameCount / totalTime; + + char buffer[512]; + sprintf_s(buffer, "FPS: %f\n", fps); + OutputDebugStringA(buffer); + + frameCount = 0; + totalTime = 0.0; + } + // Update the model matrix. + float angle = static_cast(e.TotalTime * 90.0); + const XMVECTOR rotationAxis = XMVectorSet(0, 1, 1, 0); + m_ModelMatrix = XMMatrixRotationAxis(rotationAxis, XMConvertToRadians(angle)); + // Update the view matrix. + const XMVECTOR eyePosition = XMVectorSet(0, 0, -10, 1); + const XMVECTOR focusPoint = XMVectorSet(0, 0, 0, 1); + const XMVECTOR upDirection = XMVectorSet(0, 1, 0, 0); + m_ViewMatrix = XMMatrixLookAtLH(eyePosition, focusPoint, upDirection); + // Update the projection matrix. + float aspectRatio = GetClientWidth() / static_cast(GetClientHeight()); + m_ProjectionMatrix = XMMatrixPerspectiveFovLH(XMConvertToRadians(m_FoV), aspectRatio, 0.1f, 100.0f); +} + +// Transition a resource +void Tutorial2::TransitionResource(Microsoft::WRL::ComPtr commandList, + Microsoft::WRL::ComPtr resource, + D3D12_RESOURCE_STATES beforeState, D3D12_RESOURCE_STATES afterState) +{ + CD3DX12_RESOURCE_BARRIER barrier = CD3DX12_RESOURCE_BARRIER::Transition( + resource.Get(), + beforeState, afterState); + + commandList->ResourceBarrier(1, &barrier); +} + +// Clear a render target. +void Tutorial2::ClearRTV(Microsoft::WRL::ComPtr commandList, + D3D12_CPU_DESCRIPTOR_HANDLE rtv, FLOAT* clearColor) +{ + commandList->ClearRenderTargetView(rtv, clearColor, 0, nullptr); +} + +void Tutorial2::OnRender(RenderEventArgs& e) +{ + super::OnRender(e); + + auto commandQueue = Application::Get().GetCommandQueue(D3D12_COMMAND_LIST_TYPE_DIRECT); + auto commandList = commandQueue->GetCommandList(); + + UINT currentBackBufferIndex = m_pWindow->GetCurrentBackBufferIndex(); + auto backBuffer = m_pWindow->GetCurrentBackBuffer(); + auto rtv = m_pWindow->GetCurrentRenderTargetView(); + auto dsv = m_DSVHeap->GetCPUDescriptorHandleForHeapStart(); + // Clear the render targets. + { + TransitionResource(commandList, backBuffer, + D3D12_RESOURCE_STATE_PRESENT, D3D12_RESOURCE_STATE_RENDER_TARGET); + + FLOAT clearColor[] = { 0.4f, 0.6f, 0.9f, 1.0f }; + + ClearRTV(commandList, rtv, clearColor); + ClearDepth(commandList, dsv); + } + commandList->SetPipelineState(m_PipelineState.Get()); + commandList->SetGraphicsRootSignature(m_RootSignature.Get()); + commandList->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST); + commandList->IASetVertexBuffers(0, 1, &m_VertexBufferView); + commandList->IASetIndexBuffer(&m_IndexBufferView); + commandList->RSSetViewports(1, &m_Viewport); + commandList->RSSetScissorRects(1, &m_ScissorRect); + commandList->OMSetRenderTargets(1, &rtv, FALSE, &dsv); + + // Update the MVP matrix + XMMATRIX mvpMatrix = XMMatrixMultiply(m_ModelMatrix, m_ViewMatrix); + mvpMatrix = XMMatrixMultiply(mvpMatrix, m_ProjectionMatrix); + commandList->SetGraphicsRoot32BitConstants(0, sizeof(XMMATRIX) / 4, &mvpMatrix, 0); + commandList->DrawIndexedInstanced(_countof(g_Indicies), 1, 0, 0, 0); + // Present + { + TransitionResource(commandList, backBuffer, + D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_PRESENT); + + m_FenceValues[currentBackBufferIndex] = commandQueue->ExecuteCommandList(commandList); + + currentBackBufferIndex = m_pWindow->Present(); + + commandQueue->WaitForFenceValue(m_FenceValues[currentBackBufferIndex]); + } +} + +void Tutorial2::OnKeyPressed(KeyEventArgs& e) +{ + super::OnKeyPressed(e); + + switch (e.Key) + { + case KeyCode::Escape: + Application::Get().Quit(0); + break; + case KeyCode::Enter: + if (e.Alt) + { + case KeyCode::F11: + m_pWindow->ToggleFullscreen(); + break; + } + case KeyCode::V: + m_pWindow->ToggleVSync(); + break; + } +} + +void Tutorial2::ClearDepth(Microsoft::WRL::ComPtr commandList, + D3D12_CPU_DESCRIPTOR_HANDLE dsv, FLOAT depth) +{ + commandList->ClearDepthStencilView(dsv, D3D12_CLEAR_FLAG_DEPTH, depth, 0, 0, nullptr); +} \ No newline at end of file diff --git a/dx12tutorial/dx12tutorial/Tutorial2.h b/dx12tutorial/dx12tutorial/Tutorial2.h new file mode 100644 index 0000000..8e9850d --- /dev/null +++ b/dx12tutorial/dx12tutorial/Tutorial2.h @@ -0,0 +1,99 @@ +#pragma once + +#include "Game.h" +#include "Window.h" + +#include + +class Tutorial2 : public Game +{ +public: + using super = Game; + + Tutorial2(const std::wstring& name, int width, int height, bool vSync = false); + /** + * Load content required for the demo. + */ + virtual bool LoadContent() override; + + /** + * Unload demo specific content that was loaded in LoadContent. + */ + virtual void UnloadContent() override; + +protected: + /** + * Update the game logic. + */ + virtual void OnUpdate(UpdateEventArgs& e) override; + + /** + * Render stuff. + */ + virtual void OnRender(RenderEventArgs& e) override; + + /** + * Invoked by the registered window when a key is pressed + * while the window has focus. + */ + virtual void OnKeyPressed(KeyEventArgs& e) override; + + /** + * Invoked when the mouse wheel is scrolled while the registered window has focus. + */ + virtual void OnMouseWheel(MouseWheelEventArgs& e) override; + + + virtual void OnResize(ResizeEventArgs& e) override; + +private: + // Helper functions + // Transition a resource + void TransitionResource(Microsoft::WRL::ComPtr commandList, + Microsoft::WRL::ComPtr resource, + D3D12_RESOURCE_STATES beforeState, D3D12_RESOURCE_STATES afterState); + + // Clear a render target view. + void ClearRTV(Microsoft::WRL::ComPtr commandList, + D3D12_CPU_DESCRIPTOR_HANDLE rtv, FLOAT* clearColor); + + // Clear the depth of a depth-stencil view. + void ClearDepth(Microsoft::WRL::ComPtr commandList, + D3D12_CPU_DESCRIPTOR_HANDLE dsv, FLOAT depth = 1.0f); + + // Create a GPU buffer. + void UpdateBufferResource(Microsoft::WRL::ComPtr commandList, + ID3D12Resource** pDestinationResource, ID3D12Resource** pIntermediateResource, + size_t numElements, size_t elementSize, const void* bufferData, + D3D12_RESOURCE_FLAGS flags = D3D12_RESOURCE_FLAG_NONE); + + // Resize the depth buffer to match the size of the client area. + void ResizeDepthBuffer(int width, int height); + + uint64_t m_FenceValues[Window::BufferCount] = {}; + // Vertex buffer for the cube. + Microsoft::WRL::ComPtr m_VertexBuffer; + D3D12_VERTEX_BUFFER_VIEW m_VertexBufferView; + // Index buffer for the cube. + Microsoft::WRL::ComPtr m_IndexBuffer; + D3D12_INDEX_BUFFER_VIEW m_IndexBufferView; + // Depth buffer. + Microsoft::WRL::ComPtr m_DepthBuffer; + // Descriptor heap for depth buffer. + Microsoft::WRL::ComPtr m_DSVHeap; + // Root signature + Microsoft::WRL::ComPtr m_RootSignature; + + // Pipeline state object. + Microsoft::WRL::ComPtr m_PipelineState; + + D3D12_VIEWPORT m_Viewport; + D3D12_RECT m_ScissorRect; + float m_FoV; + + DirectX::XMMATRIX m_ModelMatrix; + DirectX::XMMATRIX m_ViewMatrix; + DirectX::XMMATRIX m_ProjectionMatrix; + + bool m_ContentLoaded; +}; \ No newline at end of file diff --git a/dx12tutorial/dx12tutorial/Window.cpp b/dx12tutorial/dx12tutorial/Window.cpp new file mode 100644 index 0000000..94cd2ef --- /dev/null +++ b/dx12tutorial/dx12tutorial/Window.cpp @@ -0,0 +1,366 @@ +#include "DX12LibPCH.h" +#include "Application.h" +#include "CommandQueue.h" +#include "Window.h" +#include "Game.h" + +Window::Window(HWND hWnd, const std::wstring& windowName, int clientWidth, int clientHeight, bool vSync) + : m_hWnd(hWnd) + , m_WindowName(windowName) + , m_ClientWidth(clientWidth) + , m_ClientHeight(clientHeight) + , m_VSync(vSync) + , m_Fullscreen(false) + , m_FrameCounter(0) +{ + Application& app = Application::Get(); + + m_IsTearingSupported = app.IsTearingSupported(); + + m_dxgiSwapChain = CreateSwapChain(); + m_d3d12RTVDescriptorHeap = app.CreateDescriptorHeap(BufferCount, D3D12_DESCRIPTOR_HEAP_TYPE_RTV); + m_RTVDescriptorSize = app.GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV); + + UpdateRenderTargetViews(); +} + +Window::~Window() +{ + // Window should be destroyed with Application::DestroyWindow before + // the window goes out of scope. + assert(!m_hWnd && "Use Application::DestroyWindow before destruction."); +} + +HWND Window::GetWindowHandle() const +{ + return m_hWnd; +} + +const std::wstring& Window::GetWindowName() const +{ + return m_WindowName; +} + +void Window::Show() +{ + ::ShowWindow(m_hWnd, SW_SHOW); +} + +/** +* Hide the window. +*/ +void Window::Hide() +{ + ::ShowWindow(m_hWnd, SW_HIDE); +} + +void Window::Destroy() +{ + if (auto pGame = m_pGame.lock()) + { + // Notify the registered game that the window is being destroyed. + pGame->OnWindowDestroy(); + } + if (m_hWnd) + { + DestroyWindow(m_hWnd); + m_hWnd = nullptr; + } +} + +int Window::GetClientWidth() const +{ + return m_ClientWidth; +} + +int Window::GetClientHeight() const +{ + return m_ClientHeight; +} + +bool Window::IsVSync() const +{ + return m_VSync; +} + +void Window::SetVSync(bool vSync) +{ + m_VSync = vSync; +} + +void Window::ToggleVSync() +{ + SetVSync(!m_VSync); +} + +bool Window::IsFullScreen() const +{ + return m_Fullscreen; +} + +// Set the fullscreen state of the window. +void Window::SetFullscreen(bool fullscreen) +{ + if (m_Fullscreen != fullscreen) + { + m_Fullscreen = fullscreen; + + if (m_Fullscreen) // Switching to fullscreen. + { + // Store the current window dimensions so they can be restored + // when switching out of fullscreen state. + ::GetWindowRect(m_hWnd, &m_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(m_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(m_hWnd, MONITOR_DEFAULTTONEAREST); + MONITORINFOEX monitorInfo = {}; + monitorInfo.cbSize = sizeof(MONITORINFOEX); + ::GetMonitorInfo(hMonitor, &monitorInfo); + + ::SetWindowPos(m_hWnd, HWND_TOPMOST, + monitorInfo.rcMonitor.left, + monitorInfo.rcMonitor.top, + monitorInfo.rcMonitor.right - monitorInfo.rcMonitor.left, + monitorInfo.rcMonitor.bottom - monitorInfo.rcMonitor.top, + SWP_FRAMECHANGED | SWP_NOACTIVATE); + + ::ShowWindow(m_hWnd, SW_MAXIMIZE); + } + else + { + // Restore all the window decorators. + ::SetWindowLong(m_hWnd, GWL_STYLE, WS_OVERLAPPEDWINDOW); + + ::SetWindowPos(m_hWnd, HWND_NOTOPMOST, + m_WindowRect.left, + m_WindowRect.top, + m_WindowRect.right - m_WindowRect.left, + m_WindowRect.bottom - m_WindowRect.top, + SWP_FRAMECHANGED | SWP_NOACTIVATE); + + ::ShowWindow(m_hWnd, SW_NORMAL); + } + } +} + +void Window::ToggleFullscreen() +{ + SetFullscreen(!m_Fullscreen); +} + + +void Window::RegisterCallbacks(std::shared_ptr pGame) +{ + m_pGame = pGame; + + return; +} + +void Window::OnUpdate(UpdateEventArgs&) +{ + m_UpdateClock.Tick(); + + if (auto pGame = m_pGame.lock()) + { + m_FrameCounter++; + + UpdateEventArgs updateEventArgs(m_UpdateClock.GetDeltaSeconds(), m_UpdateClock.GetTotalSeconds()); + pGame->OnUpdate(updateEventArgs); + } +} + +void Window::OnRender(RenderEventArgs&) +{ + m_RenderClock.Tick(); + + if (auto pGame = m_pGame.lock()) + { + RenderEventArgs renderEventArgs(m_RenderClock.GetDeltaSeconds(), m_RenderClock.GetTotalSeconds()); + pGame->OnRender(renderEventArgs); + } +} + +void Window::OnKeyPressed(KeyEventArgs& e) +{ + if (auto pGame = m_pGame.lock()) + { + pGame->OnKeyPressed(e); + } +} + +void Window::OnKeyReleased(KeyEventArgs& e) +{ + if (auto pGame = m_pGame.lock()) + { + pGame->OnKeyReleased(e); + } +} + +// The mouse was moved +void Window::OnMouseMoved(MouseMotionEventArgs& e) +{ + if (auto pGame = m_pGame.lock()) + { + pGame->OnMouseMoved(e); + } +} + +// A button on the mouse was pressed +void Window::OnMouseButtonPressed(MouseButtonEventArgs& e) +{ + if (auto pGame = m_pGame.lock()) + { + pGame->OnMouseButtonPressed(e); + } +} + +// A button on the mouse was released +void Window::OnMouseButtonReleased(MouseButtonEventArgs& e) +{ + if (auto pGame = m_pGame.lock()) + { + pGame->OnMouseButtonReleased(e); + } +} + +// The mouse wheel was moved. +void Window::OnMouseWheel(MouseWheelEventArgs& e) +{ + if (auto pGame = m_pGame.lock()) + { + pGame->OnMouseWheel(e); + } +} + +void Window::OnResize(ResizeEventArgs& e) +{ + // Update the client size. + if (m_ClientWidth != e.Width || m_ClientHeight != e.Height) + { + m_ClientWidth = std::max(1, e.Width); + m_ClientHeight = std::max(1, e.Height); + + Application::Get().Flush(); + + for (int i = 0; i < BufferCount; ++i) + { + m_d3d12BackBuffers[i].Reset(); + } + + DXGI_SWAP_CHAIN_DESC swapChainDesc = {}; + ThrowIfFailed(m_dxgiSwapChain->GetDesc(&swapChainDesc)); + ThrowIfFailed(m_dxgiSwapChain->ResizeBuffers(BufferCount, m_ClientWidth, + m_ClientHeight, swapChainDesc.BufferDesc.Format, swapChainDesc.Flags)); + + m_CurrentBackBufferIndex = m_dxgiSwapChain->GetCurrentBackBufferIndex(); + + UpdateRenderTargetViews(); + } + + if (auto pGame = m_pGame.lock()) + { + pGame->OnResize(e); + } +} + +Microsoft::WRL::ComPtr Window::CreateSwapChain() +{ + Application& app = Application::Get(); + + ComPtr dxgiSwapChain4; + ComPtr 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 = m_ClientWidth; + swapChainDesc.Height = m_ClientHeight; + 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 = m_IsTearingSupported ? DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING : 0; + ID3D12CommandQueue* pCommandQueue = app.GetCommandQueue()->GetD3D12CommandQueue().Get(); + + ComPtr swapChain1; + ThrowIfFailed(dxgiFactory4->CreateSwapChainForHwnd( + pCommandQueue, + m_hWnd, + &swapChainDesc, + nullptr, + nullptr, + &swapChain1)); + + // Disable the Alt+Enter fullscreen toggle feature. Switching to fullscreen + // will be handled manually. + ThrowIfFailed(dxgiFactory4->MakeWindowAssociation(m_hWnd, DXGI_MWA_NO_ALT_ENTER)); + + ThrowIfFailed(swapChain1.As(&dxgiSwapChain4)); + + m_CurrentBackBufferIndex = dxgiSwapChain4->GetCurrentBackBufferIndex(); + + return dxgiSwapChain4; +} + +// Update the render target views for the swapchain back buffers. +void Window::UpdateRenderTargetViews() +{ + auto device = Application::Get().GetDevice(); + + CD3DX12_CPU_DESCRIPTOR_HANDLE rtvHandle(m_d3d12RTVDescriptorHeap->GetCPUDescriptorHandleForHeapStart()); + + for (int i = 0; i < BufferCount; ++i) + { + ComPtr backBuffer; + ThrowIfFailed(m_dxgiSwapChain->GetBuffer(i, IID_PPV_ARGS(&backBuffer))); + + device->CreateRenderTargetView(backBuffer.Get(), nullptr, rtvHandle); + + m_d3d12BackBuffers[i] = backBuffer; + + rtvHandle.Offset(m_RTVDescriptorSize); + } +} + +D3D12_CPU_DESCRIPTOR_HANDLE Window::GetCurrentRenderTargetView() const +{ + return CD3DX12_CPU_DESCRIPTOR_HANDLE(m_d3d12RTVDescriptorHeap->GetCPUDescriptorHandleForHeapStart(), + m_CurrentBackBufferIndex, m_RTVDescriptorSize); +} + +Microsoft::WRL::ComPtr Window::GetCurrentBackBuffer() const +{ + return m_d3d12BackBuffers[m_CurrentBackBufferIndex]; +} + +UINT Window::GetCurrentBackBufferIndex() const +{ + return m_CurrentBackBufferIndex; +} + +UINT Window::Present() +{ + UINT syncInterval = m_VSync ? 1 : 0; + UINT presentFlags = m_IsTearingSupported && !m_VSync ? DXGI_PRESENT_ALLOW_TEARING : 0; + ThrowIfFailed(m_dxgiSwapChain->Present(syncInterval, presentFlags)); + m_CurrentBackBufferIndex = m_dxgiSwapChain->GetCurrentBackBufferIndex(); + + return m_CurrentBackBufferIndex; +} \ No newline at end of file diff --git a/dx12tutorial/dx12tutorial/Window.h b/dx12tutorial/dx12tutorial/Window.h new file mode 100644 index 0000000..f5a15da --- /dev/null +++ b/dx12tutorial/dx12tutorial/Window.h @@ -0,0 +1,163 @@ +/** +* @brief A window for our application. +*/ +#pragma once + +#define WIN32_LEAN_AND_MEAN +#include + +#include +#include +#include + +#include "Events.h" +#include "HighResolutionClock.h" + +// Forward-declare the DirectXTemplate class. +class Game; + +class Window +{ +public: + // Number of swapchain back buffers. + static const UINT BufferCount = 3; + + /** + * Get a handle to this window's instance. + * @returns The handle to the window instance or nullptr if this is not a valid window. + */ + HWND GetWindowHandle() const; + + /** + * Destroy this window. + */ + void Destroy(); + + const std::wstring& GetWindowName() const; + + int GetClientWidth() const; + int GetClientHeight() const; + + /** + * Should this window be rendered with vertical refresh synchronization. + */ + bool IsVSync() const; + void SetVSync(bool vSync); + void ToggleVSync(); + + /** + * Is this a windowed window or full-screen? + */ + bool IsFullScreen() const; + + // Set the fullscreen state of the window. + void SetFullscreen(bool fullscreen); + void ToggleFullscreen(); + + /** + * Show this window. + */ + void Show(); + + /** + * Hide the window. + */ + void Hide(); + + /** + * Return the current back buffer index. + */ + UINT GetCurrentBackBufferIndex() const; + + /** + * Present the swapchain's back buffer to the screen. + * Returns the current back buffer index after the present. + */ + UINT Present(); + + /** + * Get the render target view for the current back buffer. + */ + D3D12_CPU_DESCRIPTOR_HANDLE GetCurrentRenderTargetView() const; + + /** + * Get the back buffer resource for the current back buffer. + */ + Microsoft::WRL::ComPtr GetCurrentBackBuffer() const; + + +protected: + // The Window procedure needs to call protected methods of this class. + friend LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam); + + // Only the application can create a window. + friend class Application; + // The DirectXTemplate class needs to register itself with a window. + friend class Game; + + Window() = delete; + Window(HWND hWnd, const std::wstring& windowName, int clientWidth, int clientHeight, bool vSync); + virtual ~Window(); + + // Register a Game with this window. This allows + // the window to callback functions in the Game class. + void RegisterCallbacks(std::shared_ptr pGame); + + // Update and Draw can only be called by the application. + virtual void OnUpdate(UpdateEventArgs& e); + virtual void OnRender(RenderEventArgs& e); + + // A keyboard key was pressed + virtual void OnKeyPressed(KeyEventArgs& e); + // A keyboard key was released + virtual void OnKeyReleased(KeyEventArgs& e); + + // The mouse was moved + virtual void OnMouseMoved(MouseMotionEventArgs& e); + // A button on the mouse was pressed + virtual void OnMouseButtonPressed(MouseButtonEventArgs& e); + // A button on the mouse was released + virtual void OnMouseButtonReleased(MouseButtonEventArgs& e); + // The mouse wheel was moved. + virtual void OnMouseWheel(MouseWheelEventArgs& e); + + // The window was resized. + virtual void OnResize(ResizeEventArgs& e); + + // Create the swapchian. + Microsoft::WRL::ComPtr CreateSwapChain(); + + // Update the render target views for the swapchain back buffers. + void UpdateRenderTargetViews(); + +private: + // Windows should not be copied. + Window(const Window& copy) = delete; + Window& operator=(const Window& other) = delete; + + HWND m_hWnd; + + std::wstring m_WindowName; + + int m_ClientWidth; + int m_ClientHeight; + bool m_VSync; + bool m_Fullscreen; + + HighResolutionClock m_UpdateClock; + HighResolutionClock m_RenderClock; + uint64_t m_FrameCounter; + + std::weak_ptr m_pGame; + + Microsoft::WRL::ComPtr m_dxgiSwapChain; + Microsoft::WRL::ComPtr m_d3d12RTVDescriptorHeap; + Microsoft::WRL::ComPtr m_d3d12BackBuffers[BufferCount]; + + UINT m_RTVDescriptorSize; + UINT m_CurrentBackBufferIndex; + + RECT m_WindowRect; + bool m_IsTearingSupported; + +}; \ No newline at end of file diff --git a/dx12tutorial/dx12tutorial/dx12tutorial.h b/dx12tutorial/dx12tutorial/dx12tutorial.h new file mode 100644 index 0000000..a0c485b --- /dev/null +++ b/dx12tutorial/dx12tutorial/dx12tutorial.h @@ -0,0 +1,8 @@ +// dx12tutorial.h : Include file for standard system include files, +// or project specific include files. + +#pragma once + +#include + +// TODO: Reference additional headers your program requires here. diff --git a/dx12tutorial/dx12tutorial/main.cpp b/dx12tutorial/dx12tutorial/main.cpp new file mode 100644 index 0000000..321f26a --- /dev/null +++ b/dx12tutorial/dx12tutorial/main.cpp @@ -0,0 +1,42 @@ +#define WIN32_LEAN_AND_MEAN +#include +#include + +#include "Application.h" +#include "Tutorial2.h" + +#include + +void ReportLiveObjects() +{ + IDXGIDebug1* dxgiDebug; + DXGIGetDebugInterface1(0, IID_PPV_ARGS(&dxgiDebug)); + + dxgiDebug->ReportLiveObjects(DXGI_DEBUG_ALL, DXGI_DEBUG_RLO_IGNORE_INTERNAL); + dxgiDebug->Release(); +} + +int CALLBACK wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR lpCmdLine, int nCmdShow) +{ + int retCode = 0; + + // Set the working directory to the path of the executable. + WCHAR path[MAX_PATH]; + HMODULE hModule = GetModuleHandleW(NULL); + if (GetModuleFileNameW(hModule, path, MAX_PATH) > 0) + { + PathCchRemoveFileSpec(path, MAX_PATH); + SetCurrentDirectoryW(path); + } + + Application::Create(hInstance); + { + std::shared_ptr demo = std::make_shared(L"Learning DirectX 12 - Lesson 2", 1280, 720); + retCode = Application::Get().Run(demo); + } + Application::Destroy(); + + atexit(&ReportLiveObjects); + + return retCode; +} \ No newline at end of file diff --git a/dx12tutorial/resource.h b/dx12tutorial/resource.h new file mode 100644 index 0000000..568ef56 --- /dev/null +++ b/dx12tutorial/resource.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by DX12Lib.rc +// +#define APP_ICON 5 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif \ No newline at end of file