diff options
Diffstat (limited to 'Projects/Asteroids/src/asteroids_d3d12.cpp')
| -rw-r--r-- | Projects/Asteroids/src/asteroids_d3d12.cpp | 938 |
1 files changed, 938 insertions, 0 deletions
diff --git a/Projects/Asteroids/src/asteroids_d3d12.cpp b/Projects/Asteroids/src/asteroids_d3d12.cpp new file mode 100644 index 0000000..1d6fcdd --- /dev/null +++ b/Projects/Asteroids/src/asteroids_d3d12.cpp @@ -0,0 +1,938 @@ +// Copyright 2014 Intel Corporation All Rights Reserved +// +// Intel makes no representations about the suitability of this software for any purpose. +// THIS SOFTWARE IS PROVIDED ""AS IS."" INTEL SPECIFICALLY DISCLAIMS ALL WARRANTIES, +// EXPRESS OR IMPLIED, AND ALL LIABILITY, INCLUDING CONSEQUENTIAL AND OTHER INDIRECT DAMAGES, +// FOR THE USE OF THIS SOFTWARE, INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PROPRIETARY +// RIGHTS, AND INCLUDING THE WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +// Intel does not assume any responsibility for any errors which may appear in this software +// nor any responsibility to update it. + +#include <d3dcompiler.h> +#include <directxmath.h> +#include <d3dx12.h> +#include <math.h> + +#include <iostream> +#include <algorithm> +#include <vector> +#include <limits> +#include <random> +#include <sstream> +#include <ppl.h> + +#include "asteroids_d3d12.h" +#include "util.h" +#include "mesh.h" +#include "noise.h" +#include "texture.h" + +#include "asteroid_vs.h" +#include "asteroid_ps.h" + +#include "skybox_vs.h" +#include "skybox_ps.h" + +#include "sprite_vs.h" +#include "sprite_ps.h" +#include "font_ps.h" + +using namespace DirectX; + +namespace AsteroidsD3D12 { + +enum RootParameters +{ + RP_DRAW_CBV, + RP_TEX_SRV, + RP_SMP, +}; + +Asteroids::Asteroids(AsteroidsSimulation* asteroids, GUI *gui, UINT minCmdLsts, IDXGIAdapter* adapter) + : mAsteroids(asteroids) + , mGUI(gui) + , mFenceEventHandle(CreateEvent(NULL, FALSE, FALSE, NULL)) + , mD3D12Sprite( new GUISprite(5, 10, 140, 50, "directx12.dds") ) +{ + QueryPerformanceFrequency((LARGE_INTEGER*)&mPerfCounterFreq); + + memset(&mViewPort, 0, sizeof(mViewPort)); + memset(&mScissorRect, 0, sizeof(mScissorRect)); + memset(mAsteroidTextures, 0, sizeof(mAsteroidTextures)); + memset(mIndexOffsets, 0xff, sizeof(mIndexOffsets)); + +#if defined(_DEBUG) + // Enable the D3D12 debug layer. + { + ID3D12Debug* debugController = nullptr; + if (SUCCEEDED(D3D12GetDebugInterface(IID_PPV_ARGS(&debugController)))) + { + debugController->EnableDebugLayer(); + debugController->Release(); + } + } +#endif + + // Create device + { + ThrowIfFailed(D3D12CreateDevice( + adapter, + D3D_FEATURE_LEVEL_11_0, + IID_PPV_ARGS(&mDevice))); + + D3D12_COMMAND_QUEUE_DESC QDesc; + QDesc.NodeMask = 1; + QDesc.Priority = 0; + QDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT; + QDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE; + ThrowIfFailed(mDevice->CreateCommandQueue(&QDesc, IID_PPV_ARGS(&mCommandQueue))); + + ThrowIfFailed(mDevice->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&mFence))); + } + + // General use descriptor heaps + mRTVDescs = new RTVDescriptorList(mDevice, NUM_SWAP_CHAIN_BUFFERS); + mDSVDescs = new DSVDescriptorList(mDevice, 1); + mSMPDescs = new SMPDescriptorList(mDevice, 1); + mSRVDescs = new SRVDescriptorList(mDevice, NUM_UNIQUE_TEXTURES); + + // Filled in in Resize - just take slots for them here + mDepthStencilView = mDSVDescs->Append(); + mRTVFormat = DXGI_FORMAT_B8G8R8A8_UNORM_SRGB; + mDSVFormat = DXGI_FORMAT_D32_FLOAT; + + CreatePSOs(); + CreateMeshes(); + + // Create textures + { + // TODO: Query simulation for this data? Defines good enough for now... + D3D12_RESOURCE_DESC textureDesc = + CD3DX12_RESOURCE_DESC::Tex2D(DXGI_FORMAT_R8G8B8A8_UNORM_SRGB, TEXTURE_DIM, TEXTURE_DIM, 3, 0); + + for (UINT i = 0; i < NUM_UNIQUE_TEXTURES; ++i) { + ThrowIfFailed(mDevice->CreateCommittedResource( + &CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT), + D3D12_HEAP_FLAG_NONE, + &textureDesc, + D3D12_RESOURCE_STATE_COMMON, + nullptr, // Clear value + IID_PPV_ARGS(&mAsteroidTextures[i]) + )); + textureDesc = mAsteroidTextures[i]->GetDesc(); + + InitializeTexture2D(mDevice, mCommandQueue, mAsteroidTextures[i], &textureDesc, 4, mAsteroids->TextureData(i)); + + // Append a descriptor to the heap + mSRVDescs->AppendSRV(mAsteroidTextures[i]); + } + + ThrowIfFailed(CreateTexture2DFromDDS_XXXX8( + mDevice, mCommandQueue, &mSkybox, "starbox_1024.dds", DXGI_FORMAT_R8G8B8A8_UNORM_SRGB)); + } + + CreateGUIResources(); + + // Fill in general heaps + { // Samplers + D3D12_SAMPLER_DESC sampler = {}; + sampler.Filter = D3D12_FILTER_ANISOTROPIC; + sampler.AddressU = D3D12_TEXTURE_ADDRESS_MODE_CLAMP; + sampler.AddressV = D3D12_TEXTURE_ADDRESS_MODE_CLAMP; + sampler.AddressW = D3D12_TEXTURE_ADDRESS_MODE_CLAMP; + sampler.MaxAnisotropy = TEXTURE_ANISO; + sampler.MinLOD = -std::numeric_limits<float>::max(); + sampler.MaxLOD = std::numeric_limits<float>::max(); + mSampler = mSMPDescs->Append(&sampler); + } + + // Swap chain resources + for (UINT s = 0; s < NUM_SWAP_CHAIN_BUFFERS; s++) { + // Filled in in resize, just make a slot here + mSwapChainBuffer[s].mRenderTargetView = mRTVDescs->Append(); + } + + // Per-frame resources + for (UINT f = 0; f < NUM_FRAMES_TO_BUFFER; f++) { + auto frame = &mFrame[f]; + ThrowIfFailed(mDevice->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&frame->mCmdAlloc))); + + frame->mDynamicUpload = new UploadHeapT<DynamicUploadHeap>(mDevice); + auto dynamicUploadWO = frame->mDynamicUpload->DataWO(); + auto dynamicUploadGPUVA = frame->mDynamicUpload->Heap()->GetGPUVirtualAddress(); + + frame->mDrawConstantBuffersGPUVA = dynamicUploadGPUVA + offsetof(DynamicUploadHeap, mDrawConstantBuffers); + + // Set any static asteroid data now + auto staticData = mAsteroids->StaticData(); + for (int j = 0; j < NUM_ASTEROIDS; ++j) { + auto constants = &dynamicUploadWO->mDrawConstantBuffers[j]; + constants->mSurfaceColor = staticData[j].surfaceColor; + constants->mDeepColor = staticData[j].deepColor; + constants->mTextureIndex = staticData[j].textureIndex; + + auto indirectDraw = &dynamicUploadWO->mIndirectArgs[j]; + indirectDraw->mConstantBuffer = frame->mDrawConstantBuffersGPUVA + sizeof(DrawConstantBuffer) * j; + indirectDraw->mDrawIndexed.InstanceCount = 1; + indirectDraw->mDrawIndexed.StartInstanceLocation = 0; + indirectDraw->mDrawIndexed.BaseVertexLocation = staticData[j].vertexStart; + } + + // Dynamic sprite vertices + { + frame->mSpriteVertexBufferView.BufferLocation = dynamicUploadGPUVA + offsetof(DynamicUploadHeap, mSpriteVertices); + frame->mSpriteVertexBufferView.StrideInBytes = sizeof(SpriteVertex); + frame->mSpriteVertexBufferView.SizeInBytes = sizeof(SpriteVertex) * MAX_SPRITE_VERTICES_PER_FRAME; + } + + // General frame SRV descriptor heap + { + // We allocate a pile of extra space for dynamic descriptors (GUI, etc) + frame->mSRVDescs = new SRVDescriptorList(mDevice, 100); + + // Skybox constants + frame->mSkyboxConstants = dynamicUploadGPUVA + offsetof(DynamicUploadHeap, mSkyboxConstants); + + // Skybox texture + { + auto textureDesc = mSkybox->GetDesc(); + + D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc = {}; + srvDesc.Format = textureDesc.Format; + srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING; + srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURECUBE; + srvDesc.TextureCube.MipLevels = 1; + srvDesc.TextureCube.MostDetailedMip = 0; + srvDesc.TextureCube.ResourceMinLODClamp = 0.0f; + + frame->mSkyboxTexture = frame->mSRVDescs->AppendSRV(mSkybox, &srvDesc); + } + + // The rest of the heap we'll use for dynamically copy descriptors into, etc. + frame->mSRVDescsDynamicStart = frame->mSRVDescs->Size(); + } + } + + // Command Lists + ThrowIfFailed(mDevice->CreateCommandList(1, D3D12_COMMAND_LIST_TYPE_DIRECT, mFrame[0].mCmdAlloc, mAsteroidPSO, IID_PPV_ARGS(&mPostCmdLst))); + ThrowIfFailed(mPostCmdLst->Close()); // Avoid allocator issues... command lists really should be created in a "closed" state... + ThrowIfFailed(mDevice->CreateCommandList(1, D3D12_COMMAND_LIST_TYPE_DIRECT, mFrame[0].mCmdAlloc, mAsteroidPSO, IID_PPV_ARGS(&mPreCmdLst))); + ThrowIfFailed(mPreCmdLst->Close()); + + // Each command list for asteroid drawing maps 1:1 with a descriptor heap + // Change heaps is "free" at cmdlst boundaries and this greatly simplifies the code + // Thus the expectation is that we have ~ #threads heaps for multithreaded rendering on most GPUs + // Need at least one draw in each heap/cmd list... + minCmdLsts = std::min(minCmdLsts, (UINT)NUM_ASTEROIDS); + CreateSubsets(minCmdLsts); + std::cout << "Using " << mSubsetCount << " subsets per frame." << std::endl; + + // Just in case + WaitForAll(); +} + +Asteroids::~Asteroids() +{ + WaitForAll(); + ReleaseSwapChain(); + + SafeRelease(&mPreCmdLst); + SafeRelease(&mPostCmdLst); + + for (UINT i = 0; i < NUM_UNIQUE_TEXTURES; ++i) { + SafeRelease(&mAsteroidTextures[i]); + } + for (auto i : mSpriteTextures) { + i.second->Release(); + } + + SafeRelease(&mAsteroidPSO); + SafeRelease(&mFontTexture); + SafeRelease(&mFontPSO); + SafeRelease(&mSpritePSO); + SafeRelease(&mSkybox); + SafeRelease(&mSkyboxPSO); + + for (UINT f = 0; f < NUM_FRAMES_TO_BUFFER; ++f) { + auto frame = &mFrame[f]; + SafeRelease(&frame->mCmdAlloc); + delete frame->mDynamicUpload; + delete frame->mSRVDescs; + } + + ReleaseSubsets(); + + delete mMeshUpload; + + delete mRTVDescs; + delete mDSVDescs; + delete mSMPDescs; + delete mSRVDescs; + + SafeRelease(&mGenericRootSignature); + SafeRelease(&mAsteroidsRootSignature); + SafeRelease(&mCommandSignature); + SafeRelease(&mFence); + + SafeRelease(&mCommandQueue); + SafeRelease(&mDevice); + + if (mFenceEventHandle != NULL) { + CloseHandle(mFenceEventHandle); + mFenceEventHandle = NULL; + } + + delete mD3D12Sprite; +} + + +void Asteroids::ReleaseSwapChain() +{ + WaitForAll(); + + for (UINT s = 0; s < NUM_SWAP_CHAIN_BUFFERS; s++) { + SafeRelease(&mSwapChainBuffer[s].mRenderTarget); + } + SafeRelease(&mDepthStencil); + SafeRelease(&mSwapChain); +} + +inline void DisableDXGIWindowChanges(IUnknown* device, HWND window) +{ + IDXGIDevice * pDXGIDevice; + ThrowIfFailed(device->QueryInterface(IID_PPV_ARGS(&pDXGIDevice))); + IDXGIAdapter * pDXGIAdapter; + ThrowIfFailed(pDXGIDevice->GetParent(IID_PPV_ARGS(&pDXGIAdapter))); + IDXGIFactory * pIDXGIFactory; + ThrowIfFailed(pDXGIAdapter->GetParent(IID_PPV_ARGS(&pIDXGIFactory))); + + ThrowIfFailed(pIDXGIFactory->MakeWindowAssociation(window, DXGI_MWA_NO_WINDOW_CHANGES | DXGI_MWA_NO_ALT_ENTER)); + + pIDXGIFactory->Release(); + pDXGIAdapter->Release(); + pDXGIDevice->Release(); +} + +void Asteroids::ResizeSwapChain(IDXGIFactory2* dxgiFactory, HWND outputWindow, unsigned int width, unsigned int height) +{ + ReleaseSwapChain(); + + // Create swap chain + { + DXGI_SWAP_CHAIN_DESC1 swapChainDesc = {}; + swapChainDesc.Width = width; + swapChainDesc.Height = height; + swapChainDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; // Can create an SRGB render target view on the swap chain buffer + swapChainDesc.Stereo = FALSE; + swapChainDesc.SampleDesc.Count = 1; + swapChainDesc.SampleDesc.Quality = 0; + swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; + swapChainDesc.BufferCount = NUM_SWAP_CHAIN_BUFFERS; + swapChainDesc.Scaling = DXGI_SCALING_STRETCH; + swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD; + swapChainDesc.AlphaMode = DXGI_ALPHA_MODE_UNSPECIFIED; // Not used + swapChainDesc.Flags = 0; + + IDXGISwapChain1 *swapChain1 = nullptr; + ThrowIfFailed(dxgiFactory->CreateSwapChainForHwnd( + mCommandQueue, outputWindow, &swapChainDesc, nullptr, nullptr, &swapChain1)); + // MakeWindowAssociation must be called after CreateSwapChain + ThrowIfFailed(dxgiFactory->MakeWindowAssociation(outputWindow, + DXGI_MWA_NO_WINDOW_CHANGES | DXGI_MWA_NO_ALT_ENTER | DXGI_MWA_NO_PRINT_SCREEN)); + ThrowIfFailed(swapChain1->QueryInterface(&mSwapChain)); + swapChain1->Release(); + } + + { + // Create an SRGB view of the swap chain buffer + D3D12_RENDER_TARGET_VIEW_DESC desc = {}; + desc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2D; + desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM_SRGB; + desc.Texture2D.MipSlice = 0; + desc.Texture2D.PlaneSlice = 0; + + // Create render target view for each swap chain buffer + for (UINT s = 0; s < NUM_SWAP_CHAIN_BUFFERS; s++) { + auto buffer = &mSwapChainBuffer[s]; + ThrowIfFailed(mSwapChain->GetBuffer(s, IID_PPV_ARGS(&buffer->mRenderTarget))); + mDevice->CreateRenderTargetView(buffer->mRenderTarget, &desc, buffer->mRenderTargetView); + } + } + + // create depth stencil view + { + D3D12_RESOURCE_DESC desc = CD3DX12_RESOURCE_DESC::Tex2D( + DXGI_FORMAT_D32_FLOAT, width, height, 1, 1, 1, 0, + D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL ); + + // We use (1-z) so typically clear depth to 0 + D3D12_CLEAR_VALUE clearValue = {}; + clearValue.Format = desc.Format; + clearValue.DepthStencil.Depth = 0.0f; + clearValue.DepthStencil.Stencil = 0; + + ThrowIfFailed(mDevice->CreateCommittedResource( + &CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT), + D3D12_HEAP_FLAG_NONE, + &desc, + D3D12_RESOURCE_STATE_DEPTH_WRITE, + &clearValue, + IID_PPV_ARGS(&mDepthStencil) + )); + + mDevice->CreateDepthStencilView(mDepthStencil, nullptr, mDepthStencilView); + } + + // create the viewport and scissor + ZeroMemory(&mViewPort, sizeof(D3D11_VIEWPORT)); + mViewPort.TopLeftX = 0; + mViewPort.TopLeftY = 0; + mViewPort.Width = (float)width; + mViewPort.Height = (float)height; + mViewPort.MinDepth = 0.0f; + mViewPort.MaxDepth = 1.0f; + + mScissorRect.left = 0; + mScissorRect.top = 0; + mScissorRect.right = width; + mScissorRect.bottom = height; +} + +void Asteroids::CreatePSOs() +{ + // Generic root signature (t0, s0, b0) + { + CD3DX12_DESCRIPTOR_RANGE descRanges[2]; + descRanges[0].Init(D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 1, 0, 0); // t0 + descRanges[1].Init(D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER, 1, 0); // s0 + + CD3DX12_ROOT_PARAMETER rootParams[3]; + rootParams[RP_DRAW_CBV].InitAsConstantBufferView(0, 0, D3D12_SHADER_VISIBILITY_ALL); // b0 + rootParams[RP_TEX_SRV].InitAsDescriptorTable(1, &descRanges[0], D3D12_SHADER_VISIBILITY_PIXEL); // t0 + rootParams[RP_SMP].InitAsDescriptorTable(1, &descRanges[1], D3D12_SHADER_VISIBILITY_PIXEL); // s0 + + CD3DX12_ROOT_SIGNATURE_DESC RSLayout(ARRAYSIZE(rootParams), rootParams, 0, 0, + D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT | + D3D12_ROOT_SIGNATURE_FLAG_DENY_HULL_SHADER_ROOT_ACCESS | + D3D12_ROOT_SIGNATURE_FLAG_DENY_DOMAIN_SHADER_ROOT_ACCESS | + D3D12_ROOT_SIGNATURE_FLAG_DENY_GEOMETRY_SHADER_ROOT_ACCESS); + + ID3DBlob* serializedLayout = nullptr; + D3D12SerializeRootSignature(&RSLayout, D3D_ROOT_SIGNATURE_VERSION_1, &serializedLayout, 0); + + ThrowIfFailed(mDevice->CreateRootSignature( + 1, + serializedLayout->GetBufferPointer(), + serializedLayout->GetBufferSize(), + IID_PPV_ARGS(&mGenericRootSignature))); + + serializedLayout->Release(); + } + + // Asteroids root signature (tN, s0, b0) + { + CD3DX12_DESCRIPTOR_RANGE descRanges[2]; + descRanges[0].Init(D3D12_DESCRIPTOR_RANGE_TYPE_SRV, NUM_UNIQUE_TEXTURES, 0, 0); // t0...tN + descRanges[1].Init(D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER, 1, 0); // s0 + + CD3DX12_ROOT_PARAMETER rootParams[3]; + rootParams[RP_DRAW_CBV].InitAsConstantBufferView(0, 0, D3D12_SHADER_VISIBILITY_ALL); // b0 + rootParams[RP_TEX_SRV].InitAsDescriptorTable(1, &descRanges[0], D3D12_SHADER_VISIBILITY_PIXEL); // t0 + rootParams[RP_SMP].InitAsDescriptorTable(1, &descRanges[1], D3D12_SHADER_VISIBILITY_PIXEL); // s0 + + CD3DX12_ROOT_SIGNATURE_DESC RSLayout(ARRAYSIZE(rootParams), rootParams, 0, 0, + D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT | + D3D12_ROOT_SIGNATURE_FLAG_DENY_HULL_SHADER_ROOT_ACCESS | + D3D12_ROOT_SIGNATURE_FLAG_DENY_DOMAIN_SHADER_ROOT_ACCESS | + D3D12_ROOT_SIGNATURE_FLAG_DENY_GEOMETRY_SHADER_ROOT_ACCESS); + + ID3DBlob* serializedLayout = nullptr; + D3D12SerializeRootSignature(&RSLayout, D3D_ROOT_SIGNATURE_VERSION_1, &serializedLayout, 0); + + ThrowIfFailed(mDevice->CreateRootSignature( + 1, + serializedLayout->GetBufferPointer(), + serializedLayout->GetBufferSize(), + IID_PPV_ARGS(&mAsteroidsRootSignature))); + + serializedLayout->Release(); + } + + // Command signature + { + D3D12_INDIRECT_ARGUMENT_DESC args[2] = {}; + args[0].Type = D3D12_INDIRECT_ARGUMENT_TYPE_CONSTANT_BUFFER_VIEW; + args[0].ConstantBufferView.RootParameterIndex = RP_DRAW_CBV; + args[1].Type = D3D12_INDIRECT_ARGUMENT_TYPE_DRAW_INDEXED; + + D3D12_COMMAND_SIGNATURE_DESC desc; + desc.ByteStride = sizeof(ExecuteIndirectArgs); + desc.NodeMask = 1; + desc.pArgumentDescs = args; + desc.NumArgumentDescs = ARRAYSIZE(args); + + ThrowIfFailed(mDevice->CreateCommandSignature(&desc, mGenericRootSignature, IID_PPV_ARGS(&mCommandSignature))); + } + + // Common state for this app + D3D12_GRAPHICS_PIPELINE_STATE_DESC defaultDesc = {}; + defaultDesc.BlendState = CD3DX12_BLEND_DESC(CD3DX12_DEFAULT()); + defaultDesc.DepthStencilState = CD3DX12_DEPTH_STENCIL_DESC(CD3DX12_DEFAULT()); + defaultDesc.RasterizerState = CD3DX12_RASTERIZER_DESC(CD3DX12_DEFAULT()); + defaultDesc.pRootSignature = mGenericRootSignature; + defaultDesc.SampleMask = UINT_MAX; + defaultDesc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE; + defaultDesc.NumRenderTargets = 1; + defaultDesc.RTVFormats[0] = mRTVFormat; + defaultDesc.DSVFormat = mDSVFormat; + defaultDesc.DepthStencilState.DepthFunc = D3D12_COMPARISON_FUNC_GREATER_EQUAL; + defaultDesc.SampleDesc.Count = 1; + + // asteroid pipeline state + D3D12_INPUT_ELEMENT_DESC asteroidInputDesc[] = { + { "ATTRIB", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }, + { "ATTRIB", 1, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }, + }; + D3D12_GRAPHICS_PIPELINE_STATE_DESC asteroidDesc = defaultDesc; + asteroidDesc.pRootSignature = mAsteroidsRootSignature; + asteroidDesc.VS = { g_asteroid_vs, sizeof(g_asteroid_vs) }; + asteroidDesc.PS = { g_asteroid_ps, sizeof(g_asteroid_ps) }; + asteroidDesc.InputLayout = { asteroidInputDesc, ARRAYSIZE(asteroidInputDesc) }; + + // skybox pipeline state + D3D12_INPUT_ELEMENT_DESC skyboxInputDesc[] = { + { "ATTRIB", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }, + { "ATTRIB", 1, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }, + }; + D3D12_GRAPHICS_PIPELINE_STATE_DESC skyboxDesc = defaultDesc; + skyboxDesc.VS = { g_skybox_vs, sizeof(g_skybox_vs) }; + skyboxDesc.PS = { g_skybox_ps, sizeof(g_skybox_ps) }; + skyboxDesc.InputLayout = { skyboxInputDesc, ARRAYSIZE(skyboxInputDesc) }; + + // sprite pipeline state + D3D12_INPUT_ELEMENT_DESC spriteInputDesc[] = { + { "ATTRIB", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }, + { "ATTRIB", 1, DXGI_FORMAT_R32G32_FLOAT, 0, 8, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }, + }; + D3D12_GRAPHICS_PIPELINE_STATE_DESC spriteDesc = defaultDesc; + spriteDesc.VS = { g_sprite_vs, sizeof(g_sprite_vs) }; + spriteDesc.PS = { g_sprite_ps, sizeof(g_sprite_ps) }; + spriteDesc.InputLayout = { spriteInputDesc, ARRAYSIZE(spriteInputDesc) }; + spriteDesc.DepthStencilState.DepthEnable = FALSE; + // Premultiplied over blend + spriteDesc.BlendState.RenderTarget[0].BlendEnable = TRUE; + spriteDesc.BlendState.RenderTarget[0].SrcBlend = D3D12_BLEND_ONE; + spriteDesc.BlendState.RenderTarget[0].BlendOp = D3D12_BLEND_OP_ADD; + spriteDesc.BlendState.RenderTarget[0].DestBlend = D3D12_BLEND_INV_SRC_ALPHA; + + // font pipeline state + D3D12_GRAPHICS_PIPELINE_STATE_DESC fontDesc = spriteDesc; + fontDesc.PS = { g_font_ps, sizeof(g_font_ps) }; + + concurrency::parallel_invoke( + [&] { ThrowIfFailed(mDevice->CreateGraphicsPipelineState(&asteroidDesc, IID_PPV_ARGS(&mAsteroidPSO))); }, + [&] { ThrowIfFailed(mDevice->CreateGraphicsPipelineState(&skyboxDesc, IID_PPV_ARGS(&mSkyboxPSO))); }, + [&] { ThrowIfFailed(mDevice->CreateGraphicsPipelineState(&spriteDesc, IID_PPV_ARGS(&mSpritePSO))); }, + [&] { ThrowIfFailed(mDevice->CreateGraphicsPipelineState(&fontDesc, IID_PPV_ARGS(&mFontPSO))); } + ); +} + + +void Asteroids::CreateSubsets(UINT numHeapsPerFrame) +{ + ReleaseSubsets(); + + mDrawsPerSubset = (NUM_ASTEROIDS + numHeapsPerFrame - 1) / numHeapsPerFrame; + mSubsetCount = numHeapsPerFrame; + + for (UINT f = 0; f < NUM_FRAMES_TO_BUFFER; f++) { + // Per-frame data + auto frame = &mFrame[f]; + auto dynamicUploadGPUVA = frame->mDynamicUpload->Heap()->GetGPUVirtualAddress(); + + for (UINT subsetIdx = 0; subsetIdx < mSubsetCount; ++subsetIdx) { + void* memory = _aligned_malloc(sizeof(SubsetD3D12), 64); + auto subset = new(memory) SubsetD3D12(mDevice, NUM_UNIQUE_TEXTURES, mAsteroidPSO); + frame->mSubsets.push_back(subset); + } + } +} + +void Asteroids::ReleaseSubsets() +{ + WaitForAll(); + + for (UINT i = 0; i < NUM_FRAMES_TO_BUFFER; ++i) { + for (auto j : mFrame[i].mSubsets) { + j->~SubsetD3D12(); + _aligned_free(j); + } + mFrame[i].mSubsets.clear(); + } + + mSubsetCount = 0; + mDrawsPerSubset = 0; +} + + +void Asteroids::CreateMeshes() +{ + auto asteroidMeshes = mAsteroids->Meshes(); + std::vector<SkyboxVertex> skyboxVertices; + CreateSkyboxMesh(&skyboxVertices); + + // Simple linear allocate + UINT64 asteroidVBSize = asteroidMeshes->vertices.size() * sizeof(asteroidMeshes->vertices[0]); + UINT64 asteroidIBSize = asteroidMeshes->indices.size() * sizeof(asteroidMeshes->indices[0]); + UINT64 skyboxVBSize = skyboxVertices.size() * sizeof(SkyboxVertex); + + UINT64 asteroidVBOffset = 0; + UINT64 asteroidIBOffset = asteroidVBOffset + asteroidVBSize; + UINT64 skyboxVBOffset = asteroidIBOffset + asteroidIBSize; + UINT64 totalSize = skyboxVBOffset + skyboxVBSize; + + mMeshUpload = new UploadHeap(mDevice, totalSize); + auto bufferWO = (BYTE*)mMeshUpload->DataWO(); + auto gpuVA = mMeshUpload->Heap()->GetGPUVirtualAddress(); + + // Asteroid vertices + { + memcpy(bufferWO + asteroidVBOffset, asteroidMeshes->vertices.data(), asteroidVBSize); + + mAsteroidVertexBufferView.BufferLocation = gpuVA + asteroidVBOffset; + mAsteroidVertexBufferView.SizeInBytes = static_cast<UINT>(asteroidVBSize); + mAsteroidVertexBufferView.StrideInBytes = sizeof(asteroidMeshes->vertices[0]); + } + + // Asteroid indices + { + memcpy(bufferWO + asteroidIBOffset, asteroidMeshes->indices.data(), asteroidIBSize); + + mAsteroidIndexBufferView.BufferLocation = gpuVA + asteroidIBOffset; + mAsteroidIndexBufferView.SizeInBytes = static_cast<UINT>(asteroidIBSize); + mAsteroidIndexBufferView.Format = sizeof(IndexType) == 2 ? DXGI_FORMAT_R16_UINT : DXGI_FORMAT_R32_UINT; + } + + // Skybox vertices + { + memcpy(bufferWO + skyboxVBOffset, skyboxVertices.data(), skyboxVBSize); + + mSkyboxVertexBufferView.BufferLocation = gpuVA + skyboxVBOffset; + mSkyboxVertexBufferView.SizeInBytes = static_cast<UINT>(skyboxVBSize); + mSkyboxVertexBufferView.StrideInBytes = sizeof(skyboxVertices[0]); + } +} + + +void Asteroids::CreateGUIResources() +{ + auto font = mGUI->Font(); + auto textureDesc = CD3DX12_RESOURCE_DESC::Tex2D( + DXGI_FORMAT_R8_UNORM, font->BitmapWidth(), font->BitmapHeight(), 1, 1); + + ThrowIfFailed(mDevice->CreateCommittedResource( + &CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT), + D3D12_HEAP_FLAG_NONE, + &textureDesc, + D3D12_RESOURCE_STATE_COMMON, + nullptr, + IID_PPV_ARGS(&mFontTexture) + )); + + D3D11_SUBRESOURCE_DATA initialData = {}; + initialData.pSysMem = font->Pixels(); + initialData.SysMemPitch = font->BitmapWidth(); + + InitializeTexture2D(mDevice, mCommandQueue, mFontTexture, &textureDesc, 1, &initialData); + + // Load any GUI sprite textures + for (int i = -1; i < (int)mGUI->size(); ++i) { + auto control = i>=0 ? (*mGUI)[i] : mD3D12Sprite; + if (control->TextureFile().length() > 0 && mSpriteTextures.find(control->TextureFile()) == mSpriteTextures.end()) { + ID3D12Resource* texture = nullptr; + ThrowIfFailed(CreateTexture2DFromDDS_XXXX8( + mDevice, mCommandQueue, &texture, control->TextureFile().c_str(), DXGI_FORMAT_B8G8R8A8_UNORM_SRGB)); + mSpriteTextures[control->TextureFile()] = texture; + } + } +} + +void Asteroids::WaitForAll() +{ + ThrowIfFailed(mCommandQueue->Signal(mFence, ++mCurrentFence)); + ThrowIfFailed(mFence->SetEventOnCompletion(mCurrentFence, mFenceEventHandle)); + WaitForSingleObject(mFenceEventHandle, INFINITE); +} + +void Asteroids::WaitForReadyToRender() +{ + // Wait for both the GPU to be done with our per-frame resources + HANDLE handles[] = { mFenceEventHandle }; + + ThrowIfFailed(mFence->SetEventOnCompletion(mFrame[mCurrentFrameIndex].mFrameCompleteFence, mFenceEventHandle)); + WaitForMultipleObjects(ARRAYSIZE(handles), handles, TRUE, INFINITE); +} + +void Asteroids::RenderSubset( + D3D12_CPU_DESCRIPTOR_HANDLE renderTargetView, + size_t frameIndex, float frameTime, + SubsetD3D12* subset, UINT subsetIdx, + XMVECTOR cameraEye, XMMATRIX viewProjection, + const Settings& settings) +{ + UINT drawStart = mDrawsPerSubset * subsetIdx; + UINT drawEnd = std::min(drawStart + mDrawsPerSubset, (UINT)NUM_ASTEROIDS); + assert(drawStart < drawEnd); + auto staticAsteroidData = mAsteroids->StaticData(); + auto dynamicAsteroidData = mAsteroids->DynamicData(); + // Frame data + auto frame = &mFrame[frameIndex]; + auto drawConstantBuffers = frame->mDynamicUpload->DataWO()->mDrawConstantBuffers; + auto indirectArgs = frame->mDynamicUpload->DataWO()->mIndirectArgs; + + auto cmdLst = subset->Begin(mAsteroidPSO); + + // Root signature and common bindings + cmdLst->SetGraphicsRootSignature(mAsteroidsRootSignature); + ID3D12DescriptorHeap* heaps[2] = {mSRVDescs->Heap(), mSMPDescs->Heap()}; + cmdLst->SetDescriptorHeaps(ARRAYSIZE(heaps), heaps); + + // Common state + cmdLst->IASetIndexBuffer(&mAsteroidIndexBufferView); + cmdLst->IASetVertexBuffers(0, 1, &mAsteroidVertexBufferView); + cmdLst->RSSetViewports(1, &mViewPort); + cmdLst->RSSetScissorRects(1, &mScissorRect); + cmdLst->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); + cmdLst->OMSetRenderTargets(1, &renderTargetView, true, &mDepthStencilView); + + // Set textures (all as a single descriptor table) and samplers + cmdLst->SetGraphicsRootDescriptorTable(RP_TEX_SRV, mSRVDescs->GPU(0)); + cmdLst->SetGraphicsRootDescriptorTable(RP_SMP, mSampler); + + if (!settings.executeIndirect) + { + // Standard draw path + auto constantsPointer = frame->mDrawConstantBuffersGPUVA + sizeof(DrawConstantBuffer) * drawStart; + for (UINT drawIdx = drawStart; drawIdx < drawEnd; ++drawIdx) + { + auto staticData = &staticAsteroidData[drawIdx]; + auto dynamicData = &dynamicAsteroidData[drawIdx]; + + XMStoreFloat4x4(&drawConstantBuffers[drawIdx].mWorld, dynamicData->world); + XMStoreFloat4x4(&drawConstantBuffers[drawIdx].mViewProjection, viewProjection); + + // Set root cbuffer + //cmdLst->SetGraphicsRootDescriptorTable(RP_TEX_SRV, mSRVDescs->GPU(0)); + cmdLst->SetGraphicsRootConstantBufferView(RP_DRAW_CBV, constantsPointer); + constantsPointer += sizeof(DrawConstantBuffer); + + cmdLst->DrawIndexedInstanced(dynamicData->indexCount, 1, dynamicData->indexStart, staticData->vertexStart, 0); + } + } + else + { + // ExecuteIndirect path + for (UINT drawIdx = drawStart; drawIdx < drawEnd; ++drawIdx) + { + auto dynamicData = &dynamicAsteroidData[drawIdx]; + + XMStoreFloat4x4(&drawConstantBuffers[drawIdx].mWorld, dynamicData->world); + XMStoreFloat4x4(&drawConstantBuffers[drawIdx].mViewProjection, viewProjection); + + auto drawIndexed = &indirectArgs[drawIdx].mDrawIndexed; + drawIndexed->IndexCountPerInstance = dynamicData->indexCount; + drawIndexed->StartIndexLocation = dynamicData->indexStart; + } + + UINT64 offset = (BYTE*)(&indirectArgs[drawStart]) - (BYTE*)frame->mDynamicUpload->DataWO(); + cmdLst->ExecuteIndirect(mCommandSignature, drawEnd - drawStart, + frame->mDynamicUpload->Heap(), offset, + nullptr, 0); + } + + subset->End(); +} + +void Asteroids::Render(float frameTime, const OrbitCamera& camera, const Settings& settings) +{ + // Pick the right swap chain buffer based on where DXGI says we are... + auto backBufferIndex = mSwapChain->GetCurrentBackBufferIndex(); + assert(backBufferIndex < NUM_SWAP_CHAIN_BUFFERS); + auto swapChainBuffer = &mSwapChainBuffer[backBufferIndex]; + + // And the right frame data based on our own rotation/fences + assert(mCurrentFrameIndex < NUM_FRAMES_TO_BUFFER); + auto frame = &mFrame[mCurrentFrameIndex]; + + QueryPerformanceCounter((LARGE_INTEGER*)&mTotalUpdateTicks); + // Update asteroid simulation + if (settings.multithreadedRendering) + { + concurrency::parallel_for<UINT>(0, mSubsetCount, [&](UINT subsetIdx) { + UINT drawStart = mDrawsPerSubset * subsetIdx; + UINT drawEnd = std::min(drawStart + mDrawsPerSubset, (UINT)NUM_ASTEROIDS); + mAsteroids->Update(frameTime, camera.Eye(), settings, drawStart, drawEnd - drawStart); + }); + } + else + { + mAsteroids->Update(frameTime, camera.Eye(), settings, 0, (UINT)NUM_ASTEROIDS); + } + LONG64 currCounter; + QueryPerformanceCounter((LARGE_INTEGER*)&currCounter); + mTotalUpdateTicks = currCounter - mTotalUpdateTicks; + + mTotalRenderTicks = currCounter; + // Generate command lists + if (settings.multithreadedRendering) + { + concurrency::parallel_for<UINT>(0, mSubsetCount, [&](UINT subsetIdx) { + RenderSubset(swapChainBuffer->mRenderTargetView, mCurrentFrameIndex, frameTime, + frame->mSubsets[subsetIdx], subsetIdx, camera.Eye(), camera.ViewProjection(), settings); + }); + } + else + { + LONG64 SubsetUpdateTicks = 0, SubsetRenderTicks = 0; + for (unsigned int subsetIdx = 0; subsetIdx < mSubsetCount; ++subsetIdx) { + RenderSubset(swapChainBuffer->mRenderTargetView, mCurrentFrameIndex, frameTime, + frame->mSubsets[subsetIdx], subsetIdx, camera.Eye(), camera.ViewProjection(), settings); + } + } + QueryPerformanceCounter((LARGE_INTEGER*)&currCounter); + mTotalRenderTicks = currCounter - mTotalRenderTicks; + + LONG64 prevCounter = currCounter; + // Set up pre and post commands + { + auto cmdAlloc = frame->mCmdAlloc; + auto dynamicUploadWO = frame->mDynamicUpload->DataWO(); + ThrowIfFailed(cmdAlloc->Reset()); + + // Set resource states for rendering + ResourceBarrier rb; + rb.AddTransition(swapChainBuffer->mRenderTarget, D3D12_RESOURCE_STATE_PRESENT, D3D12_RESOURCE_STATE_RENDER_TARGET); + + // Pre + { + ThrowIfFailed(mPreCmdLst->Reset(cmdAlloc, mAsteroidPSO)); + rb.Submit(mPreCmdLst); + + // Don't need to clear color at the moment - skybox overwrites it all and no MSAA + //float clearcol[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + //mPreCmdLst->ClearRenderTargetView(swapChainBuffer->mRenderTargetView, clearcol, 0, 0); + + // Clear depth + mPreCmdLst->ClearDepthStencilView(mDepthStencilView, D3D12_CLEAR_FLAG_DEPTH, 0.0f, 0, 0, nullptr); + + ThrowIfFailed(mPreCmdLst->Close()); + } + + rb.ReverseTransitions(); + + // Post + { + ThrowIfFailed(mPostCmdLst->Reset(cmdAlloc, mSkyboxPSO)); + + // Root signature and descriptor heaps + mPostCmdLst->SetGraphicsRootSignature(mGenericRootSignature); + ID3D12DescriptorHeap* heaps[2] = { frame->mSRVDescs->Heap(), mSMPDescs->Heap() }; + mPostCmdLst->SetDescriptorHeaps(2, heaps); + + mPostCmdLst->SetGraphicsRootDescriptorTable(RP_SMP, mSampler); + + // Common state + mPostCmdLst->RSSetViewports(1, &mViewPort); + mPostCmdLst->RSSetScissorRects(1, &mScissorRect); + mPostCmdLst->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); + mPostCmdLst->OMSetRenderTargets(1, &swapChainBuffer->mRenderTargetView, true, &mDepthStencilView); + + // Draw skybox + { + auto constants = &frame->mDynamicUpload->DataWO()->mSkyboxConstants; + XMStoreFloat4x4(&constants->mViewProjection, camera.ViewProjection()); + + mPostCmdLst->IASetVertexBuffers(0, 1, &mSkyboxVertexBufferView); + + mPostCmdLst->SetGraphicsRootConstantBufferView(RP_DRAW_CBV, frame->mSkyboxConstants); + mPostCmdLst->SetGraphicsRootDescriptorTable(RP_TEX_SRV, frame->mSkyboxTexture); + mPostCmdLst->DrawInstanced(6 * 6, 1, 0, 0); + } + + // Draw sprites + { + auto vertexBase = dynamicUploadWO->mSpriteVertices; + auto vertexEnd = vertexBase; + + // Drop off any dynamic descriptors from the last frame + auto descHeap = frame->mSRVDescs; + descHeap->Resize(frame->mSRVDescsDynamicStart); + + mPostCmdLst->IASetVertexBuffers(0, 1, &frame->mSpriteVertexBufferView); + + for (int i = -1; i < (int)mGUI->size(); ++i) { + auto control = i >=0 ? (*mGUI)[i] : mD3D12Sprite; + if (!control->Visible()) continue; + + UINT numVertices = (UINT)(control->Draw(mViewPort.Width, mViewPort.Height, vertexEnd) - vertexEnd); + ID3D12Resource* texture = nullptr; + + // TODO: Could eliminate redundant state setting and cache descriptors... meh for now. + if (control->TextureFile().length() == 0) { // Font + mPostCmdLst->SetPipelineState(mFontPSO); + texture = mFontTexture; + } + else { // Sprite + mPostCmdLst->SetPipelineState(mSpritePSO); + texture = mSpriteTextures[control->TextureFile()]; + } + + if (texture) { + mPostCmdLst->SetGraphicsRootDescriptorTable(RP_TEX_SRV, descHeap->AppendSRV(texture)); + } + + mPostCmdLst->DrawInstanced(numVertices, 1, (UINT)(vertexEnd - vertexBase), 0); + vertexEnd += numVertices; + } + } + + // Final resource state transitions + rb.Submit(mPostCmdLst); + ThrowIfFailed(mPostCmdLst->Close()); + } + } + + // Set up command lists for submission + mCmdListsToSubmit.resize(0); + mCmdListsToSubmit.push_back(mPreCmdLst); + if (settings.submitRendering) { + for (auto &i :frame->mSubsets) + mCmdListsToSubmit.push_back(i->mCmdLst); + } + mCmdListsToSubmit.push_back(mPostCmdLst); + + mCommandQueue->ExecuteCommandLists( + (UINT)mCmdListsToSubmit.size(), + CommandListCast(mCmdListsToSubmit.data())); + + if (settings.vsync) + ThrowIfFailed(mSwapChain->Present(1, 0)); + else + ThrowIfFailed(mSwapChain->Present(0, 0)); + + ThrowIfFailed(mCommandQueue->Signal(mFence, ++mCurrentFence)); + frame->mFrameCompleteFence = mCurrentFence; + mCurrentFrameIndex = (mCurrentFrameIndex + 1) % NUM_FRAMES_TO_BUFFER; + + //LONG64 currCounter; + QueryPerformanceCounter((LARGE_INTEGER*)&currCounter); + mTotalRenderTicks += currCounter - prevCounter; +} + +void Asteroids::GetPerfCounters(float &UpdateTime, float &RenderTime) +{ + UpdateTime = (float)mTotalUpdateTicks/(float)mPerfCounterFreq; + RenderTime = (float)mTotalRenderTicks/(float)mPerfCounterFreq; +} + +} // namespace AsteroidsD3D12 |
