Unverified Commit 6c0205ce authored by Yuting Jiang's avatar Yuting Jiang Committed by GitHub
Browse files

Benchmarks: micro benchmarks - add source code for DirectXRenderPerf (#549)



**Description**
add source code for DirectXRenderPerf.

---------
Co-authored-by: default avataryukirora <yuting.jiang@microsoft.com>
parent 67f2aa72
//
// DeviceResources.cpp - A wrapper for the Direct3D 12 device and swapchain
//
#include "DeviceResources.h"
using namespace DirectX;
using namespace DX;
using Microsoft::WRL::ComPtr;
#ifdef __clang__
#pragma clang diagnostic ignored "-Wcovered-switch-default"
#pragma clang diagnostic ignored "-Wswitch-enum"
#endif
#pragma warning(disable : 4061)
namespace {
inline DXGI_FORMAT NoSRGB(DXGI_FORMAT fmt) noexcept {
switch (fmt) {
case DXGI_FORMAT_R8G8B8A8_UNORM_SRGB:
return DXGI_FORMAT_R8G8B8A8_UNORM;
case DXGI_FORMAT_B8G8R8A8_UNORM_SRGB:
return DXGI_FORMAT_B8G8R8A8_UNORM;
case DXGI_FORMAT_B8G8R8X8_UNORM_SRGB:
return DXGI_FORMAT_B8G8R8X8_UNORM;
default:
return fmt;
}
}
inline long ComputeIntersectionArea(long ax1, long ay1, long ax2, long ay2, long bx1, long by1, long bx2,
long by2) noexcept {
return std::max(0l, std::min(ax2, bx2) - std::max(ax1, bx1)) *
std::max(0l, std::min(ay2, by2) - std::max(ay1, by1));
}
} // namespace
// Constructor for DeviceResources.
DeviceResources::DeviceResources(DXGI_FORMAT backBufferFormat, DXGI_FORMAT depthBufferFormat, UINT backBufferCount,
D3D_FEATURE_LEVEL minFeatureLevel, unsigned int flags) noexcept(false)
: m_backBufferIndex(0), m_fenceValues{}, m_rtvDescriptorSize(0), m_screenViewport{}, m_scissorRect{},
m_backBufferFormat(backBufferFormat), m_depthBufferFormat(depthBufferFormat), m_backBufferCount(backBufferCount),
m_d3dMinFeatureLevel(minFeatureLevel), m_window(nullptr), m_d3dFeatureLevel(D3D_FEATURE_LEVEL_11_0),
m_dxgiFactoryFlags(0), m_outputSize{0, 0, 1, 1}, m_colorSpace(DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709),
m_options(flags), m_deviceNotify(nullptr) {
if (backBufferCount < 2 || backBufferCount > MAX_BACK_BUFFER_COUNT) {
throw std::out_of_range("invalid backBufferCount");
}
if (minFeatureLevel < D3D_FEATURE_LEVEL_11_0) {
throw std::out_of_range("minFeatureLevel too low");
}
}
// Destructor for DeviceResources.
DeviceResources::~DeviceResources() {
// Ensure that the GPU is no longer referencing resources that are about to be destroyed.
WaitForGpu();
}
// Configures the Direct3D device, and stores handles to it and the device context.
void DeviceResources::CreateDeviceResources() {
#if defined(_DEBUG)
// Enable the debug layer (requires the Graphics Tools "optional feature").
//
// NOTE: Enabling the debug layer after device creation will invalidate the active device.
{
ComPtr<ID3D12Debug> debugController;
if (SUCCEEDED(D3D12GetDebugInterface(IID_PPV_ARGS(debugController.GetAddressOf())))) {
debugController->EnableDebugLayer();
} else {
OutputDebugStringA("WARNING: Direct3D Debug Device is not available\n");
}
ComPtr<IDXGIInfoQueue> dxgiInfoQueue;
if (SUCCEEDED(DXGIGetDebugInterface1(0, IID_PPV_ARGS(dxgiInfoQueue.GetAddressOf())))) {
m_dxgiFactoryFlags = DXGI_CREATE_FACTORY_DEBUG;
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 = static_cast<UINT>(std::size(hide));
filter.DenyList.pIDList = hide;
dxgiInfoQueue->AddStorageFilterEntries(DXGI_DEBUG_DXGI, &filter);
}
}
#endif
ThrowIfFailed(CreateDXGIFactory2(m_dxgiFactoryFlags, IID_PPV_ARGS(m_dxgiFactory.ReleaseAndGetAddressOf())));
// Determines whether tearing support is available for fullscreen borderless windows.
if (m_options & c_AllowTearing) {
BOOL allowTearing = FALSE;
HRESULT hr =
m_dxgiFactory->CheckFeatureSupport(DXGI_FEATURE_PRESENT_ALLOW_TEARING, &allowTearing, sizeof(allowTearing));
if (FAILED(hr) || !allowTearing) {
m_options &= ~c_AllowTearing;
#ifdef _DEBUG
OutputDebugStringA("WARNING: Variable refresh rate displays not supported");
#endif
}
}
ComPtr<IDXGIAdapter1> adapter;
GetAdapter(adapter.GetAddressOf());
// Create the DX12 API device object.
HRESULT hr =
D3D12CreateDevice(adapter.Get(), m_d3dMinFeatureLevel, IID_PPV_ARGS(m_d3dDevice.ReleaseAndGetAddressOf()));
ThrowIfFailed(hr);
m_d3dDevice->SetName(L"DeviceResources");
#ifndef NDEBUG
// Configure debug device (if active).
ComPtr<ID3D12InfoQueue> d3dInfoQueue;
if (SUCCEEDED(m_d3dDevice.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 = static_cast<UINT>(std::size(hide));
filter.DenyList.pIDList = hide;
d3dInfoQueue->AddStorageFilterEntries(&filter);
}
#endif
// Determine maximum supported feature level for this device
static const D3D_FEATURE_LEVEL s_featureLevels[] = {
#if defined(NTDDI_WIN10_FE) || defined(USING_D3D12_AGILITY_SDK)
D3D_FEATURE_LEVEL_12_2,
#endif
D3D_FEATURE_LEVEL_12_1,
D3D_FEATURE_LEVEL_12_0,
D3D_FEATURE_LEVEL_11_1,
D3D_FEATURE_LEVEL_11_0,
};
D3D12_FEATURE_DATA_FEATURE_LEVELS featLevels = {static_cast<UINT>(std::size(s_featureLevels)), s_featureLevels,
D3D_FEATURE_LEVEL_11_0};
hr = m_d3dDevice->CheckFeatureSupport(D3D12_FEATURE_FEATURE_LEVELS, &featLevels, sizeof(featLevels));
if (SUCCEEDED(hr)) {
m_d3dFeatureLevel = featLevels.MaxSupportedFeatureLevel;
} else {
m_d3dFeatureLevel = m_d3dMinFeatureLevel;
}
// Create the command queue.
D3D12_COMMAND_QUEUE_DESC queueDesc = {};
queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
ThrowIfFailed(m_d3dDevice->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(m_commandQueue.ReleaseAndGetAddressOf())));
m_commandQueue->SetName(L"DeviceResources");
// Create descriptor heaps for render target views and depth stencil views.
D3D12_DESCRIPTOR_HEAP_DESC rtvDescriptorHeapDesc = {};
rtvDescriptorHeapDesc.NumDescriptors = m_backBufferCount;
rtvDescriptorHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
ThrowIfFailed(m_d3dDevice->CreateDescriptorHeap(&rtvDescriptorHeapDesc,
IID_PPV_ARGS(m_rtvDescriptorHeap.ReleaseAndGetAddressOf())));
m_rtvDescriptorHeap->SetName(L"DeviceResources");
m_rtvDescriptorSize = m_d3dDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
if (m_depthBufferFormat != DXGI_FORMAT_UNKNOWN) {
D3D12_DESCRIPTOR_HEAP_DESC dsvDescriptorHeapDesc = {};
dsvDescriptorHeapDesc.NumDescriptors = 1;
dsvDescriptorHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_DSV;
ThrowIfFailed(m_d3dDevice->CreateDescriptorHeap(&dsvDescriptorHeapDesc,
IID_PPV_ARGS(m_dsvDescriptorHeap.ReleaseAndGetAddressOf())));
m_dsvDescriptorHeap->SetName(L"DeviceResources");
}
// Create a command allocator for each back buffer that will be rendered to.
for (UINT n = 0; n < m_backBufferCount; n++) {
ThrowIfFailed(m_d3dDevice->CreateCommandAllocator(
D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(m_commandAllocators[n].ReleaseAndGetAddressOf())));
wchar_t name[25] = {};
swprintf_s(name, L"Render target %u", n);
m_commandAllocators[n]->SetName(name);
}
// Create a command list for recording graphics commands.
ThrowIfFailed(m_d3dDevice->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, m_commandAllocators[0].Get(),
nullptr, IID_PPV_ARGS(m_commandList.ReleaseAndGetAddressOf())));
ThrowIfFailed(m_commandList->Close());
m_commandList->SetName(L"DeviceResources");
// Create a fence for tracking GPU execution progress.
ThrowIfFailed(m_d3dDevice->CreateFence(m_fenceValues[m_backBufferIndex], D3D12_FENCE_FLAG_NONE,
IID_PPV_ARGS(m_fence.ReleaseAndGetAddressOf())));
m_fenceValues[m_backBufferIndex]++;
m_fence->SetName(L"DeviceResources");
m_fenceEvent.Attach(CreateEventEx(nullptr, nullptr, 0, EVENT_MODIFY_STATE | SYNCHRONIZE));
if (!m_fenceEvent.IsValid()) {
throw std::system_error(std::error_code(static_cast<int>(GetLastError()), std::system_category()),
"CreateEventEx");
}
}
// These resources need to be recreated every time the window size is changed.
void DeviceResources::CreateWindowSizeDependentResources() {
if (!m_window) {
throw std::logic_error("Call SetWindow with a valid Win32 window handle");
}
// Wait until all previous GPU work is complete.
WaitForGpu();
// Release resources that are tied to the swap chain and update fence values.
for (UINT n = 0; n < m_backBufferCount; n++) {
m_renderTargets[n].Reset();
m_fenceValues[n] = m_fenceValues[m_backBufferIndex];
}
// Determine the render target size in pixels.
const UINT backBufferWidth = std::max<UINT>(static_cast<UINT>(m_outputSize.right - m_outputSize.left), 1u);
const UINT backBufferHeight = std::max<UINT>(static_cast<UINT>(m_outputSize.bottom - m_outputSize.top), 1u);
const DXGI_FORMAT backBufferFormat = NoSRGB(m_backBufferFormat);
// If the swap chain already exists, resize it, otherwise create one.
if (m_swapChain) {
// If the swap chain already exists, resize it.
HRESULT hr = m_swapChain->ResizeBuffers(m_backBufferCount, backBufferWidth, backBufferHeight, backBufferFormat,
(m_options & c_AllowTearing) ? DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING : 0u);
if (hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET) {
#ifdef _DEBUG
char buff[64] = {};
sprintf_s(buff, "Device Lost on ResizeBuffers: Reason code 0x%08X\n",
static_cast<unsigned int>(
(hr == DXGI_ERROR_DEVICE_REMOVED) ? m_d3dDevice->GetDeviceRemovedReason() : hr));
OutputDebugStringA(buff);
#endif
// If the device was removed for any reason, a new device and swap chain will need to be created.
HandleDeviceLost();
// Everything is set up now. Do not continue execution of this method. HandleDeviceLost will reenter this
// method and correctly set up the new device.
return;
} else {
ThrowIfFailed(hr);
}
} else {
// Create a descriptor for the swap chain.
DXGI_SWAP_CHAIN_DESC1 swapChainDesc = {};
swapChainDesc.Width = backBufferWidth;
swapChainDesc.Height = backBufferHeight;
swapChainDesc.Format = backBufferFormat;
swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
swapChainDesc.BufferCount = m_backBufferCount;
swapChainDesc.SampleDesc.Count = 1;
swapChainDesc.SampleDesc.Quality = 0;
swapChainDesc.Scaling = DXGI_SCALING_STRETCH;
swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
swapChainDesc.AlphaMode = DXGI_ALPHA_MODE_IGNORE;
swapChainDesc.Flags = (m_options & c_AllowTearing) ? DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING : 0u;
DXGI_SWAP_CHAIN_FULLSCREEN_DESC fsSwapChainDesc = {};
fsSwapChainDesc.Windowed = TRUE;
// Create a swap chain for the window.
ComPtr<IDXGISwapChain1> swapChain;
ThrowIfFailed(m_dxgiFactory->CreateSwapChainForHwnd(m_commandQueue.Get(), m_window, &swapChainDesc,
&fsSwapChainDesc, nullptr, swapChain.GetAddressOf()));
ThrowIfFailed(swapChain.As(&m_swapChain));
// This class does not support exclusive full-screen mode and prevents DXGI from responding to the ALT+ENTER
// shortcut
ThrowIfFailed(m_dxgiFactory->MakeWindowAssociation(m_window, DXGI_MWA_NO_ALT_ENTER));
}
// Handle color space settings for HDR
UpdateColorSpace();
// Obtain the back buffers for this window which will be the final render targets
// and create render target views for each of them.
for (UINT n = 0; n < m_backBufferCount; n++) {
ThrowIfFailed(m_swapChain->GetBuffer(n, IID_PPV_ARGS(m_renderTargets[n].GetAddressOf())));
wchar_t name[25] = {};
swprintf_s(name, L"Render target %u", n);
m_renderTargets[n]->SetName(name);
D3D12_RENDER_TARGET_VIEW_DESC rtvDesc = {};
rtvDesc.Format = m_backBufferFormat;
rtvDesc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2D;
const CD3DX12_CPU_DESCRIPTOR_HANDLE rtvDescriptor(m_rtvDescriptorHeap->GetCPUDescriptorHandleForHeapStart(),
static_cast<INT>(n), m_rtvDescriptorSize);
m_d3dDevice->CreateRenderTargetView(m_renderTargets[n].Get(), &rtvDesc, rtvDescriptor);
}
// Reset the index to the current back buffer.
m_backBufferIndex = m_swapChain->GetCurrentBackBufferIndex();
if (m_depthBufferFormat != DXGI_FORMAT_UNKNOWN) {
// Allocate a 2-D surface as the depth/stencil buffer and create a depth/stencil view
// on this surface.
const CD3DX12_HEAP_PROPERTIES depthHeapProperties(D3D12_HEAP_TYPE_DEFAULT);
D3D12_RESOURCE_DESC depthStencilDesc =
CD3DX12_RESOURCE_DESC::Tex2D(m_depthBufferFormat, backBufferWidth, backBufferHeight,
1, // This depth stencil view has only one texture.
1 // Use a single mipmap level.
);
depthStencilDesc.Flags |= D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL;
const CD3DX12_CLEAR_VALUE depthOptimizedClearValue(m_depthBufferFormat,
(m_options & c_ReverseDepth) ? 0.0f : 1.0f, 0u);
ThrowIfFailed(m_d3dDevice->CreateCommittedResource(
&depthHeapProperties, D3D12_HEAP_FLAG_NONE, &depthStencilDesc, D3D12_RESOURCE_STATE_DEPTH_WRITE,
&depthOptimizedClearValue, IID_PPV_ARGS(m_depthStencil.ReleaseAndGetAddressOf())));
m_depthStencil->SetName(L"Depth stencil");
D3D12_DEPTH_STENCIL_VIEW_DESC dsvDesc = {};
dsvDesc.Format = m_depthBufferFormat;
dsvDesc.ViewDimension = D3D12_DSV_DIMENSION_TEXTURE2D;
m_d3dDevice->CreateDepthStencilView(m_depthStencil.Get(), &dsvDesc,
m_dsvDescriptorHeap->GetCPUDescriptorHandleForHeapStart());
}
// Set the 3D rendering viewport and scissor rectangle to target the entire window.
m_screenViewport.TopLeftX = m_screenViewport.TopLeftY = 0.f;
m_screenViewport.Width = static_cast<float>(backBufferWidth);
m_screenViewport.Height = static_cast<float>(backBufferHeight);
m_screenViewport.MinDepth = D3D12_MIN_DEPTH;
m_screenViewport.MaxDepth = D3D12_MAX_DEPTH;
m_scissorRect.left = m_scissorRect.top = 0;
m_scissorRect.right = static_cast<LONG>(backBufferWidth);
m_scissorRect.bottom = static_cast<LONG>(backBufferHeight);
}
// This method is called when the Win32 window is created (or re-created).
void DeviceResources::SetWindow(HWND window, int width, int height) noexcept {
m_window = window;
m_outputSize.left = m_outputSize.top = 0;
m_outputSize.right = static_cast<long>(width);
m_outputSize.bottom = static_cast<long>(height);
}
// This method is called when the Win32 window changes size.
bool DeviceResources::WindowSizeChanged(int width, int height) {
if (!m_window)
return false;
RECT newRc;
newRc.left = newRc.top = 0;
newRc.right = static_cast<long>(width);
newRc.bottom = static_cast<long>(height);
if (newRc.right == m_outputSize.right && newRc.bottom == m_outputSize.bottom) {
// Handle color space settings for HDR
UpdateColorSpace();
return false;
}
m_outputSize = newRc;
CreateWindowSizeDependentResources();
return true;
}
// Recreate all device resources and set them back to the current state.
void DeviceResources::HandleDeviceLost() {
if (m_deviceNotify) {
m_deviceNotify->OnDeviceLost();
}
for (UINT n = 0; n < m_backBufferCount; n++) {
m_commandAllocators[n].Reset();
m_renderTargets[n].Reset();
}
m_depthStencil.Reset();
m_commandQueue.Reset();
m_commandList.Reset();
m_fence.Reset();
m_rtvDescriptorHeap.Reset();
m_dsvDescriptorHeap.Reset();
m_swapChain.Reset();
m_d3dDevice.Reset();
m_dxgiFactory.Reset();
#ifdef _DEBUG
{
ComPtr<IDXGIDebug1> dxgiDebug;
if (SUCCEEDED(DXGIGetDebugInterface1(0, IID_PPV_ARGS(&dxgiDebug)))) {
dxgiDebug->ReportLiveObjects(DXGI_DEBUG_ALL,
DXGI_DEBUG_RLO_FLAGS(DXGI_DEBUG_RLO_SUMMARY | DXGI_DEBUG_RLO_IGNORE_INTERNAL));
}
}
#endif
CreateDeviceResources();
CreateWindowSizeDependentResources();
if (m_deviceNotify) {
m_deviceNotify->OnDeviceRestored();
}
}
// Prepare the command list and render target for rendering.
void DeviceResources::Prepare(D3D12_RESOURCE_STATES beforeState, D3D12_RESOURCE_STATES afterState) {
// Reset command list and allocator.
ThrowIfFailed(m_commandAllocators[m_backBufferIndex]->Reset());
ThrowIfFailed(m_commandList->Reset(m_commandAllocators[m_backBufferIndex].Get(), nullptr));
if (beforeState != afterState) {
// Transition the render target into the correct state to allow for drawing into it.
const D3D12_RESOURCE_BARRIER barrier =
CD3DX12_RESOURCE_BARRIER::Transition(m_renderTargets[m_backBufferIndex].Get(), beforeState, afterState);
m_commandList->ResourceBarrier(1, &barrier);
}
}
// Present the contents of the swap chain to the screen.
void DeviceResources::Present(D3D12_RESOURCE_STATES beforeState) {
if (beforeState != D3D12_RESOURCE_STATE_PRESENT) {
// Transition the render target to the state that allows it to be presented to the display.
const D3D12_RESOURCE_BARRIER barrier = CD3DX12_RESOURCE_BARRIER::Transition(
m_renderTargets[m_backBufferIndex].Get(), beforeState, D3D12_RESOURCE_STATE_PRESENT);
m_commandList->ResourceBarrier(1, &barrier);
}
// Send the command list off to the GPU for processing.
ThrowIfFailed(m_commandList->Close());
m_commandQueue->ExecuteCommandLists(1, CommandListCast(m_commandList.GetAddressOf()));
HRESULT hr;
if (m_options & c_AllowTearing) {
// Recommended to always use tearing if supported when using a sync interval of 0.
// Note this will fail if in true 'fullscreen' mode.
hr = m_swapChain->Present(0, DXGI_PRESENT_ALLOW_TEARING);
} else {
// The first argument instructs DXGI to block until VSync, putting the application
// to sleep until the next VSync. This ensures we don't waste any cycles rendering
// frames that will never be displayed to the screen.
hr = m_swapChain->Present(1, 0);
}
// If the device was reset we must completely reinitialize the renderer.
if (hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET) {
#ifdef _DEBUG
char buff[64] = {};
sprintf_s(
buff, "Device Lost on Present: Reason code 0x%08X\n",
static_cast<unsigned int>((hr == DXGI_ERROR_DEVICE_REMOVED) ? m_d3dDevice->GetDeviceRemovedReason() : hr));
OutputDebugStringA(buff);
#endif
HandleDeviceLost();
} else {
ThrowIfFailed(hr);
MoveToNextFrame();
if (!m_dxgiFactory->IsCurrent()) {
UpdateColorSpace();
}
}
}
// Wait for pending GPU work to complete.
void DeviceResources::WaitForGpu() noexcept {
if (m_commandQueue && m_fence && m_fenceEvent.IsValid()) {
// Schedule a Signal command in the GPU queue.
const UINT64 fenceValue = m_fenceValues[m_backBufferIndex];
if (SUCCEEDED(m_commandQueue->Signal(m_fence.Get(), fenceValue))) {
// Wait until the Signal has been processed.
if (SUCCEEDED(m_fence->SetEventOnCompletion(fenceValue, m_fenceEvent.Get()))) {
std::ignore = WaitForSingleObjectEx(m_fenceEvent.Get(), INFINITE, FALSE);
// Increment the fence value for the current frame.
m_fenceValues[m_backBufferIndex]++;
}
}
}
}
// Prepare to render the next frame.
void DeviceResources::MoveToNextFrame() {
// Schedule a Signal command in the queue.
const UINT64 currentFenceValue = m_fenceValues[m_backBufferIndex];
ThrowIfFailed(m_commandQueue->Signal(m_fence.Get(), currentFenceValue));
// Update the back buffer index.
m_backBufferIndex = m_swapChain->GetCurrentBackBufferIndex();
// If the next frame is not ready to be rendered yet, wait until it is ready.
if (m_fence->GetCompletedValue() < m_fenceValues[m_backBufferIndex]) {
ThrowIfFailed(m_fence->SetEventOnCompletion(m_fenceValues[m_backBufferIndex], m_fenceEvent.Get()));
std::ignore = WaitForSingleObjectEx(m_fenceEvent.Get(), INFINITE, FALSE);
}
// Set the fence value for the next frame.
m_fenceValues[m_backBufferIndex] = currentFenceValue + 1;
}
// This method acquires the first available hardware adapter that supports Direct3D 12.
// If no such adapter can be found, try WARP. Otherwise throw an exception.
void DeviceResources::GetAdapter(IDXGIAdapter1 **ppAdapter) {
*ppAdapter = nullptr;
ComPtr<IDXGIAdapter1> adapter;
for (UINT adapterIndex = 0; SUCCEEDED(m_dxgiFactory->EnumAdapterByGpuPreference(
adapterIndex, DXGI_GPU_PREFERENCE_HIGH_PERFORMANCE, IID_PPV_ARGS(adapter.ReleaseAndGetAddressOf())));
adapterIndex++) {
DXGI_ADAPTER_DESC1 desc;
ThrowIfFailed(adapter->GetDesc1(&desc));
if (desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE) {
// Don't select the Basic Render Driver adapter.
continue;
}
// Check to see if the adapter supports Direct3D 12, but don't create the actual device yet.
if (SUCCEEDED(D3D12CreateDevice(adapter.Get(), m_d3dMinFeatureLevel, __uuidof(ID3D12Device), nullptr))) {
#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
break;
}
}
#if !defined(NDEBUG)
if (!adapter) {
// Try WARP12 instead
if (FAILED(m_dxgiFactory->EnumWarpAdapter(IID_PPV_ARGS(adapter.ReleaseAndGetAddressOf())))) {
throw std::runtime_error("WARP12 not available. Enable the 'Graphics Tools' optional feature");
}
OutputDebugStringA("Direct3D Adapter - WARP12\n");
}
#endif
if (!adapter) {
throw std::runtime_error("No Direct3D 12 device found");
}
*ppAdapter = adapter.Detach();
}
// Sets the color space for the swap chain in order to handle HDR output.
void DeviceResources::UpdateColorSpace() {
if (!m_dxgiFactory)
return;
if (!m_dxgiFactory->IsCurrent()) {
// Output information is cached on the DXGI Factory. If it is stale we need to create a new factory.
ThrowIfFailed(CreateDXGIFactory2(m_dxgiFactoryFlags, IID_PPV_ARGS(m_dxgiFactory.ReleaseAndGetAddressOf())));
}
DXGI_COLOR_SPACE_TYPE colorSpace = DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709;
bool isDisplayHDR10 = false;
if (m_swapChain) {
// To detect HDR support, we will need to check the color space in the primary
// DXGI output associated with the app at this point in time
// (using window/display intersection).
// Get the retangle bounds of the app window.
RECT windowBounds;
if (!GetWindowRect(m_window, &windowBounds))
throw std::system_error(std::error_code(static_cast<int>(GetLastError()), std::system_category()),
"GetWindowRect");
const long ax1 = windowBounds.left;
const long ay1 = windowBounds.top;
const long ax2 = windowBounds.right;
const long ay2 = windowBounds.bottom;
ComPtr<IDXGIOutput> bestOutput;
long bestIntersectArea = -1;
ComPtr<IDXGIAdapter> adapter;
for (UINT adapterIndex = 0;
SUCCEEDED(m_dxgiFactory->EnumAdapters(adapterIndex, adapter.ReleaseAndGetAddressOf())); ++adapterIndex) {
ComPtr<IDXGIOutput> output;
for (UINT outputIndex = 0; SUCCEEDED(adapter->EnumOutputs(outputIndex, output.ReleaseAndGetAddressOf()));
++outputIndex) {
// Get the rectangle bounds of current output.
DXGI_OUTPUT_DESC desc;
ThrowIfFailed(output->GetDesc(&desc));
const auto &r = desc.DesktopCoordinates;
// Compute the intersection
const long intersectArea =
ComputeIntersectionArea(ax1, ay1, ax2, ay2, r.left, r.top, r.right, r.bottom);
if (intersectArea > bestIntersectArea) {
bestOutput.Swap(output);
bestIntersectArea = intersectArea;
}
}
}
if (bestOutput) {
ComPtr<IDXGIOutput6> output6;
if (SUCCEEDED(bestOutput.As(&output6))) {
DXGI_OUTPUT_DESC1 desc;
ThrowIfFailed(output6->GetDesc1(&desc));
if (desc.ColorSpace == DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020) {
// Display output is HDR10.
isDisplayHDR10 = true;
}
}
}
}
if ((m_options & c_EnableHDR) && isDisplayHDR10) {
switch (m_backBufferFormat) {
case DXGI_FORMAT_R10G10B10A2_UNORM:
// The application creates the HDR10 signal.
colorSpace = DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020;
break;
case DXGI_FORMAT_R16G16B16A16_FLOAT:
// The system creates the HDR10 signal; application uses linear values.
colorSpace = DXGI_COLOR_SPACE_RGB_FULL_G10_NONE_P709;
break;
default:
break;
}
}
m_colorSpace = colorSpace;
UINT colorSpaceSupport = 0;
if (m_swapChain && SUCCEEDED(m_swapChain->CheckColorSpaceSupport(colorSpace, &colorSpaceSupport)) &&
(colorSpaceSupport & DXGI_SWAP_CHAIN_COLOR_SPACE_SUPPORT_FLAG_PRESENT)) {
ThrowIfFailed(m_swapChain->SetColorSpace1(colorSpace));
}
}
//
// DeviceResources.h - A wrapper for the Direct3D 12 device and swapchain
//
#pragma once
#include <system_error>
#include <tuple>
#include "pch.h"
namespace DX {
// Provides an interface for an application that owns DeviceResources to be notified of the device being lost or
// created.
interface IDeviceNotify {
virtual void OnDeviceLost() = 0;
virtual void OnDeviceRestored() = 0;
protected:
~IDeviceNotify() = default;
};
// Controls all the DirectX device resources.
class DeviceResources {
public:
static constexpr unsigned int c_AllowTearing = 0x1;
static constexpr unsigned int c_EnableHDR = 0x2;
static constexpr unsigned int c_ReverseDepth = 0x4;
DeviceResources(DXGI_FORMAT backBufferFormat = DXGI_FORMAT_B8G8R8A8_UNORM,
DXGI_FORMAT depthBufferFormat = DXGI_FORMAT_D32_FLOAT, UINT backBufferCount = 2,
D3D_FEATURE_LEVEL minFeatureLevel = D3D_FEATURE_LEVEL_11_0, unsigned int flags = 0) noexcept(false);
~DeviceResources();
DeviceResources(DeviceResources &&) = default;
DeviceResources &operator=(DeviceResources &&) = default;
DeviceResources(DeviceResources const &) = delete;
DeviceResources &operator=(DeviceResources const &) = delete;
void CreateDeviceResources();
void CreateWindowSizeDependentResources();
void SetWindow(HWND window, int width, int height) noexcept;
bool WindowSizeChanged(int width, int height);
void HandleDeviceLost();
void RegisterDeviceNotify(IDeviceNotify *deviceNotify) noexcept { m_deviceNotify = deviceNotify; }
void Prepare(D3D12_RESOURCE_STATES beforeState = D3D12_RESOURCE_STATE_PRESENT,
D3D12_RESOURCE_STATES afterState = D3D12_RESOURCE_STATE_RENDER_TARGET);
void Present(D3D12_RESOURCE_STATES beforeState = D3D12_RESOURCE_STATE_RENDER_TARGET);
void WaitForGpu() noexcept;
void UpdateColorSpace();
// Device Accessors.
RECT GetOutputSize() const noexcept { return m_outputSize; }
// Direct3D Accessors.
auto GetD3DDevice() const noexcept { return m_d3dDevice.Get(); }
auto GetSwapChain() const noexcept { return m_swapChain.Get(); }
auto GetDXGIFactory() const noexcept { return m_dxgiFactory.Get(); }
HWND GetWindow() const noexcept { return m_window; }
D3D_FEATURE_LEVEL GetDeviceFeatureLevel() const noexcept { return m_d3dFeatureLevel; }
ID3D12Resource *GetRenderTarget() const noexcept { return m_renderTargets[m_backBufferIndex].Get(); }
ID3D12Resource *GetDepthStencil() const noexcept { return m_depthStencil.Get(); }
ID3D12CommandQueue *GetCommandQueue() const noexcept { return m_commandQueue.Get(); }
ID3D12CommandAllocator *GetCommandAllocator() const noexcept {
return m_commandAllocators[m_backBufferIndex].Get();
}
auto GetCommandList() const noexcept { return m_commandList.Get(); }
DXGI_FORMAT GetBackBufferFormat() const noexcept { return m_backBufferFormat; }
DXGI_FORMAT GetDepthBufferFormat() const noexcept { return m_depthBufferFormat; }
D3D12_VIEWPORT GetScreenViewport() const noexcept { return m_screenViewport; }
D3D12_RECT GetScissorRect() const noexcept { return m_scissorRect; }
UINT GetCurrentFrameIndex() const noexcept { return m_backBufferIndex; }
UINT GetBackBufferCount() const noexcept { return m_backBufferCount; }
DXGI_COLOR_SPACE_TYPE GetColorSpace() const noexcept { return m_colorSpace; }
unsigned int GetDeviceOptions() const noexcept { return m_options; }
CD3DX12_CPU_DESCRIPTOR_HANDLE GetRenderTargetView() const noexcept {
return CD3DX12_CPU_DESCRIPTOR_HANDLE(m_rtvDescriptorHeap->GetCPUDescriptorHandleForHeapStart(),
static_cast<INT>(m_backBufferIndex), m_rtvDescriptorSize);
}
CD3DX12_CPU_DESCRIPTOR_HANDLE GetDepthStencilView() const noexcept {
return CD3DX12_CPU_DESCRIPTOR_HANDLE(m_dsvDescriptorHeap->GetCPUDescriptorHandleForHeapStart());
}
void MoveToNextFrame();
void GetAdapter(IDXGIAdapter1 **ppAdapter);
static constexpr size_t MAX_BACK_BUFFER_COUNT = 3;
UINT m_backBufferIndex;
// Direct3D objects.
Microsoft::WRL::ComPtr<ID3D12Device> m_d3dDevice;
Microsoft::WRL::ComPtr<ID3D12GraphicsCommandList> m_commandList;
Microsoft::WRL::ComPtr<ID3D12CommandQueue> m_commandQueue;
Microsoft::WRL::ComPtr<ID3D12CommandAllocator> m_commandAllocators[MAX_BACK_BUFFER_COUNT];
// Swap chain objects.
Microsoft::WRL::ComPtr<IDXGIFactory6> m_dxgiFactory;
Microsoft::WRL::ComPtr<IDXGISwapChain3> m_swapChain;
Microsoft::WRL::ComPtr<ID3D12Resource> m_renderTargets[MAX_BACK_BUFFER_COUNT];
Microsoft::WRL::ComPtr<ID3D12Resource> m_depthStencil;
// Presentation fence objects.
Microsoft::WRL::ComPtr<ID3D12Fence> m_fence;
UINT64 m_fenceValues[MAX_BACK_BUFFER_COUNT];
Microsoft::WRL::Wrappers::Event m_fenceEvent;
// Direct3D rendering objects.
Microsoft::WRL::ComPtr<ID3D12DescriptorHeap> m_rtvDescriptorHeap;
Microsoft::WRL::ComPtr<ID3D12DescriptorHeap> m_dsvDescriptorHeap;
UINT m_rtvDescriptorSize;
D3D12_VIEWPORT m_screenViewport;
D3D12_RECT m_scissorRect;
// Direct3D properties.
DXGI_FORMAT m_backBufferFormat;
DXGI_FORMAT m_depthBufferFormat;
UINT m_backBufferCount;
D3D_FEATURE_LEVEL m_d3dMinFeatureLevel;
// Cached device properties.
HWND m_window;
D3D_FEATURE_LEVEL m_d3dFeatureLevel;
DWORD m_dxgiFactoryFlags;
RECT m_outputSize;
// HDR Support
DXGI_COLOR_SPACE_TYPE m_colorSpace;
// DeviceResources options (see flags above)
unsigned int m_options;
// The IDeviceNotify can be held directly as it owns the DeviceResources.
IDeviceNotify *m_deviceNotify;
};
} // namespace DX
\ No newline at end of file
//--------------------------------------------------------------------------------------
// pch.h
//
// Header for standard system include files.
//
// Advanced Technology Group (ATG)
// Copyright (C) Microsoft Corporation. All rights reserved.
//--------------------------------------------------------------------------------------
#pragma once
#include <WinSDKVer.h>
#define _WIN32_WINNT 0x0A00
#include <SDKDDKVer.h>
// Use the C++ standard templated min/max
#define NOMINMAX
// DirectX apps don't need GDI
#define NODRAWTEXT
#define NOGDI
#define NOBITMAP
// Include <mcx.h> if you need this
#define NOMCX
// Include <winsvc.h> if you need this
#define NOSERVICE
// WinHelp is deprecated
#define NOHELP
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <wrl/client.h>
#include <wrl/event.h>
#include <d3d12.h>
#if defined(NTDDI_WIN10_RS2)
#include <dxgi1_6.h>
#else
#include <dxgi1_5.h>
#endif
#include <DirectXColors.h>
#include <DirectXMath.h>
#include "d3dx12.h"
#include <algorithm>
#include <exception>
#include <memory>
#include <stdexcept>
#ifdef _DEBUG
#include <dxgidebug.h>
#endif
#include <stdio.h>
// To use graphics and CPU markup events with the latest version of PIX, change this to include <pix3.h>
// then add the NuGet package WinPixEventRuntime to the project.
#include <pix.h>
#include <D3Dcompiler.h>
#include <DirectXMath.h>
#pragma comment(lib, "D3Dcompiler.lib")
#pragma comment(lib, "d3d12.lib")
#pragma comment(lib, "dxgi.lib")
#pragma comment(lib, "dxguid.lib")
namespace DX {
// Helper class for COM exceptions
class com_exception : public std::exception {
public:
com_exception(HRESULT hr) noexcept : result(hr) {}
const char *what() const override {
static char s_str[64] = {};
sprintf_s(s_str, "Failure with HRESULT of %08X", static_cast<unsigned int>(result));
return s_str;
}
private:
HRESULT result;
};
// Helper utility converts D3D API failures into exceptions.
inline void ThrowIfFailed(HRESULT hr) {
if (FAILED(hr)) {
throw com_exception(hr);
}
}
} // namespace DX
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment