Following along with the example for the simple

triangle in the DirectX-Graphics-Samples repository
it can render a simple triangle
This commit is contained in:
William McVicar
2024-04-10 23:08:59 +02:00
parent bfccfbea99
commit 90f8185cbc
15 changed files with 4937 additions and 82 deletions

View File

@@ -2,3 +2,5 @@
An dx12-starter cpp project which can open a simple window, create a dx12 device with associated resources and render a triangle to the screen. The project contains boilerplate for CMake, testing and basic CI. Static Analysis, Unit Tests and Sanitizers are off by default
References
* https://github.com/microsoft/DirectX-Graphics-Samples/tree/master/Samples/Desktop/D3D12HelloWorld/src

4094
external/d3dx12.h vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
target_sources(
dx12-starter PRIVATE
device.h
renderer.h
window.h
)

View File

@@ -1,28 +0,0 @@
#pragma once
#include <memory>
namespace dx12_starter
{
struct DeviceCreateParams
{
void* nativeWindowHandle;
int versionMajor;
int versionMinor;
};
class IDevice
{
public:
virtual ~IDevice() = default;
virtual void ClearBuffers() = 0;
virtual void Present() = 0;
virtual void DrawScene() = 0;
virtual void Destroy() = 0;
static std::unique_ptr<IDevice> Create(const DeviceCreateParams& params);
};
} // namespace dx12_starter

33
include/renderer.h Normal file
View File

@@ -0,0 +1,33 @@
#pragma once
#include <memory>
namespace dx12_starter
{
struct APIVersion
{
uint32_t major;
uint32_t minor;
};
struct RendererCreateParams
{
void* nativeWindowHandle;
APIVersion minimumVersion;
uint32_t frameCount;
};
class IRenderer
{
public:
virtual ~IRenderer() = default;
virtual void RecordCommands() = 0;
virtual void Render() = 0;
virtual void WaitForPreviousFrame() = 0;
virtual void Destroy() = 0;
static std::unique_ptr<IRenderer> Create(const RendererCreateParams& params);
};
} // namespace dx12_starter

View File

@@ -7,16 +7,25 @@ add_executable(
target_sources(${current_target} PRIVATE
pch.h
main.cpp
dx12_device.cpp
dx12_renderer.cpp
win32_window.cpp
dx12_starter_dxgi.h
dx12_starter_dxgi.cpp
)
target_include_directories(${current_target} PUBLIC .)
target_include_directories(${current_target} PUBLIC . ${CMAKE_SOURCE_DIR}/external)
target_precompile_headers(${current_target} PRIVATE pch.h pch.cpp)
target_compile_definitions(${current_target} PRIVATE SHADERS_PATH="${CMAKE_CURRENT_SOURCE_DIR}")
if(MSVC)
target_sources(${current_target} PRIVATE pch.cpp)
target_link_libraries(${current_target} PUBLIC d3d12.lib dxgi.lib)
target_link_libraries(${current_target} PRIVATE
d3d12.lib
dxgi.lib
dxguid.lib
d3dcompiler.lib
runtimeobject.lib
)
endif()
if( ENABLE_ALL_REASONABLE_WARNINGS )

View File

@@ -1,37 +0,0 @@
#include "pch.h"
#include "device.h"
namespace dx12_starter
{
struct Win32DX12Impl final : public IDevice
{
void ClearBuffers();
void Present();
void DrawScene();
void Destroy();
HWND m_windowHandle;
};
void Win32DX12Impl::ClearBuffers()
{
}
void Win32DX12Impl::Present()
{
}
void Win32DX12Impl::DrawScene()
{
}
void Win32DX12Impl::Destroy()
{
}
std::unique_ptr<IDevice> IDevice::Create([[maybe_unused]] const DeviceCreateParams& params)
{
return std::make_unique<Win32DX12Impl>();
}
} // namespace ogl_starter

529
src/dx12_renderer.cpp Normal file
View File

@@ -0,0 +1,529 @@
#include "pch.h"
#include "renderer.h"
#include "dx12_starter_dxgi.h"
namespace dx12_starter
{
namespace
{
static constexpr UINT s_MaxBackBufferCount = 2;
auto ParseAPIVersion(APIVersion version) -> D3D_FEATURE_LEVEL
{
if (version.major < 9)
{
return D3D_FEATURE_LEVEL_1_0_CORE;
}
if (version.major == 9)
{
if (version.minor == 1)
return D3D_FEATURE_LEVEL_9_1;
else if (version.minor == 2)
return D3D_FEATURE_LEVEL_9_2;
else if (version.minor == 3)
return D3D_FEATURE_LEVEL_9_3;
return D3D_FEATURE_LEVEL_9_1;
}
else if (version.major == 10)
{
if (version.minor == 0)
return D3D_FEATURE_LEVEL_10_0;
else if (version.minor == 1)
return D3D_FEATURE_LEVEL_10_0;
return D3D_FEATURE_LEVEL_10_1;
}
else if (version.major == 11)
{
if (version.minor == 0)
return D3D_FEATURE_LEVEL_11_0;
else if (version.minor == 1)
return D3D_FEATURE_LEVEL_11_1;
return D3D_FEATURE_LEVEL_11_0;
}
else if (version.major == 12)
{
if (version.minor == 0)
return D3D_FEATURE_LEVEL_12_0;
else if (version.minor == 1)
return D3D_FEATURE_LEVEL_12_1;
else if (version.minor == 2)
return D3D_FEATURE_LEVEL_12_2;
return D3D_FEATURE_LEVEL_12_0;
}
return D3D_FEATURE_LEVEL_1_0_CORE;
}
auto EnableDeviceDebugLayer(ComPtr<ID3D12Device> device)
{
#ifndef NDEBUG
ComPtr<ID3D12InfoQueue> d3dInfoQueue{};
if (SUCCEEDED(device.As(&d3dInfoQueue)))
{
#ifdef _DEBUG
d3dInfoQueue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_CORRUPTION, true);
d3dInfoQueue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_ERROR, true);
#endif
D3D12_MESSAGE_ID hide[] =
{
D3D12_MESSAGE_ID_MAP_INVALID_NULLRANGE,
D3D12_MESSAGE_ID_UNMAP_INVALID_NULLRANGE,
// Workarounds for debug layer issues on hybrid-graphics systems
D3D12_MESSAGE_ID_EXECUTECOMMANDLISTS_WRONGSWAPCHAINBUFFERREFERENCE,
D3D12_MESSAGE_ID_RESOURCE_BARRIER_MISMATCHING_COMMAND_LIST_TYPE};
D3D12_INFO_QUEUE_FILTER filter = {};
filter.DenyList.NumIDs = _countof(hide);
filter.DenyList.pIDList = hide;
d3dInfoQueue->AddStorageFilterEntries(&filter);
}
#endif
}
} // namespace
struct RendererDX12 final : public IRenderer
{
RendererDX12() = default;
void RecordCommands();
void Render();
void WaitForPreviousFrame();
void Destroy();
HWND m_windowHandle = nullptr;
struct Vertex
{
float position[3];
float color[4];
};
// Pipeline objects.
CD3DX12_VIEWPORT m_viewport = {};
CD3DX12_RECT m_scissorRect = {};
ComPtr<IDXGISwapChain3> m_swapChain = nullptr;
ComPtr<ID3D12Device> m_device = nullptr;
ComPtr<ID3D12Resource> m_renderTargets[s_MaxBackBufferCount] = {};
ComPtr<ID3D12CommandAllocator> m_commandAllocator = nullptr;
ComPtr<ID3D12CommandQueue> m_commandQueue = nullptr;
ComPtr<ID3D12RootSignature> m_rootSignature = nullptr;
ComPtr<ID3D12DescriptorHeap> m_rtvHeap = nullptr;
ComPtr<ID3D12PipelineState> m_pipelineState = nullptr;
ComPtr<ID3D12GraphicsCommandList> m_commandList = nullptr;
UINT m_rtvDescriptorSize = 0u;
// App resources.
ComPtr<ID3D12Resource> m_vertexBuffer = nullptr;
D3D12_VERTEX_BUFFER_VIEW m_vertexBufferView = {};
// Synchronization objects.
UINT m_frameIndex = 0u;
HANDLE m_fenceEvent = nullptr;
ComPtr<ID3D12Fence> m_fence = nullptr;
UINT64 m_fenceValue = 0llu;
};
void RendererDX12::RecordCommands()
{
// Command list allocators can only be reset when the associated
// command lists have finished execution on the GPU; apps should use
// fences to determine GPU execution progress.
if (FAILED(m_commandAllocator->Reset()))
{
MessageBox(m_windowHandle, "Failed to reset command allocator", "Command Allocator Failure", MB_ICONERROR | MB_OK);
return;
}
// However, when ExecuteCommandList() is called on a particular command
// list, that command list can then be reset at any time and must be before
// re-recording.
if (FAILED(m_commandList->Reset(m_commandAllocator.Get(), m_pipelineState.Get())))
{
MessageBox(m_windowHandle, "Failed to reset command list", "Command Allocator Failure", MB_ICONERROR | MB_OK);
return;
}
// Set necessary state.
m_commandList->SetGraphicsRootSignature(m_rootSignature.Get());
m_commandList->RSSetViewports(1, &m_viewport);
m_commandList->RSSetScissorRects(1, &m_scissorRect);
// Indicate that the back buffer will be used as a render target.
{
auto barrier = CD3DX12_RESOURCE_BARRIER::Transition(m_renderTargets[m_frameIndex].Get(), D3D12_RESOURCE_STATE_PRESENT, D3D12_RESOURCE_STATE_RENDER_TARGET);
m_commandList->ResourceBarrier(1, &barrier);
}
CD3DX12_CPU_DESCRIPTOR_HANDLE rtvHandle(m_rtvHeap->GetCPUDescriptorHandleForHeapStart(), m_frameIndex, m_rtvDescriptorSize);
m_commandList->OMSetRenderTargets(1, &rtvHandle, FALSE, nullptr);
// Record commands.
const float clearColor[] = {0.0f, 0.2f, 0.4f, 1.0f};
m_commandList->ClearRenderTargetView(rtvHandle, clearColor, 0, nullptr);
m_commandList->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
m_commandList->IASetVertexBuffers(0, 1, &m_vertexBufferView);
m_commandList->DrawInstanced(3, 1, 0, 0);
// Indicate that the back buffer will now be used to present.
{
auto barrier = CD3DX12_RESOURCE_BARRIER::Transition(m_renderTargets[m_frameIndex].Get(), D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_PRESENT);
m_commandList->ResourceBarrier(1, &barrier);
}
if (FAILED(m_commandList->Close()))
{
MessageBox(m_windowHandle, "Failed to close command list", "Command List Failure", MB_ICONERROR | MB_OK);
}
}
void RendererDX12::Render()
{
// Execute the command list.
ID3D12CommandList* ppCommandLists[] = {m_commandList.Get()};
m_commandQueue->ExecuteCommandLists(_countof(ppCommandLists), ppCommandLists);
// Present the frame.
if (FAILED(m_swapChain->Present(1, 0)))
{
MessageBox(m_windowHandle, "Failed to preset frame", "Swapchain Failure", MB_ICONERROR | MB_OK);
}
}
void RendererDX12::Destroy()
{
WaitForPreviousFrame();
CloseHandle(m_fenceEvent);
}
void RendererDX12::WaitForPreviousFrame()
{
// WAITING FOR THE FRAME TO COMPLETE BEFORE CONTINUING IS NOT BEST PRACTICE.
// This is code implemented as such for simplicity. The D3D12HelloFrameBuffering
// sample illustrates how to use fences for efficient resource usage and to
// maximize GPU utilization.
// Signal and increment the fence value.
const UINT64 fence = std::exchange(m_fenceValue, m_fenceValue + 1);
if (FAILED(m_commandQueue->Signal(m_fence.Get(), fence)))
{
MessageBox(m_windowHandle, "Failed to signal command queue fence", "Command Queue Failure", MB_ICONERROR | MB_OK);
return;
}
// Wait until the previous frame is finished.
if (m_fence->GetCompletedValue() < fence)
{
if (FAILED(m_fence->SetEventOnCompletion(fence, m_fenceEvent)))
{
MessageBox(m_windowHandle, "Failed to signal command queue fence", "Command Queue Failure", MB_ICONERROR | MB_OK);
return;
}
WaitForSingleObject(m_fenceEvent, INFINITE);
}
m_frameIndex = m_swapChain->GetCurrentBackBufferIndex();
}
std::unique_ptr<IRenderer> IRenderer::Create(const RendererCreateParams& params)
{
/*
- Enable DIGI Debug Layers (optional)
- Create DXGI Factory
- Create Device
- Create Command Queue
*/
auto renderer = std::make_unique<RendererDX12>();
UINT dxgiFactoryFlags{};
#if (_DEBUG)
dxgi::EnableDebugLayer();
dxgiFactoryFlags = DXGI_CREATE_FACTORY_DEBUG;
#endif
auto hWnd = static_cast<HWND>(params.nativeWindowHandle);
auto minimumFeatureLevel = ParseAPIVersion(params.minimumVersion);
renderer->m_windowHandle = hWnd;
ComPtr<IDXGIFactory4> factory{};
if (FAILED(CreateDXGIFactory2(dxgiFactoryFlags, IID_PPV_ARGS(&factory))))
{
MessageBoxA(hWnd, "Failed to create DXGIFactory", "DXGI Factory Create Error", MB_ICONERROR | MB_OK);
return nullptr;
}
ComPtr<IDXGIAdapter1> adapter = dxgi::CreateAdapter(factory);
if (FAILED(D3D12CreateDevice(adapter.Get(),
minimumFeatureLevel,
IID_PPV_ARGS(renderer->m_device.ReleaseAndGetAddressOf()))))
{
MessageBoxA(hWnd, "Failed to create D3D12Device", "Device Create Error", MB_ICONERROR | MB_OK);
return nullptr;
}
EnableDeviceDebugLayer(renderer->m_device);
D3D12_COMMAND_QUEUE_DESC queueDesc = {
.Type = D3D12_COMMAND_LIST_TYPE_DIRECT,
.Priority = 0,
.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE,
.NodeMask = 0};
if (FAILED(renderer->m_device->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&renderer->m_commandQueue))))
{
MessageBoxA(hWnd, "Failed to create D3D12CommandQueue", "Command Queue Create Error", MB_ICONERROR | MB_OK);
return nullptr;
}
RECT windowRect{};
GetClientRect(hWnd, &windowRect);
const UINT width = windowRect.right - windowRect.left;
const UINT height = windowRect.bottom - windowRect.top;
const float aspectRatio = static_cast<float>(width) / static_cast<float>(height);
renderer->m_viewport = CD3DX12_VIEWPORT{
static_cast<float>(windowRect.left),
static_cast<float>(windowRect.top),
static_cast<float>(width),
static_cast<float>(height)};
renderer->m_scissorRect = CD3DX12_RECT{
windowRect.left,
windowRect.top,
windowRect.right,
windowRect.bottom};
DXGI_SWAP_CHAIN_DESC1 swapChainDesc = {
.Width = width,
.Height = height,
.Format = DXGI_FORMAT_R8G8B8A8_UNORM,
.Stereo = FALSE,
.SampleDesc = {
.Count = 1,
.Quality = 0},
.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT,
.BufferCount = params.frameCount,
.Scaling = DXGI_SCALING_STRETCH,
.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD,
.AlphaMode = DXGI_ALPHA_MODE_UNSPECIFIED,
.Flags = 0};
if (ComPtr<IDXGISwapChain1> swapChain{}; FAILED(factory->CreateSwapChainForHwnd(renderer->m_commandQueue.Get(), hWnd, &swapChainDesc, nullptr, nullptr, &swapChain)))
{
MessageBoxA(hWnd, "Failed to create DXGISwapChain", "Swap Chain Create Error", MB_ICONERROR | MB_OK);
return nullptr;
}
else
{
if (FAILED(swapChain.As(&renderer->m_swapChain)))
{
MessageBoxA(hWnd, "Failed to create DXGISwapChain3", "Swap Chain Create Error", MB_ICONERROR | MB_OK);
return nullptr;
}
if (FAILED(factory->MakeWindowAssociation(hWnd, DXGI_MWA_NO_ALT_ENTER)))
{
MessageBoxA(hWnd, "Failed to Associate DXGI Factory with HWND", "DXGIFactory Error", MB_ICONERROR | MB_OK);
return nullptr;
}
}
D3D12_DESCRIPTOR_HEAP_DESC rtvHeapDesc = {
.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV,
.NumDescriptors = params.frameCount,
.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE,
.NodeMask = 0u};
if (FAILED(renderer->m_device->CreateDescriptorHeap(&rtvHeapDesc, IID_PPV_ARGS(&renderer->m_rtvHeap))))
{
MessageBoxA(hWnd, "Failed to create D3D12DescriptorHeap", "Device Error", MB_ICONERROR | MB_OK);
return nullptr;
}
renderer->m_rtvDescriptorSize = renderer->m_device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
CD3DX12_CPU_DESCRIPTOR_HANDLE rtvHandle{renderer->m_rtvHeap->GetCPUDescriptorHandleForHeapStart()};
for (uint32_t i = 0; i < params.frameCount; ++i)
{
if (FAILED(renderer->m_swapChain->GetBuffer(i, IID_PPV_ARGS(&renderer->m_renderTargets[i]))))
{
MessageBoxA(hWnd, "Failed to create Render Target", "Device Error", MB_ICONERROR | MB_OK);
return nullptr;
}
renderer->m_device->CreateRenderTargetView(renderer->m_renderTargets[i].Get(), nullptr, rtvHandle);
rtvHandle.Offset(1, renderer->m_rtvDescriptorSize);
}
if (FAILED(renderer->m_device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&renderer->m_commandAllocator))))
{
MessageBoxA(hWnd, "Failed to create Command Allocator", "Device Error", MB_ICONERROR | MB_OK);
return nullptr;
}
// Create an empty root signature.
CD3DX12_ROOT_SIGNATURE_DESC rootSignatureDesc;
rootSignatureDesc.Init(0, nullptr, 0, nullptr, D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT);
ComPtr<ID3DBlob> signature;
ComPtr<ID3DBlob> error;
if (FAILED(D3D12SerializeRootSignature(&rootSignatureDesc, D3D_ROOT_SIGNATURE_VERSION_1, &signature, &error)))
{
MessageBox(nullptr, "Failed to Serialize Root Signature", "Root Signature Failure", MB_ICONERROR | MB_OK);
return nullptr;
}
else if (FAILED(renderer->m_device->CreateRootSignature(0, signature->GetBufferPointer(), signature->GetBufferSize(), IID_PPV_ARGS(&renderer->m_rootSignature))))
{
MessageBox(nullptr, "Failed to Create Root Signature", "Device Failure", MB_ICONERROR | MB_OK);
return nullptr;
}
// Create the pipeline state, which includes compiling and loading shaders.
ComPtr<ID3DBlob> vertexShader{};
ComPtr<ID3DBlob> pixelShader{};
UINT compileFlags = 0;
#if defined(_DEBUG)
// Enable better shader debugging with the graphics debugging tools.
compileFlags |= D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION;
#endif
std::filesystem::path shaders_file = (std::filesystem::path(SHADERS_PATH) / std::filesystem::path("shaders.hlsl")).make_preferred();
if (FAILED(D3DCompileFromFile(shaders_file.wstring().c_str(), nullptr, nullptr, "VSMain", "vs_5_0", compileFlags, 0, &vertexShader, nullptr)))
{
MessageBox(nullptr, "Failed to Compile VSMain from shaders.hlsl", "Shader Compilation Failure", MB_ICONERROR | MB_OK);
return nullptr;
}
if (FAILED(D3DCompileFromFile(shaders_file.wstring().c_str(), nullptr, nullptr, "PSMain", "ps_5_0", compileFlags, 0, &pixelShader, nullptr)))
{
MessageBox(nullptr, "Failed to Compile PSMain from shaders.hlsl", "Shader Compilation Failure", MB_ICONERROR | MB_OK);
return nullptr;
}
// Define the vertex input layout.
D3D12_INPUT_ELEMENT_DESC inputElementDescs[] =
{
{"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0},
{"COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0}};
// Describe and create the graphics pipeline state object (PSO).
D3D12_GRAPHICS_PIPELINE_STATE_DESC psoDesc = {};
psoDesc.InputLayout = {inputElementDescs, _countof(inputElementDescs)};
psoDesc.pRootSignature = renderer->m_rootSignature.Get();
psoDesc.VS = CD3DX12_SHADER_BYTECODE(vertexShader.Get());
psoDesc.PS = CD3DX12_SHADER_BYTECODE(pixelShader.Get());
psoDesc.RasterizerState = CD3DX12_RASTERIZER_DESC(D3D12_DEFAULT);
psoDesc.BlendState = CD3DX12_BLEND_DESC(D3D12_DEFAULT);
psoDesc.DepthStencilState.DepthEnable = FALSE;
psoDesc.DepthStencilState.StencilEnable = FALSE;
psoDesc.SampleMask = UINT_MAX;
psoDesc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;
psoDesc.NumRenderTargets = 1;
psoDesc.RTVFormats[0] = DXGI_FORMAT_R8G8B8A8_UNORM;
psoDesc.SampleDesc.Count = 1;
if (FAILED(renderer->m_device->CreateGraphicsPipelineState(&psoDesc, IID_PPV_ARGS(&renderer->m_pipelineState))))
{
MessageBox(nullptr, "Failed to Create Graphics Pipeline State Object", "PSO failure", MB_ICONERROR | MB_OK);
return nullptr;
}
// Create the command list.
if (FAILED(renderer->m_device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, renderer->m_commandAllocator.Get(), renderer->m_pipelineState.Get(), IID_PPV_ARGS(&renderer->m_commandList))))
{
MessageBox(nullptr, "Failed to Create Command List", "Device failure", MB_ICONERROR | MB_OK);
return nullptr;
}
// Command lists are created in the recording state, but there is nothing
// to record yet. The main loop expects it to be closed, so close it now.
if (FAILED(renderer->m_commandList->Close()))
{
MessageBox(nullptr, "Failed to close Command List", "Command List Failure", MB_ICONERROR | MB_OK);
return nullptr;
}
// Create the vertex buffer.
{
// Define the geometry for a triangle.
RendererDX12::Vertex triangleVertices[] =
{
{{0.0f, 0.25f * aspectRatio, 0.0f}, {1.0f, 0.0f, 0.0f, 1.0f}},
{{0.25f, -0.25f * aspectRatio, 0.0f}, {0.0f, 1.0f, 0.0f, 1.0f}},
{{-0.25f, -0.25f * aspectRatio, 0.0f}, {0.0f, 0.0f, 1.0f, 1.0f}}};
const UINT vertexBufferSize = sizeof(triangleVertices);
// Note: using upload heaps to transfer static data like vert buffers is not
// recommended. Every time the GPU needs it, the upload heap will be marshalled
// over. Please read up on Default Heap usage. An upload heap is used here for
// code simplicity and because there are very few verts to actually transfer.
CD3DX12_HEAP_PROPERTIES cHeapProps(D3D12_HEAP_TYPE_UPLOAD);
auto bufferDesc = CD3DX12_RESOURCE_DESC::Buffer(vertexBufferSize);
HRESULT hr = renderer->m_device->CreateCommittedResource(
&cHeapProps,
D3D12_HEAP_FLAG_NONE,
&bufferDesc,
D3D12_RESOURCE_STATE_GENERIC_READ,
nullptr,
IID_PPV_ARGS(&renderer->m_vertexBuffer));
if (FAILED(hr))
{
MessageBox(nullptr, "Failed to Create Vertex Buffer", "Device Failure", MB_ICONERROR | MB_OK);
return nullptr;
}
// Copy the triangle data to the vertex buffer.
UINT8* pVertexDataBegin;
CD3DX12_RANGE readRange(0, 0); // We do not intend to read from this resource on the CPU.
if (FAILED(renderer->m_vertexBuffer->Map(0, &readRange, reinterpret_cast<void**>(&pVertexDataBegin))))
{
MessageBox(nullptr, "Failed to Map Vertex Buffer", "Vertex Buffer Failure", MB_ICONERROR | MB_OK);
return nullptr;
}
memcpy(pVertexDataBegin, triangleVertices, sizeof(triangleVertices));
renderer->m_vertexBuffer->Unmap(0, nullptr);
// Initialize the vertex buffer view.
renderer->m_vertexBufferView.BufferLocation = renderer->m_vertexBuffer->GetGPUVirtualAddress();
renderer->m_vertexBufferView.StrideInBytes = sizeof(RendererDX12::Vertex);
renderer->m_vertexBufferView.SizeInBytes = vertexBufferSize;
}
if (FAILED(renderer->m_device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&renderer->m_fence))))
{
MessageBoxA(hWnd, "Failed to create Fence", "Device Error", MB_ICONERROR | MB_OK);
return nullptr;
}
renderer->m_fenceValue = 1;
renderer->m_fenceEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
if (renderer->m_fenceEvent == nullptr)
{
MessageBoxA(hWnd, "Failed to create Fence Event Handle", "Win32API Error", MB_ICONERROR | MB_OK);
return nullptr;
}
renderer->WaitForPreviousFrame();
return renderer;
}
} // namespace dx12_starter

158
src/dx12_starter_dxgi.cpp Normal file
View File

@@ -0,0 +1,158 @@
#include "pch.h"
#include "dx12_starter_dxgi.h"
namespace dxgi
{
using PFNDXGIGETDEBUGINTERFACE = HRESULT(*)(const IID&, void**);
static PFNDXGIGETDEBUGINTERFACE DXGIGetDebugInterfaceFunc;
void EnableDebugLayer()
{
#if _DEBUG
if (ComPtr<ID3D12Debug6> debug6; SUCCEEDED(D3D12GetDebugInterface(IID_PPV_ARGS(debug6.GetAddressOf()))))
{
debug6->EnableDebugLayer();
}
else if (ComPtr<ID3D12Debug5> debug5; SUCCEEDED(D3D12GetDebugInterface(IID_PPV_ARGS(debug5.GetAddressOf()))))
{
debug5->EnableDebugLayer();
}
else if (ComPtr<ID3D12Debug4> debug4; SUCCEEDED(D3D12GetDebugInterface(IID_PPV_ARGS(debug4.GetAddressOf()))))
{
debug4->EnableDebugLayer();
}
else if (ComPtr<ID3D12Debug3> debug3; SUCCEEDED(D3D12GetDebugInterface(IID_PPV_ARGS(debug3.GetAddressOf()))))
{
debug3->EnableDebugLayer();
}
//Must be a bug in the API there is no EnableDebugLayer call
//else if (ComPtr<ID3D12Debug2> debug2; SUCCEEDED(D3D12GetDebugInterface(IID_PPV_ARGS(debug2.GetAddressOf()))))
//{
// debug2->EnableDebugLayer();
//}
else if (ComPtr<ID3D12Debug1> debug1; SUCCEEDED(D3D12GetDebugInterface(IID_PPV_ARGS(debug1.GetAddressOf()))))
{
debug1->EnableDebugLayer();
}
else if (ComPtr<ID3D12Debug> debug; SUCCEEDED(D3D12GetDebugInterface(IID_PPV_ARGS(debug.GetAddressOf()))))
{
debug->EnableDebugLayer();
}
else
{
MessageBoxA(nullptr, "Error: Cannot Enable Debug Layer", "Debug Layer Failure", MB_ICONWARNING | MB_OK);
}
const auto onInfoQueueCreated = [](IDXGIInfoQueue* dxgiInfoQueue) {
dxgiInfoQueue->SetBreakOnSeverity(DXGI_DEBUG_ALL, DXGI_INFO_QUEUE_MESSAGE_SEVERITY_ERROR, true);
dxgiInfoQueue->SetBreakOnSeverity(DXGI_DEBUG_ALL, DXGI_INFO_QUEUE_MESSAGE_SEVERITY_CORRUPTION, true);
DXGI_INFO_QUEUE_MESSAGE_ID hide[] =
{
80 /* IDXGISwapChain::GetContainingOutput: The swapchain's adapter does not control the output on which the swapchain's window resides. */,
};
DXGI_INFO_QUEUE_FILTER filter = {};
filter.DenyList.NumIDs = _countof(hide);
filter.DenyList.pIDList = hide;
dxgiInfoQueue->AddStorageFilterEntries(DXGI_DEBUG_DXGI, &filter);
};
ComPtr<IDXGIInfoQueue> infoQueue{};
if (SUCCEEDED(DXGIGetDebugInterface1(0, IID_PPV_ARGS(infoQueue.GetAddressOf()))))
{
onInfoQueueCreated(infoQueue.Get());
}
else if (DXGIGetDebugInterfaceFunc && SUCCEEDED(DXGIGetDebugInterfaceFunc(IID_PPV_ARGS(infoQueue.GetAddressOf()))))
{
onInfoQueueCreated(infoQueue.Get());
}
else
{
MessageBoxA(nullptr, "Error: Cannot Create Debug Interface", "Debug Interface Failure", MB_ICONWARNING | MB_OK);
}
#endif
}
auto CreateAdapter(ComPtr<IDXGIFactory> factory) -> ComPtr<IDXGIAdapter1>
{
ComPtr<IDXGIAdapter1> adapter{};
if (ComPtr<IDXGIFactory6> factory6{}; SUCCEEDED(factory.As<IDXGIFactory6>(&factory6)))
{
for (UINT adapterIndex = 0;
SUCCEEDED(factory6->EnumAdapterByGpuPreference(
adapterIndex,
DXGI_GPU_PREFERENCE_HIGH_PERFORMANCE,
IID_PPV_ARGS(adapter.ReleaseAndGetAddressOf())));
adapterIndex++)
{
DXGI_ADAPTER_DESC1 desc;
if (FAILED(adapter->GetDesc1(&desc)))
{
MessageBoxA(nullptr, "Error: Failed to get the adapter description", "Adapter Desc Error", MB_ICONERROR | MB_OK);
return nullptr;
}
if (desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE)
{
// Don't select the Basic Render Driver adapter.
continue;
}
const auto DebugPrintInfo = [](UINT adapterIndex, const DXGI_ADAPTER_DESC1& desc) {
#ifdef _DEBUG
wchar_t buff[256] = {};
swprintf_s(buff, L"Direct3D Adapter (%u): VID:%04X, PID:%04X - %ls\n", adapterIndex, desc.VendorId, desc.DeviceId, desc.Description);
OutputDebugStringW(buff);
#endif
};
// Check to see if the adapter supports Direct3D 12, but don't create the actual device yet.
if (SUCCEEDED(D3D12CreateDevice(adapter.Get(), D3D_FEATURE_LEVEL_11_0, _uuidof(ID3D12Device), nullptr)))
{
DebugPrintInfo(adapterIndex, desc);
break;
}
}
}
return adapter;
}
auto CreateSwapChain(ComPtr<IDXGIFactory> factory, ComPtr<ID3D12CommandQueue> commandQueue, HWND hWnd, UINT width, UINT height) -> ComPtr<IDXGISwapChain>
{
if (ComPtr<IDXGIFactory4> factory4{}; SUCCEEDED(factory.As<IDXGIFactory4>(&factory4)))
{
DXGI_SWAP_CHAIN_DESC1 desc = {
.Width = width,
.Height = height,
.Format = DXGI_FORMAT_R8G8B8A8_UNORM,
.Stereo = FALSE,
.SampleDesc = {
.Count = 1,
.Quality = 0},
.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT,
.BufferCount = 2,
.Scaling = DXGI_SCALING_STRETCH,
.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD,
.AlphaMode = DXGI_ALPHA_MODE_UNSPECIFIED,
.Flags = 0};
ComPtr<IDXGISwapChain1> swapChain{};
if (FAILED(factory4->CreateSwapChainForHwnd(commandQueue.Get(), hWnd, &desc, nullptr, nullptr, &swapChain)))
{
MessageBox(hWnd, "Failed to create swap chain", "Swap chain error", MB_ICONERROR | MB_OK);
return nullptr;
}
if (FAILED(factory->MakeWindowAssociation(hWnd, DXGI_MWA_NO_ALT_ENTER)))
{
MessageBox(hWnd, "Failed to disable alt enter", "Swap chain error", MB_ICONWARNING | MB_OK);
}
return swapChain;
}
return nullptr;
}
}

6
src/dx12_starter_dxgi.h Normal file
View File

@@ -0,0 +1,6 @@
namespace dxgi
{
void EnableDebugLayer();
auto CreateAdapter(ComPtr<IDXGIFactory> factory) -> ComPtr<IDXGIAdapter1>;
auto CreateSwapChain(ComPtr<IDXGIFactory> factory, ComPtr<ID3D12CommandQueue> commandQueue, HWND hWnd, UINT width, UINT height) -> ComPtr<IDXGISwapChain>;
} // namespace dxgi

5
src/dxgi.h Normal file
View File

@@ -0,0 +1,5 @@
namespace dxgi
{
void EnableDebugLayer();
void CreateFactory();
}

View File

@@ -1,9 +1,13 @@
#include "pch.h"
#include "window.h"
#include "device.h"
#include "renderer.h"
auto main() -> int
{
Microsoft::WRL::Wrappers::RoInitializeWrapper initialize(RO_INIT_MULTITHREADED);
if (FAILED(initialize))
return 1;
auto window = dx12_starter::IWindow::Create({
.x = CW_USEDEFAULT,
.y = CW_USEDEFAULT,
@@ -12,19 +16,25 @@ auto main() -> int
.name = "Starter Dx12"
});
auto renderer = dx12_starter::IDevice::Create({
auto renderer = dx12_starter::IRenderer::Create({
.nativeWindowHandle = window->GetNativeHandle(),
.versionMajor = 4,
.versionMinor = 6
.minimumVersion = {
.major = 11,
.minor = 0
},
.frameCount = 2
});
if (renderer == nullptr)
return 1;
while (window->IsOpen())
{
window->PumpMessages();
renderer->ClearBuffers();
renderer->DrawScene();
renderer->Present();
renderer->RecordCommands();
renderer->Render();
renderer->WaitForPreviousFrame();
}
renderer->Destroy();

View File

@@ -0,0 +1 @@
#include "pch.h"

View File

@@ -1,14 +1,56 @@
#ifndef DX12_STARTER_PCH_H_INCLUDED
#define DX12_STARTER_PCH_H_INCLUDED
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif //WIN32_LEAN_AND_MEAN
#pragma warning(push)
#pragma warning(disable : 4265)
#ifndef NOMINMAX
#include <winsdkver.h>
#define _WIN32_WINNT 0x0A00
#include <sdkddkver.h>
#define NODRAWTEXT
#define NOGDI
#define NOBITMAP
#define NOMXC
#define NOSERVICE
#define NOHELP
#define NOMINMAX
#endif //NOMINMAX
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <wrl/client.h>
#include <wrl/event.h>
#include <roapi.h>
template<typename T>
using ComPtr = Microsoft::WRL::ComPtr<T>;
#if defined(NTDDI_WIN10_RS2)
#include <dxgi1_6.h>
#else
#include <dxgi1_5.h>
#endif
#include <d3d12sdklayers.h>
#include <d3dcompiler.h>
#include "d3dx12.h"
#if _DEBUG
#include <dxgidebug.h>
#endif
#include <memory>
#include <algorithm>
#include <span>
#include <numeric>
#include <array>
#include <vector>
#include <cinttypes>
#include <filesystem>
#pragma warning(pop)
#endif //DX12_STARTER_PCH_H_INCLUDED

31
src/shaders.hlsl Normal file
View File

@@ -0,0 +1,31 @@
//*********************************************************
//
// Copyright (c) Microsoft. All rights reserved.
// This code is licensed under the MIT License (MIT).
// THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
// IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
// PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************
struct PSInput
{
float4 position : SV_POSITION;
float4 color : COLOR;
};
PSInput VSMain(float4 position : POSITION, float4 color : COLOR)
{
PSInput result;
result.position = position;
result.color = color;
return result;
}
float4 PSMain(PSInput input) : SV_TARGET
{
return input.color;
}