From 0b5491e0a2f1a9a4023e2c4eb171287bede41388 Mon Sep 17 00:00:00 2001 From: Marc Sunet Date: Fri, 21 Nov 2025 09:41:06 -0800 Subject: Switch to plain C --- .gitignore | 1 + CMakeLists.txt | 23 ++- app/CMakeLists.txt | 11 ++ app/include/dxwindow.h | 28 +++ app/src/dxwindow.c | 81 +++++++++ contrib/glfw/CMakeLists.txt | 2 +- dxg/CMakeLists.txt | 23 ++- dxg/dxcommon.h | 17 -- dxg/dxcommon.ixx | 53 ------ dxg/include/dxg/dxcommon.h | 55 ++++++ dxg/src/dxg.c | 1 + dxwindow/CMakeLists.txt | 10 - dxwindow/dxwindow.ixx | 113 ------------ hello/CMakeLists.txt | 8 +- hello/main.c | 430 +++++++++++++++++++++++++++++++++++++++++++ hello/main.cc | 433 -------------------------------------------- 16 files changed, 638 insertions(+), 651 deletions(-) create mode 100644 app/CMakeLists.txt create mode 100644 app/include/dxwindow.h create mode 100644 app/src/dxwindow.c delete mode 100644 dxg/dxcommon.h delete mode 100644 dxg/dxcommon.ixx create mode 100644 dxg/include/dxg/dxcommon.h create mode 100644 dxg/src/dxg.c delete mode 100644 dxwindow/CMakeLists.txt delete mode 100644 dxwindow/dxwindow.ixx create mode 100644 hello/main.c delete mode 100644 hello/main.cc diff --git a/.gitignore b/.gitignore index c4c4ffc..2795b52 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ *.zip +compile_commands.json diff --git a/CMakeLists.txt b/CMakeLists.txt index ca8b0ac..82d9df9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,16 +1,18 @@ -cmake_minimum_required(VERSION 3.25) +cmake_minimum_required(VERSION 3.20) -set(CMAKE_CXX_STANDARD 20) -set(CMAKE_CXX_STANDARD_REQUIRED ON) -set(CMAKE_CXX_EXTENSIONS OFF) -set(CMAKE_EXPERIMENTAL_CXX_MODULE_DYNDEP 1) -set(CMAKE_EXPERIMENTAL_CXX_MODULE_CMAKE_API "2182bf5c-ef0d-489a-91da-49dbc3090d2a") -set(CMAKE_CXX_SCAN_FOR_MODULES ON) +set(CMAKE_C_STANDARD 17) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS OFF) # Multi-threaded statically-linked runtime library (-MT) -set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") +# Debug results in a linker warning, I think because the DX12 libs are linked +# against a Release version of the runtime. +#set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") +set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded") -project(dx12) +add_compile_definitions(-D_AMD64_=1) + +project(dx12c) # External dependencies. add_subdirectory(contrib/DirectX-Headers) @@ -18,7 +20,8 @@ add_subdirectory(contrib/glfw) # Common libraries. add_subdirectory(dxg) -add_subdirectory(dxwindow) +add_subdirectory(app) # Applications. +#add_subdirectory(game) add_subdirectory(hello) diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt new file mode 100644 index 0000000..7b2bdaf --- /dev/null +++ b/app/CMakeLists.txt @@ -0,0 +1,11 @@ +cmake_minimum_required(VERSION 3.20) + +add_library(app + include/dxwindow.h + src/dxwindow.c) + +target_include_directories(app PUBLIC + include) + +target_link_libraries(app PUBLIC + glfw) diff --git a/app/include/dxwindow.h b/app/include/dxwindow.h new file mode 100644 index 0000000..7e5a373 --- /dev/null +++ b/app/include/dxwindow.h @@ -0,0 +1,28 @@ +#pragma once + +#include // HWND + +#include + +typedef struct Window Window; + +/// Initialise the window subsystem. +/// +/// This function must be called at the start of your application before any +/// Windows are created. +bool window_global_init(); + +/// Terminate the window subsystem. +/// +/// This function should be called at the end of your application. Any existing +/// Windows are destroyed and are invalid beyond this call. +void window_global_quit(); + +/// Return the last Window error. +const char* window_get_error(); + +Window* window_init(int width, int height, const char* title); +void window_destroy(Window**); +HWND window_handle(Window*); +void window_update(Window*); +bool window_should_close(const Window*); diff --git a/app/src/dxwindow.c b/app/src/dxwindow.c new file mode 100644 index 0000000..3f775e7 --- /dev/null +++ b/app/src/dxwindow.c @@ -0,0 +1,81 @@ +#include + +// Include Windows.h before GLFW to avoid macro redefinition warnings. +#define WIN32_LEAN_AND_MEAN +#include + +#define GLFW_INCLUDE_NONE // Do not include OpenGL headers. +#include + +#define GLFW_EXPOSE_NATIVE_WIN32 +#include + +#include +#include +#include + +typedef struct Window { + GLFWwindow* glfw_window; +} Window; + +static char glfw_error[1024] = {}; + +static void glfw_error_callback(int error, const char* description) { + sprintf_s(glfw_error, sizeof(glfw_error), + "GLFW error %d: %s", error, description); +} + +bool window_global_init() { + glfwSetErrorCallback(glfw_error_callback); + return glfwInit() == GLFW_TRUE; +} + +void window_global_quit() { + glfwTerminate(); +} + +const char* window_get_error(Window* wnd) { + assert(wnd); + return glfw_error; +} + +Window* window_init(int width, int height, const char* title) { + Window* wnd = calloc(1, sizeof(Window)); + if (!wnd) { + return 0; + } + // GLFW by default creates an OpenGL context with the window. + // Use GLFW_NO_API to tell it not to do so. + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + if ((wnd->glfw_window = glfwCreateWindow( + width, height, title, /*monitor=*/NULL, /*share=*/NULL)) == 0) { + free(wnd); + return 0; + } + return wnd; +} + +void window_destroy(Window** ppWindow) { + assert(ppWindow); + Window* wnd = *ppWindow; + if (wnd) { + glfwDestroyWindow(wnd->glfw_window); + free(wnd); + *ppWindow = 0; + } +} + +HWND window_handle(Window* wnd) { + assert(wnd); + return glfwGetWin32Window(wnd->glfw_window); +} + +void window_update(Window* wnd) { + assert(wnd); + glfwPollEvents(); +} + +bool window_should_close(const Window* wnd) { + assert(wnd); + return glfwWindowShouldClose(wnd->glfw_window) == GLFW_TRUE; +} diff --git a/contrib/glfw/CMakeLists.txt b/contrib/glfw/CMakeLists.txt index 5635cfb..3521151 100644 --- a/contrib/glfw/CMakeLists.txt +++ b/contrib/glfw/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.25) +cmake_minimum_required(VERSION 3.20) add_library(glfw INTERFACE) diff --git a/dxg/CMakeLists.txt b/dxg/CMakeLists.txt index 6fa5401..b7607c0 100644 --- a/dxg/CMakeLists.txt +++ b/dxg/CMakeLists.txt @@ -1,20 +1,23 @@ -cmake_minimum_required(VERSION 3.25) +cmake_minimum_required(VERSION 3.20) project(dxg) -add_library(dxg) +add_library(dxg + include/dxg/dxcommon.h + src/dxg.c) -target_sources(dxg PUBLIC - dxcommon.h) - -target_sources(dxg PUBLIC - FILE_SET cxx_modules TYPE CXX_MODULES FILES - dxcommon.ixx) +# target_sources(dxg PUBLIC +# FILE_SET cxx_modules TYPE CXX_MODULES FILES +# asset.ixx +# dxcommon.ixx +# dxg.ixx +# imm.ixx) target_include_directories(dxg PUBLIC - .) + include) target_link_libraries(dxg PUBLIC DirectX-Headers D3D12.lib - DXGI.lib) + DXGI.lib + DXGUID.lib) # For IID_Xyz symbols diff --git a/dxg/dxcommon.h b/dxg/dxcommon.h deleted file mode 100644 index addb8c3..0000000 --- a/dxg/dxcommon.h +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once - -#include -#include -#include - -#define THROW(error) throw exception(error, __FILE__, __LINE__) - -#define ThrowIfFailed(result) \ -{\ - if (result != S_OK) \ - {\ - THROW(result);\ - }\ -} - -//#define IID_PPV_ARGS(ppType) __uuidof(**(ppType)), static_cast(ppType) diff --git a/dxg/dxcommon.ixx b/dxg/dxcommon.ixx deleted file mode 100644 index b06ae95..0000000 --- a/dxg/dxcommon.ixx +++ /dev/null @@ -1,53 +0,0 @@ -module; - -#include -#include - -export module dxcommon; - -using Microsoft::WRL::ComPtr; - -namespace dx { - -export { - -class exception : public std::exception -{ -public: - exception() noexcept = default; - - exception(HRESULT result, const char* file, int line) noexcept - { - sprintf_s(m_error, sizeof(m_error), "%s:%d Failed with HRESULT = %08X", - file, line, static_cast(result)); - } - - exception(const char* error, const char* file, int line) noexcept - { - sprintf_s(m_error, sizeof(m_error), "%s:%d %s", file, line, error); - } - - [[nodiscard]] const char* what() const noexcept final - { - return m_error; - } - -private: - static thread_local char m_error[1024]; -}; - -template -void SafeRelease(ComPtr& ptr) -{ - if (ptr) - { - ptr->Release(); - ptr = nullptr; - } -} - -} // export - -thread_local char exception::m_error[1024]; - -} // dx diff --git a/dxg/include/dxg/dxcommon.h b/dxg/include/dxg/dxcommon.h new file mode 100644 index 0000000..bfcdbe8 --- /dev/null +++ b/dxg/include/dxg/dxcommon.h @@ -0,0 +1,55 @@ +#pragma once + +#include +#include +#include + +#include +#include + +#define TRAP(ERROR) { \ + fprintf(stderr, "Error in file:[%s] line:%d: %s", __FILE__, __LINE__, ERROR);\ + assert(false);\ + __debugbreak();\ +} + +#define TRAP_HRESULT(RESULT) { \ + fprintf(stderr, "HRESULT: %u", RESULT);\ + TRAP("API call failed")\ +} + +#define TrapIfFailed(RESULT) {\ + if (RESULT != S_OK) {\ + TRAP_HRESULT(RESULT);\ + }\ +} + +#define SafeRelease(PTR) {\ + if (PTR) {\ + PTR->lpVtbl->Release(PTR);\ + PTR = 0;\ + }\ +} + +#define CD3DX12_CPU_DESCRIPTOR_HANDLE(HANDLE, INDEX, SIZE) \ + (D3D12_CPU_DESCRIPTOR_HANDLE){HANDLE.ptr + (INDEX * SIZE)} + +#define OFFSET_HANDLE(HANDLE, OFFSET, SIZE) \ + (D3D12_CPU_DESCRIPTOR_HANDLE){HANDLE.ptr + (OFFSET * SIZE)} + +static inline D3D12_RESOURCE_BARRIER CD3DX12_RESOURCE_BARRIER_Transition( + _In_ ID3D12Resource* pResource, + D3D12_RESOURCE_STATES stateBefore, + D3D12_RESOURCE_STATES stateAfter) { + return (D3D12_RESOURCE_BARRIER){ + .Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION, + .Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE, + .Transition.pResource = pResource, + .Transition.StateBefore = stateBefore, + .Transition.StateAfter = stateAfter, + .Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES}; +} + +typedef enum SampleMask { + PointSampling = 0xffffffff +} SampleMask; diff --git a/dxg/src/dxg.c b/dxg/src/dxg.c new file mode 100644 index 0000000..e985d3d --- /dev/null +++ b/dxg/src/dxg.c @@ -0,0 +1 @@ +int x = 2; diff --git a/dxwindow/CMakeLists.txt b/dxwindow/CMakeLists.txt deleted file mode 100644 index 16c1709..0000000 --- a/dxwindow/CMakeLists.txt +++ /dev/null @@ -1,10 +0,0 @@ -cmake_minimum_required(VERSION 3.25) - -add_library(dxwindow) - -target_sources(dxwindow PUBLIC - FILE_SET cxx_modules TYPE CXX_MODULES FILES - dxwindow.ixx) - -target_link_libraries(dxwindow PUBLIC - glfw) diff --git a/dxwindow/dxwindow.ixx b/dxwindow/dxwindow.ixx deleted file mode 100644 index 6efcc18..0000000 --- a/dxwindow/dxwindow.ixx +++ /dev/null @@ -1,113 +0,0 @@ -module; - -// Include Windows.h before GLFW to avoid macro redefinition warnings. -#define WIN32_LEAN_AND_MEAN -#include - -#define GLFW_INCLUDE_NONE // Do not include OpenGL headers. -#include - -#define GLFW_EXPOSE_NATIVE_WIN32 -#include - -#include -#include - -export module dxwindow; - -namespace dx { - -char glfw_error[1024] = {}; - -void glfw_error_callback(int error, const char* description) -{ - sprintf_s(glfw_error, sizeof(glfw_error), - "GLFW error %d: %s", error, description); -} - -export { - -class Window -{ -public: - ~Window() - { - if (m_window != nullptr) - { - glfwDestroyWindow(m_window); - } - } - - /// Creates the window. - bool Initialise(int width, int height, const char* title) - { - // GLFW by default creates an OpenGL context with the window. - // Use GLFW_NO_API to tell it not to do so. - glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); - - if ((m_window = glfwCreateWindow( - width, height, title, /*monitor=*/NULL, /*share=*/NULL)) == nullptr) - { - return false; - } - - return true; - } - - /// Returns the native window handle. - /// If the window has not been initialized, returns an invalid handle. - HWND GetWindowHandle() - { - if (!m_window) - { - return NULL; - } - return glfwGetWin32Window(m_window); - } - - /// Updates the window by polling for user input. - void Update() - { - assert(m_window); - glfwPollEvents(); - } - - /// Returns true if the user tried to close the window, false otherwise. - bool ShouldClose() const - { - assert(m_window); - return glfwWindowShouldClose(m_window) == GLFW_TRUE; - } - -private: - GLFWwindow* m_window = nullptr; -}; - -/// Initialise the window subsystem. -/// -/// This function must be called at the start of your application before any -/// Windows are created. -bool WindowInitialise() -{ - glfwSetErrorCallback(glfw_error_callback); - return glfwInit() == GLFW_TRUE; -} - -/// Terminate the window subsystem. -/// -/// This function should be called at the end of your application. Any existing -/// Windows are destroyed and are invalid beyond this call. -void WindowTerminate() -{ - glfwTerminate(); -} - -/// Returns the last Window error. -const char* GetWindowError() -{ - return glfw_error; -} - -} // export - -} // namespace dx diff --git a/hello/CMakeLists.txt b/hello/CMakeLists.txt index 2804b24..b3c1507 100644 --- a/hello/CMakeLists.txt +++ b/hello/CMakeLists.txt @@ -1,10 +1,10 @@ -cmake_minimum_required(VERSION 3.25) +cmake_minimum_required(VERSION 3.20) project(hello) add_executable(hello - main.cc) + main.c) target_link_libraries(hello PRIVATE - dxg - dxwindow) + app + dxg) diff --git a/hello/main.c b/hello/main.c new file mode 100644 index 0000000..738f5c0 --- /dev/null +++ b/hello/main.c @@ -0,0 +1,430 @@ +#include +#include + +#include +#include + +#define SWAP_CHAIN_BUFFER_COUNT 2 // Double-buffering. + +typedef struct D3DSettings +{ + int width; + int height; +} D3DSettings; + +typedef struct D3D { + Window* pWindow; + D3DSettings settings; + + IDXGIFactory4* pDxgiFactory; + ID3D12Device* pDevice; + + ID3D12CommandQueue* pCommandQueue; + ID3D12CommandAllocator* pCommandAllocator; + ID3D12GraphicsCommandList* pCommandList; + + IDXGISwapChain3* pSwapChain; + + ID3D12DescriptorHeap* pRtvHeap; + ID3D12DescriptorHeap* pDsvHeap; + + ID3D12Resource* pSwapChainBuffer[SWAP_CHAIN_BUFFER_COUNT]; + ID3D12Resource* pDepthStencilBuffer; + + ID3D12Fence* pFence; + HANDLE fence_event; + UINT64 fence_value; + + UINT rtv_descriptor_size; + UINT dsv_descriptor_size; + UINT cbv_descriptor_size; +} D3D; + +/// Creates the application's swap chain. +/// +/// This method can be called multiple times to re-create the swap chain. +static void d3d_create_swap_chain(D3D* d3d) { + assert(d3d); + assert(d3d->pDxgiFactory); + assert(d3d->pCommandQueue); + + SafeRelease(d3d->pSwapChain); + + DXGI_SWAP_CHAIN_DESC1 desc = (DXGI_SWAP_CHAIN_DESC1){ + .Width = (UINT)(d3d->settings.width), + .Height = (UINT)(d3d->settings.height), + .Format = DXGI_FORMAT_R8G8B8A8_UNORM, + .SampleDesc = (DXGI_SAMPLE_DESC){ + .Count = 1, + .Quality = 0, + }, + .BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT, + .BufferCount = SWAP_CHAIN_BUFFER_COUNT, + .SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD, + }; + IDXGISwapChain1* pSwapChain = 0; + TrapIfFailed(d3d->pDxgiFactory->lpVtbl->CreateSwapChainForHwnd( + d3d->pDxgiFactory, + (IUnknown*)d3d->pCommandQueue, // Swap chain uses queue to perform flush. + window_handle(d3d->pWindow), + &desc, + /*pFullScreenDesc=*/0, // Running in windowed mode. + /*pRestrictToOutput=*/0, + &pSwapChain)); + //TrapIfFailed(pSwapChain.As(&d3d->pSwapChain)); + d3d->pSwapChain = (IDXGISwapChain3*)pSwapChain; +} + +/// Creates RTVs for all of the swap chain's buffers. +static void d3d_create_swap_chain_buffer_render_target_views(D3D* d3d) { + assert(d3d); + assert(d3d->pDevice); + assert(d3d->pSwapChain); + assert(d3d->pRtvHeap); + + // Create the new buffer views. + D3D12_CPU_DESCRIPTOR_HANDLE rtv_heap_handle; + d3d->pRtvHeap->lpVtbl->GetCPUDescriptorHandleForHeapStart(d3d->pRtvHeap, &rtv_heap_handle); + for (int i = 0; i < SWAP_CHAIN_BUFFER_COUNT; ++i) + { + TrapIfFailed(d3d->pSwapChain->lpVtbl->GetBuffer( + d3d->pSwapChain, i, &IID_ID3D12Resource, &d3d->pSwapChainBuffer[i])); + + d3d->pDevice->lpVtbl->CreateRenderTargetView( + d3d->pDevice, d3d->pSwapChainBuffer[i], /*pDesc=*/0, rtv_heap_handle); + + rtv_heap_handle = OFFSET_HANDLE(rtv_heap_handle, 1, d3d->rtv_descriptor_size); + } +} + +static D3D12_CPU_DESCRIPTOR_HANDLE d3d_get_depth_stencil_view(D3D* d3d) { + assert(d3d); + assert(d3d->pDsvHeap); + D3D12_CPU_DESCRIPTOR_HANDLE handle; + d3d->pDsvHeap->lpVtbl->GetCPUDescriptorHandleForHeapStart(d3d->pDsvHeap, &handle); + return handle; +} + +/// Creates a depth/stencil buffer and its view. +static void d3d_create_depth_stencil_buffer_and_view(D3D* d3d) { + assert(d3d); + assert(d3d->pDevice); + + const D3D12_RESOURCE_DESC depth_stencil_desc = (D3D12_RESOURCE_DESC){ + .Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D, + .Alignment = 0, + .Width = (UINT64)(d3d->settings.width), + .Height = (UINT)(d3d->settings.height), + .DepthOrArraySize = 1, + .MipLevels = 1, + .Format = DXGI_FORMAT_D24_UNORM_S8_UINT, + .SampleDesc = (DXGI_SAMPLE_DESC){ + .Count = 1, + .Quality = 0, + }, + .Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN, + .Flags = D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL, + }; + const D3D12_CLEAR_VALUE opt_clear_value = (D3D12_CLEAR_VALUE){ + .Format = depth_stencil_desc.Format, + .DepthStencil = (D3D12_DEPTH_STENCIL_VALUE){ + .Depth = 1.0f, + .Stencil = 0, + }, + }; + const D3D12_HEAP_PROPERTIES depth_stencil_heap_properties = (D3D12_HEAP_PROPERTIES){ + .Type = D3D12_HEAP_TYPE_DEFAULT, + .CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN, + .MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN, + .CreationNodeMask = 1, + .VisibleNodeMask = 1, + }; + TrapIfFailed(d3d->pDevice->lpVtbl->CreateCommittedResource( + d3d->pDevice, + &depth_stencil_heap_properties, + D3D12_HEAP_FLAG_NONE, + &depth_stencil_desc, + D3D12_RESOURCE_STATE_COMMON, + &opt_clear_value, + &IID_ID3D12Resource, + &d3d->pDepthStencilBuffer)); + + d3d->pDevice->lpVtbl->CreateDepthStencilView( + d3d->pDevice, + d3d->pDepthStencilBuffer, + /*pDesc=*/0, + d3d_get_depth_stencil_view(d3d)); +} + +/// Creates RTV and DSV descriptor heaps. +static void d3d_create_descriptor_heaps(D3D* d3d) { + assert(d3d); + assert(d3d->pDevice); + + // The RTV heap must hold as many descriptors as we have buffers in the + // swap chain. + const D3D12_DESCRIPTOR_HEAP_DESC rtv_heap_desc = (D3D12_DESCRIPTOR_HEAP_DESC){ + .Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV, + .NumDescriptors = SWAP_CHAIN_BUFFER_COUNT, + .Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE, + .NodeMask = 0, + }; + TrapIfFailed(d3d->pDevice->lpVtbl->CreateDescriptorHeap( + d3d->pDevice, &rtv_heap_desc, &IID_ID3D12DescriptorHeap, &d3d->pRtvHeap)); + + // For the depth/stencil buffer, we just need one view. + const D3D12_DESCRIPTOR_HEAP_DESC dsv_heap_desc = (D3D12_DESCRIPTOR_HEAP_DESC){ + .Type = D3D12_DESCRIPTOR_HEAP_TYPE_DSV, + .NumDescriptors = 1, + .Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE, + .NodeMask = 0, + }; + TrapIfFailed(d3d->pDevice->lpVtbl->CreateDescriptorHeap( + d3d->pDevice, &dsv_heap_desc, &IID_ID3D12DescriptorHeap, &d3d->pDsvHeap)); +} + +static void d3d_init(D3D* d3d, Window* pWindow, const D3DSettings* pSettings) { + assert(d3d); + assert(pWindow); + assert(pSettings); + + d3d->pWindow = pWindow; + d3d->settings = *pSettings; + + UINT dxgiFactoryFlags = 0; +#ifdef DEBUG + ID3D12Debug* debug = 0; + D3D12GetDebugInterface(&IID_ID3D12Debug, (&debug)); + debug->EnableDebugLayer(); + dxgiFactoryFlags |= DXGI_CREATE_FACTORY_DEBUG; +#endif + TrapIfFailed(CreateDXGIFactory2( + dxgiFactoryFlags, &IID_IDXGIFactory4, &d3d->pDxgiFactory)); + + // Prevent Alt+Enter from going into fullscreen. + TrapIfFailed(d3d->pDxgiFactory->lpVtbl->MakeWindowAssociation( + d3d->pDxgiFactory, + window_handle(d3d->pWindow), + DXGI_MWA_NO_ALT_ENTER)); + + TrapIfFailed(D3D12CreateDevice( + /*pAdapter=*/0, // Default adapter. + D3D_FEATURE_LEVEL_11_0, + &IID_ID3D12Device, + &d3d->pDevice)); + + d3d->rtv_descriptor_size = d3d->pDevice->lpVtbl->GetDescriptorHandleIncrementSize( + d3d->pDevice, + D3D12_DESCRIPTOR_HEAP_TYPE_RTV); + d3d->dsv_descriptor_size = d3d->pDevice->lpVtbl->GetDescriptorHandleIncrementSize( + d3d->pDevice, + D3D12_DESCRIPTOR_HEAP_TYPE_DSV); + d3d->cbv_descriptor_size = d3d->pDevice->lpVtbl->GetDescriptorHandleIncrementSize( + d3d->pDevice, + D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV); + + const D3D12_COMMAND_QUEUE_DESC queue_desc = (D3D12_COMMAND_QUEUE_DESC){ + .Type = D3D12_COMMAND_LIST_TYPE_DIRECT, + .Flags = D3D12_COMMAND_QUEUE_FLAG_NONE, + }; + TrapIfFailed(d3d->pDevice->lpVtbl->CreateCommandQueue( + d3d->pDevice, + &queue_desc, + &IID_ID3D12CommandQueue, + &d3d->pCommandQueue)); + + // The command allocator is the memory backing for the command list. + // It is in the allocator's memory where the commands are stored. + TrapIfFailed(d3d->pDevice->lpVtbl->CreateCommandAllocator( + d3d->pDevice, + queue_desc.Type, + &IID_ID3D12CommandAllocator, + &d3d->pCommandAllocator)); + + TrapIfFailed(d3d->pDevice->lpVtbl->CreateCommandList( + d3d->pDevice, + /*nodeMask=*/0, + queue_desc.Type, + d3d->pCommandAllocator, + /*pInitialState=*/0, // Pipeline state. + &IID_ID3D12CommandList, + &d3d->pCommandList)); + + // Command lists are in the "open" state after they are created. It is + // easier to assume that they start in the "closed" state at each + // iteration of the main loop, however. The Reset() method, which we'll + // use later, also expects the command list to be closed. + TrapIfFailed(d3d->pCommandList->lpVtbl->Close(d3d->pCommandList)); + + d3d_create_descriptor_heaps(d3d); + + d3d_create_swap_chain(d3d); + d3d_create_swap_chain_buffer_render_target_views(d3d); + d3d_create_depth_stencil_buffer_and_view(d3d); + + TrapIfFailed(d3d->pDevice->lpVtbl->CreateFence( + d3d->pDevice, + d3d->fence_value, + D3D12_FENCE_FLAG_NONE, + &IID_ID3D12Fence, + &d3d->pFence)); + + if ((d3d->fence_event = CreateEvent(0, FALSE, FALSE, 0)) == 0) { + TrapIfFailed(HRESULT_FROM_WIN32(GetLastError())); + } +} + +static D3D12_CPU_DESCRIPTOR_HANDLE d3d_get_current_back_buffer_view(const D3D* d3d) { + assert(d3d); + assert(d3d->pRtvHeap); + assert(d3d->rtv_descriptor_size > 0); + D3D12_CPU_DESCRIPTOR_HANDLE rtv_handle; + d3d->pRtvHeap->lpVtbl->GetCPUDescriptorHandleForHeapStart(d3d->pRtvHeap, &rtv_handle); + return CD3DX12_CPU_DESCRIPTOR_HANDLE( + rtv_handle, + d3d->pSwapChain->lpVtbl->GetCurrentBackBufferIndex(d3d->pSwapChain), + d3d->rtv_descriptor_size); +} + +static ID3D12Resource* d3d_get_current_back_buffer(const D3D* d3d) { + assert(d3d); + return d3d->pSwapChainBuffer[d3d->pSwapChain->lpVtbl->GetCurrentBackBufferIndex(d3d->pSwapChain)]; +} + +static void d3d_populate_command_list(D3D* d3d) { + assert(d3d); + + /// Note that we skip the following two items: + /// + /// 1. RSSetViewports() + /// 2. OMSetRenderTargets() + /// + /// This application does not render anything useful, it simply clears + /// the back buffer and depth/stencil view. Clearing both resources + /// does not require a viewport to be set or the OM (output-merger + /// stage) to be configured. + + // A command allocator can only be reset when its associated command + // lists are finished executing on the GPU. This requires + // synchronisation. + TrapIfFailed(d3d->pCommandAllocator->lpVtbl->Reset(d3d->pCommandAllocator)); + + // A command list can be reset as soon as it is executed with + // ExecuteCommandList(). Reset() does require that the command list is + // in a "closed" state, however, which is why we Close() it right away + // after creation. + TrapIfFailed(d3d->pCommandList->lpVtbl->Reset( + d3d->pCommandList, + d3d->pCommandAllocator, + /*pInitialState=*/0)); + + // Indicate that we intend to use the back buffer as a render target. + const D3D12_RESOURCE_BARRIER render_barrier = CD3DX12_RESOURCE_BARRIER_Transition( + d3d_get_current_back_buffer(d3d), + D3D12_RESOURCE_STATE_PRESENT, + D3D12_RESOURCE_STATE_RENDER_TARGET); + d3d->pCommandList->lpVtbl->ResourceBarrier(d3d->pCommandList, 1, &render_barrier); + + // Record commands. + const float clear_colour[] = { 0.0f, 0.502f, 0.494f, 0.0f }; + d3d->pCommandList->lpVtbl->ClearRenderTargetView( + d3d->pCommandList, + d3d_get_current_back_buffer_view(d3d), + clear_colour, + 0, // Number of rectangles in the following array. + 0); // No rectangles; clear the entire resource. + + d3d->pCommandList->lpVtbl->ClearDepthStencilView( + d3d->pCommandList, + d3d_get_depth_stencil_view(d3d), + D3D12_CLEAR_FLAG_DEPTH | D3D12_CLEAR_FLAG_STENCIL, + 1.0f, // Depth. + 0, // Stencil. + 0, // Number of rectangles in the following array. + 0); // No rectangles; clear the entire resource view. + + // Indicate that we now intend to use the back buffer to present. + const D3D12_RESOURCE_BARRIER present_barrier = CD3DX12_RESOURCE_BARRIER_Transition( + d3d_get_current_back_buffer(d3d), + D3D12_RESOURCE_STATE_RENDER_TARGET, + D3D12_RESOURCE_STATE_PRESENT); + d3d->pCommandList->lpVtbl->ResourceBarrier(d3d->pCommandList, 1, &present_barrier); + + // A command list must be closed before it can be executed. + TrapIfFailed(d3d->pCommandList->lpVtbl->Close(d3d->pCommandList)); +} + +static void d3d_wait_for_previous_frame(D3D* d3d) { + assert(d3d); + + // Advance the fence value to mark commands up to this fence point. + d3d->fence_value++; + + // The command queue will signal the new fence value when all commands + // up to this point have finished execution. + TrapIfFailed(d3d->pCommandQueue->lpVtbl->Signal( + d3d->pCommandQueue, d3d->pFence, d3d->fence_value)); + + // Wait for commands to finish execution. + // It is possible that execution has already finished by the time we + // get here, so first check the fence's completed value. + if (d3d->pFence->lpVtbl->GetCompletedValue(d3d->pFence) < d3d->fence_value) { + // Commands are still being executed. Configure a Windows event + // and wait for it. The event fires when the commands have finished + // execution. + + // Indicate that |fence_event| is to be fired when |fence| + // reaches the new fence value. + TrapIfFailed(d3d->pFence->lpVtbl->SetEventOnCompletion( + d3d->pFence, d3d->fence_value, d3d->fence_event)); + + // Will wake up when the fence takes on the new fence value. + WaitForSingleObject(d3d->fence_event, INFINITE); + } +} + +static void d3d_render(D3D* d3d) { + assert(d3d); + + d3d_populate_command_list(d3d); + + ID3D12CommandList* command_lists[] = { (ID3D12CommandList*)d3d->pCommandList }; + d3d->pCommandQueue->lpVtbl->ExecuteCommandLists( + d3d->pCommandQueue, _countof(command_lists), command_lists); + + TrapIfFailed(d3d->pSwapChain->lpVtbl->Present( + d3d->pSwapChain, /*SyncInterval=*/1, /*Flags=*/0)); + + // It is not efficient to wait for the frame to complete here, but it + // is simple and sufficient for this application. + d3d_wait_for_previous_frame(d3d); +} + +int main(int argc, const char** argv) { + const D3DSettings settings = (D3DSettings){ + .width = 1920, + .height = 1200, + }; + + if (!window_global_init()) { + TRAP("Failed to initialise the window subsystem"); + } + + Window* window = window_init(settings.width, settings.height, "D3D Application"); + if (!window) { + TRAP(window_get_error()); + } + + D3D d3d = {0}; + d3d_init(&d3d, window, &settings); + + while (!window_should_close(window)) + { + window_update(window); + d3d_render(&d3d); + Sleep(10); + } + + window_global_quit(); + return 0; +} diff --git a/hello/main.cc b/hello/main.cc deleted file mode 100644 index d9040b4..0000000 --- a/hello/main.cc +++ /dev/null @@ -1,433 +0,0 @@ -#include - -#include -#include - -import dxcommon; -import dxwindow; - -using namespace dx; -using Microsoft::WRL::ComPtr; - -struct D3DSettings -{ - int width = 0; - int height = 0; -}; - -class D3D -{ -public: - void Initialise(Window* window, const D3DSettings& settings) - { - m_window = window; - m_settings = settings; - - UINT dxgiFactoryFlags = 0; -#ifdef DEBUG - { - ComPtr debug; - D3D12GetDebugInterface(IID_PPV_ARGS(&debug)); - debug->EnableDebugLayer(); - dxgiFactoryFlags |= DXGI_CREATE_FACTORY_DEBUG; - } -#endif - ThrowIfFailed(CreateDXGIFactory2( - dxgiFactoryFlags, IID_PPV_ARGS(&m_dxgi_factory))); - - // Prevent Alt+Enter from going into fullscreen. - ThrowIfFailed(m_dxgi_factory->MakeWindowAssociation( - m_window->GetWindowHandle(), - DXGI_MWA_NO_ALT_ENTER)); - - ThrowIfFailed(D3D12CreateDevice( - /*pAdapter=*/nullptr, // Default adapter. - D3D_FEATURE_LEVEL_11_0, - IID_PPV_ARGS(&m_device))); - - m_rtv_descriptor_size = m_device->GetDescriptorHandleIncrementSize( - D3D12_DESCRIPTOR_HEAP_TYPE_RTV); - m_dsv_descriptor_size = m_device->GetDescriptorHandleIncrementSize( - D3D12_DESCRIPTOR_HEAP_TYPE_DSV); - m_cbv_descriptor_size = m_device->GetDescriptorHandleIncrementSize( - D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV); - - const D3D12_COMMAND_QUEUE_DESC queue_desc - { - .Type = D3D12_COMMAND_LIST_TYPE_DIRECT, - .Flags = D3D12_COMMAND_QUEUE_FLAG_NONE, - }; - ThrowIfFailed(m_device->CreateCommandQueue( - &queue_desc, - IID_PPV_ARGS(&m_command_queue))); - - // The command allocator is the memory backing for the command list. - // It is in the allocator's memory where the commands are stored. - ThrowIfFailed(m_device->CreateCommandAllocator( - queue_desc.Type, - IID_PPV_ARGS(&m_command_allocator))); - - ThrowIfFailed(m_device->CreateCommandList( - /*nodeMask=*/0, - queue_desc.Type, - m_command_allocator.Get(), - /*pInitialState=*/nullptr, // Pipeline state. - IID_PPV_ARGS(&m_command_list))); - - // Command lists are in the "open" state after they are created. It is - // easier to assume that they start in the "closed" state at each - // iteration of the main loop, however. The Reset() method, which we'll - // use later, also expects the command list to be closed. - ThrowIfFailed(m_command_list->Close()); - - CreateDescriptorHeaps(); - - CreateSwapChain(); - CreateSwapChainBufferRenderTargetViews(); - CreateDepthStencilBufferAndView(); - - ThrowIfFailed(m_device->CreateFence( - /*InitialValue=*/m_fence_value, - D3D12_FENCE_FLAG_NONE, - IID_PPV_ARGS(&m_fence))); - - if ((m_fence_event = CreateEvent( - /*lpEventAttributes=*/nullptr, - /*bManualReset=*/FALSE, - /*bInitialState=*/FALSE, - /*lpName=*/nullptr)) == 0) - { - ThrowIfFailed(HRESULT_FROM_WIN32(GetLastError())); - } - } - - void Render() - { - PopulateCommandList(); - - ID3D12CommandList* command_lists[] { m_command_list.Get() }; - m_command_queue->ExecuteCommandLists( - _countof(command_lists), command_lists); - - ThrowIfFailed(m_swap_chain->Present(/*SyncInterval=*/1, /*Flags=*/0)); - - // It is not efficient to wait for the frame to complete here, but it - // is simple and sufficient for this application. - WaitForPreviousFrame(); - } - -private: - void PopulateCommandList() - { - /// Note that we skip the following two items: - /// - /// 1. RSSetViewports() - /// 2. OMSetRenderTargets() - /// - /// This application does not render anything useful, it simply clears - /// the back buffer and depth/stencil view. Clearing both resources - /// does not require a viewport to be set or the OM (output-merger - /// stage) to be configured. - - // A command allocator can only be reset when its associated command - // lists are finished executing on the GPU. This requires - // synchronisation. - ThrowIfFailed(m_command_allocator->Reset()); - - // A command list can be reset as soon as it is executed with - // ExecuteCommandList(). Reset() does require that the command list is - // in a "closed" state, however, which is why we Close() it right away - // after creation. - ThrowIfFailed(m_command_list->Reset( - m_command_allocator.Get(), - /*pInitialState=*/nullptr)); - - // Indicate that we intend to use the back buffer as a render target. - const auto render_barrier = CD3DX12_RESOURCE_BARRIER::Transition( - GetCurrentBackBuffer(), - D3D12_RESOURCE_STATE_PRESENT, - D3D12_RESOURCE_STATE_RENDER_TARGET); - m_command_list->ResourceBarrier(1, &render_barrier); - - // Record commands. - const float clear_colour[] { 0.0f, 0.502f, 0.494f, 0.0f }; - m_command_list->ClearRenderTargetView( - GetCurrentBackBufferView(), - clear_colour, - 0, // Number of rectangles in the following array. - nullptr); // No rectangles; clear the entire resource. - - m_command_list->ClearDepthStencilView( - GetDepthStencilView(), - D3D12_CLEAR_FLAG_DEPTH | D3D12_CLEAR_FLAG_STENCIL, - 1.0f, // Depth. - 0, // Stencil. - 0, // Number of rectangles in the following array. - nullptr); // No rectangles; clear the entire resource view. - - // Indicate that we now intend to use the back buffer to present. - const auto present_barrier = CD3DX12_RESOURCE_BARRIER::Transition( - GetCurrentBackBuffer(), - D3D12_RESOURCE_STATE_RENDER_TARGET, - D3D12_RESOURCE_STATE_PRESENT); - m_command_list->ResourceBarrier(1, &present_barrier); - - // A command list must be closed before it can be executed. - ThrowIfFailed(m_command_list->Close()); - } - - void WaitForPreviousFrame() - { - // Advance the fence value to mark commands up to this fence point. - m_fence_value++; - - // The command queue will signal the new fence value when all commands - // up to this point have finished execution. - ThrowIfFailed(m_command_queue->Signal(m_fence.Get(), m_fence_value)); - - // Wait for commands to finish execution. - // It is possible that execution has already finished by the time we - // get here, so first check the fence's completed value. - if (m_fence->GetCompletedValue() < m_fence_value) - { - // Commands are still being executed. Configure a Windows event - // and wait for it. The event fires when the commands have finished - // execution. - - // Indicate that |m_fence_event| is to be fired when |m_fence| - // reaches the new fence value. - ThrowIfFailed(m_fence->SetEventOnCompletion( - m_fence_value, m_fence_event)); - - // Will wake up when the fence takes on the new fence value. - WaitForSingleObject(m_fence_event, INFINITE); - } - } - - /// Creates RTV and DSV descriptor heaps. - void CreateDescriptorHeaps() - { - assert(m_device); - - // The RTV heap must hold as many descriptors as we have buffers in the - // swap chain. - const D3D12_DESCRIPTOR_HEAP_DESC rtv_heap_desc - { - .Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV, - .NumDescriptors = SWAP_CHAIN_BUFFER_COUNT, - .Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE, - .NodeMask = 0, - }; - ThrowIfFailed(m_device->CreateDescriptorHeap( - &rtv_heap_desc, IID_PPV_ARGS(&m_rtv_heap))); - - // For the depth/stencil buffer, we just need one view. - const D3D12_DESCRIPTOR_HEAP_DESC dsv_heap_desc - { - .Type = D3D12_DESCRIPTOR_HEAP_TYPE_DSV, - .NumDescriptors = 1, - .Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE, - .NodeMask = 0, - }; - ThrowIfFailed(m_device->CreateDescriptorHeap( - &dsv_heap_desc, IID_PPV_ARGS(&m_dsv_heap))); - } - - /// Creates the application's swap chain. - /// - /// This method can be called multiple times to re-create the swap chain. - void CreateSwapChain() - { - assert(m_dxgi_factory); - assert(m_command_queue); - - SafeRelease(m_swap_chain); - - DXGI_SWAP_CHAIN_DESC1 desc - { - .Width = static_cast(m_settings.width), - .Height = static_cast(m_settings.height), - .Format = DXGI_FORMAT_R8G8B8A8_UNORM, - .SampleDesc = DXGI_SAMPLE_DESC - { - .Count = 1, - .Quality = 0, - }, - .BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT, - .BufferCount = SWAP_CHAIN_BUFFER_COUNT, - .SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD, - }; - ComPtr swap_chain; - ThrowIfFailed(m_dxgi_factory->CreateSwapChainForHwnd( - m_command_queue.Get(), // Swap chain uses queue to perform flush. - m_window->GetWindowHandle(), - &desc, - /*pFullScreenDesc=*/nullptr, // Running in windowed mode. - /*pRestrictToOutput=*/nullptr, - &swap_chain)); - ThrowIfFailed(swap_chain.As(&m_swap_chain)); - } - - /// Creates RTVs for all of the swap chain's buffers. - void CreateSwapChainBufferRenderTargetViews() - { - assert(m_device); - assert(m_swap_chain); - assert(m_rtv_heap); - - // Create the new buffer views. - CD3DX12_CPU_DESCRIPTOR_HANDLE rtv_heap_handle( - m_rtv_heap->GetCPUDescriptorHandleForHeapStart()); - for (int i = 0; i < SWAP_CHAIN_BUFFER_COUNT; ++i) - { - ThrowIfFailed(m_swap_chain->GetBuffer( - i, IID_PPV_ARGS(&m_swap_chain_buffer[i]))); - - m_device->CreateRenderTargetView( - m_swap_chain_buffer[i].Get(), /*pDesc=*/nullptr, rtv_heap_handle); - - rtv_heap_handle.Offset(1, m_rtv_descriptor_size); - } - } - - /// Creates a depth/stencil buffer and its view. - void CreateDepthStencilBufferAndView() - { - assert(m_device); - - const D3D12_RESOURCE_DESC depth_stencil_desc - { - .Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D, - .Alignment = 0, - .Width = static_cast(m_settings.width), - .Height = static_cast(m_settings.height), - .DepthOrArraySize = 1, - .MipLevels = 1, - .Format = DXGI_FORMAT_D24_UNORM_S8_UINT, - .SampleDesc = DXGI_SAMPLE_DESC - { - .Count = 1, - .Quality = 0, - }, - .Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN, - .Flags = D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL, - }; - const D3D12_CLEAR_VALUE opt_clear_value - { - .Format = depth_stencil_desc.Format, - .DepthStencil = D3D12_DEPTH_STENCIL_VALUE - { - .Depth = 1.0f, - .Stencil = 0, - }, - }; - const CD3DX12_HEAP_PROPERTIES depth_stencil_heap_properties( - D3D12_HEAP_TYPE_DEFAULT); - ThrowIfFailed(m_device->CreateCommittedResource( - &depth_stencil_heap_properties, - D3D12_HEAP_FLAG_NONE, - &depth_stencil_desc, - D3D12_RESOURCE_STATE_COMMON, - &opt_clear_value, - IID_PPV_ARGS(&m_depth_stencil_buffer))); - - m_device->CreateDepthStencilView( - m_depth_stencil_buffer.Get(), - /*pDesc=*/nullptr, - GetDepthStencilView()); - } - - ID3D12Resource* GetCurrentBackBuffer() const - { - return m_swap_chain_buffer[m_swap_chain->GetCurrentBackBufferIndex()].Get(); - } - - D3D12_CPU_DESCRIPTOR_HANDLE GetCurrentBackBufferView() const - { - assert(m_rtv_heap); - assert(m_rtv_descriptor_size > 0); - return CD3DX12_CPU_DESCRIPTOR_HANDLE( - m_rtv_heap->GetCPUDescriptorHandleForHeapStart(), - m_swap_chain->GetCurrentBackBufferIndex(), - m_rtv_descriptor_size); - } - - D3D12_CPU_DESCRIPTOR_HANDLE GetDepthStencilView() const - { - assert(m_dsv_heap); - return m_dsv_heap->GetCPUDescriptorHandleForHeapStart(); - } - -private: - static constexpr int SWAP_CHAIN_BUFFER_COUNT = 2; // Double-buffering. - - Window* m_window = nullptr; - D3DSettings m_settings; - - ComPtr m_dxgi_factory; - ComPtr m_device; - - ComPtr m_command_queue; - ComPtr m_command_allocator; - ComPtr m_command_list; - - ComPtr m_swap_chain; - - ComPtr m_rtv_heap; - ComPtr m_dsv_heap; - - ComPtr m_swap_chain_buffer[SWAP_CHAIN_BUFFER_COUNT]; - ComPtr m_depth_stencil_buffer; - - ComPtr m_fence; - HANDLE m_fence_event = 0; - UINT64 m_fence_value = 0; - - UINT m_rtv_descriptor_size = 0; - UINT m_dsv_descriptor_size = 0; - UINT m_cbv_descriptor_size = 0; -}; - -int main() -{ - try - { - const D3DSettings settings = - { - // TODO: use 960x600 or 1920x1200 depending on native resolution. - .width = 1920, - .height = 1200, - }; - - if (!WindowInitialise()) - { - THROW("Failed to initialise the window subsystem"); - } - { - Window window; - if (!window.Initialise( - settings.width, settings.height, /*title=*/"D3D Application")) - { - THROW(GetWindowError()); - } - - D3D d3d; - d3d.Initialise(&window, settings); - - while (!window.ShouldClose()) - { - window.Update(); - d3d.Render(); - Sleep(10); - } - } - WindowTerminate(); - - return 0; - } - catch (const std::exception& e) - { - fprintf(stderr, "Exception caught: %s\n", e.what()); - return 1; - } -} -- cgit v1.2.3