好像有个叫 Vulkan 的图形 API 试图替代 Opengl,不过要跟上时代,还是得把 WIN10 装上,试试看在 DX12 写个下个时代的 3D 设计工具,当然只是个小玩意,不过据说很多软件能走到今天,也是因为积淀,如果不早点开始积淀,就永远会落后那么多
我想给这个玩意起个名字,叫啥呢,就叫 W4D 好了,首先这个软件定位不是游戏引擎,而且是超轻量的,W:windows 4:x y z t D: Data 指代在 windows 下处理 3 维与时间数据的软件
想要有什么呢?
1:首先需要能应对基本的 3D 建模,绑骨,展 UV 贴图,动作
2:要有语言能应对复杂的各种情况 类似 maxscript 脚本语言,但是这里可能级别要更强
3:最好有时间轴,觉得这个概念与 2D 里 flash 接近,跟 unity 里 animation 差不多,用不着搞成 timeline 那么复杂。
代码使用 C++ DX,确定要一切考虑效率,即使函数入口都不要检查,只在最外层进行检查,出错是程序员的事,代码不是用来为程序员负责的,希望这个玩意能像刀片一样 欻欻的就搞定哈哈
环境 WIN10,VS2017.这二者搞定后,微软官方例子可以直接打开编译运行的,miniEngine 可能要重定向项目-> 选择相应的 SDK 版本(在解决方案右键会出来选项)
dx12 基础
http://www.rastertek.com/tutdx12.html
官网的
https://msdn.microsoft.com/en-us/library/windows/desktop/dn899121(v=vs.85).aspx
Working samples
例子
Working samples (in the form of Visual Studio 2015 projects) can be downloaded from GitHub/Microsoft/DirectX-Graphics-Samples.
DX12 遍历显卡
Intel(R) HD Graphics 4600
NVIDIA GeForce GTX 960M
Microsoft Basic Render Driver
把特性设置为 D3D_FEATURE_LEVEL_11_0
HRESULT hr = CreateDXGIFactory(__uuidof(IDXGIFactory4), (void**)&factory);
int i = 0;
IDXGIAdapter * pAdapter;
std::vector <IDXGIAdapter*> vAdapters;
while (factory->EnumAdapters(i, &pAdapter) != DXGI_ERROR_NOT_FOUND)
{
//hr = D3D12CreateDevice(pAdapter, feature_level, __uuidof(ID3D12Device), (void**)& pDevice);
//if (SUCCEEDED(hr))
// break;
pAdapter->GetDesc(&adapterDesc);
wcstombs_s(&stringLength, (char*)videoCardDes, 128, adapterDesc.Description, 128);
vAdapters.push_back(pAdapter);
++i;
}
http://www.rastertek.com/
这个网站上 3 篇 DX12 的已经照着写了一遍,感觉概念多,复杂,勉强能跑个背景色出来就废了这么大功夫,可惜的是作者似乎没有继续写了
还是看 dx12 在 git 上的例子好了。
哎呀,感觉好难啊。
打开微软 ModelViewer_VS15.sln 的例子,运行效果还是挺好的,就是连入口都摸了半天
入口是一个宏
CREATE_APPLICATION( ModelViewer )
映射到 GameCore::RunApplication( app_class(), L#app_class );
namespace GameCore 里定义了这个全局函数 RunApplication
这个函数初始化了引擎的各个部分,注册了窗口类,创建了消息循环,循环结束后,进行各个部分的资源清理,基本所有引擎框架都有类似这样的一个结构吧。
不过这个函数的入口是 c 语言的 int wmain(int argc, wchar_t** argv),估计是要通过宏适应多平台吧。
在 GameCore::RunApplication 里看下有哪些模块都在这里初始化的
在创建完窗口后调用了
InitializeApplication(app);
定义为
void InitializeApplication( IGameApp& game )
{
Graphics::Initialize();
SystemTime::Initialize();
GameInput::Initialize();
EngineTuning::Initialize();
game.Startup();
}
这个 game 参数,就是 ModelViewer 的一个对象
这部分基本和
www.rastertek.com
描述的差不多,图形初始化,输入初始化,游戏初始化,系统时间初始化,引擎协调初始化
Graphics::Initialize();在这里,
创建了 dx12 设备
(遍历了一次取了显存最大的显卡,还过滤掉了软件模拟卡)
SIZE_T MaxSize = 0;
for (uint32_t Idx = 0; DXGI_ERROR_NOT_FOUND != dxgiFactory->EnumAdapters1(Idx, &pAdapter); ++Idx)
{
DXGI_ADAPTER_DESC1 desc;
pAdapter->GetDesc1(&desc);
if (desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE)
continue;
if (desc.DedicatedVideoMemory > MaxSize && SUCCEEDED(D3D12CreateDevice(pAdapter.Get(), D3D_FEATURE_LEVEL_11_0, MY_IID_PPV_ARGS(&pDevice))))
{
pAdapter->GetDesc1(&desc);
Utility::Printf(L"D3D12-capable hardware found: %s (%u MB)\n", desc.Description, desc.DedicatedVideoMemory >> 20);
MaxSize = desc.DedicatedVideoMemory;
}
}
if (MaxSize > 0)
g_Device = pDevice.Detach();
然后检查了设备是否支持 R11G11B10 和 R16G16B16A16 2 种 UAVLoadSupport 上传格式,这部分仅仅设置了几个变量标识是否有此特性(g_bTypedUAVLoadSupport_R11G11B10_FLOAT,g_bTypedUAVLoadSupport_R16G16B16A16_FLOAT)。
D3D12_FEATURE_DATA_D3D12_OPTIONS FeatureData = {};
if (SUCCEEDED(g_Device->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS, &FeatureData, sizeof(FeatureData))))
{
if (FeatureData.TypedUAVLoadAdditionalFormats)
{
D3D12_FEATURE_DATA_FORMAT_SUPPORT Support =
{
DXGI_FORMAT_R11G11B10_FLOAT, D3D12_FORMAT_SUPPORT1_NONE, D3D12_FORMAT_SUPPORT2_NONE
};
if (SUCCEEDED(g_Device->CheckFeatureSupport(D3D12_FEATURE_FORMAT_SUPPORT, &Support, sizeof(Support))) &&
(Support.Support2 & D3D12_FORMAT_SUPPORT2_UAV_TYPED_LOAD) != 0)
{
g_bTypedUAVLoadSupport_R11G11B10_FLOAT = true;
}
Support.Format = DXGI_FORMAT_R16G16B16A16_FLOAT;
if (SUCCEEDED(g_Device->CheckFeatureSupport(D3D12_FEATURE_FORMAT_SUPPORT, &Support, sizeof(Support))) &&
(Support.Support2 & D3D12_FORMAT_SUPPORT2_UAV_TYPED_LOAD) != 0)
{
g_bTypedUAVLoadSupport_R16G16B16A16_FLOAT = true;
}
}
}
接着创建了各种 commandqueue,
g_CommandManager.Create(g_Device);
g_CommandManager 是 CommandListManager 类型的
跟进去看一下
void CommandListManager::Create(ID3D12Device* pDevice)
{
ASSERT(pDevice != nullptr);
m_Device = pDevice;
m_GraphicsQueue.Create(pDevice);
m_ComputeQueue.Create(pDevice);
m_CopyQueue.Create(pDevice);
}
m_GraphicsQueue m_ComputeQueue m_CopyQueue 都是 CommandQueue,这里的实现都一样
void CommandQueue::Create(ID3D12Device* pDevice)
{
ASSERT(pDevice != nullptr);
ASSERT(!IsReady());
ASSERT(m_AllocatorPool.Size() == 0);
D3D12_COMMAND_QUEUE_DESC QueueDesc = {};
QueueDesc.Type = m_Type;
QueueDesc.NodeMask = 1;
pDevice->CreateCommandQueue(&QueueDesc, MY_IID_PPV_ARGS(&m_CommandQueue));
m_CommandQueue->SetName(L"CommandListManager::m_CommandQueue");
ASSERT_SUCCEEDED(pDevice->CreateFence(0, D3D12_FENCE_FLAG_NONE, MY_IID_PPV_ARGS(&m_pFence)));
m_pFence->SetName(L"CommandListManager::m_pFence");
m_pFence->Signal((uint64_t)m_Type << 56);
m_FenceEventHandle = CreateEvent(nullptr, false, false, nullptr);
ASSERT(m_FenceEventHandle != INVALID_HANDLE_VALUE);
m_AllocatorPool.Create(pDevice);
ASSERT(IsReady());
}
这个类应该算对 dx12 commandqueue 的封装吧,核心就是使用 pDevice->CreateCommandQueue(&QueueDesc, MY_IID_PPV_ARGS(&m_CommandQueue));
然后创建了 fence,fence_event
m_AllocatorPool.Create(pDevice);只是把类的成员变量设置为 pDevice,这个 m_AllocatorPool 也是对 commandAllocator 的一个池的封装 他成员里有 std::vector<ID3D12CommandAllocator*> m_AllocatorPool;
这部分完了,返回堆栈上面的 Graphics::Initialize();
在 g_CommandManager.Create(g_Device);之后
有
DXGI_SWAP_CHAIN_DESC1 swapChainDesc = {};
swapChainDesc.Width = g_DisplayWidth;
swapChainDesc.Height = g_DisplayHeight;
swapChainDesc.Format = SwapChainFormat;
swapChainDesc.Scaling = DXGI_SCALING_NONE;
swapChainDesc.SampleDesc.Quality = 0;
swapChainDesc.SampleDesc.Count = 1;
swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
swapChainDesc.BufferCount = SWAP_CHAIN_BUFFER_COUNT;
swapChainDesc.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;
swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL;
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) // Win32
ASSERT_SUCCEEDED(dxgiFactory->CreateSwapChainForHwnd(g_CommandManager.GetCommandQueue(), GameCore::g_hWnd, &swapChainDesc, nullptr, nullptr, &s_SwapChain1));
创建了一个交换链的描述,然后通过这个描述(swapChainDesc)创建了一个交换链,g_CommandManager.GetCommandQueue()返回的是他成员里,m_GraphicsQueue 创建的 CommandQueue, 交换链接口的指针放到 s_SwapChain1 里
下面有部分代码不明白意思,跳过去
#if CONDITIONALLY_ENABLE_HDR_OUTPUT && defined(NTDDI_WIN10_RS2) && (NTDDI_VERSION >= NTDDI_WIN10_RS2)
{
IDXGISwapChain4* swapChain = (IDXGISwapChain4*)s_SwapChain1;
ComPtr output;
ComPtr output6;
DXGI_OUTPUT_DESC1 outputDesc;
UINT colorSpaceSupport;
// Query support for ST.2084 on the display and set the color space accordingly
if (SUCCEEDED(swapChain->GetContainingOutput(&output)) &&
SUCCEEDED(output.As(&output6)) &&
SUCCEEDED(output6->GetDesc1(&outputDesc)) &&
outputDesc.ColorSpace == DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020 &&
SUCCEEDED(swapChain->CheckColorSpaceSupport(DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020, &colorSpaceSupport)) &&
(colorSpaceSupport & DXGI_SWAP_CHAIN_COLOR_SPACE_SUPPORT_FLAG_PRESENT) &&
SUCCEEDED(swapChain->SetColorSpace1(DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020)))
{
g_bEnableHDROutput = true;
}
}
#endif
看英文解释是是否支持 ST.2048 并设置交换链的颜色空间的。
接着看
for (uint32_t i = 0; i < SWAP_CHAIN_BUFFER_COUNT; ++i)
{
ComPtr DisplayPlane;
ASSERT_SUCCEEDED(s_SwapChain1->GetBuffer(i, MY_IID_PPV_ARGS(&DisplayPlane)));
g_DisplayPlane[i].CreateFromSwapChain(L"Primary SwapChain Buffer", DisplayPlane.Detach());
}
这部分是创建交换链使用的资源以及相应的 RenderTargetView,一个交换链上创建数个 RTV,话说这个包装还是有点难理解的。
交换链上 GetBuffer 类似按照指定的序号(在这里就是 i),把 ID3D12Resource 指针返回,要使用就创建一个 ID3D12Resource 描述的 RTV 对象
这个 RTV 对象是通过
g_DisplayPlane[i].CreateFromSwapChain(L"Primary SwapChain Buffer", DisplayPlane.Detach())创建的
g_DisplayPlane 是一个 ColorBuffer 数组
跟进去看一下
void ColorBuffer::CreateFromSwapChain( const std::wstring& Name, ID3D12Resource* BaseResource )
{
AssociateWithResource(Graphics::g_Device, Name, BaseResource, D3D12_RESOURCE_STATE_PRESENT);
//m_UAVHandle[0] = Graphics::AllocateDescriptor(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
//Graphics::g_Device->CreateUnorderedAccessView(m_pResource.Get(), nullptr, nullptr, m_UAVHandle[0]);
m_RTVHandle = Graphics::AllocateDescriptor(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
Graphics::g_Device->CreateRenderTargetView(m_pResource.Get(), nullptr, m_RTVHandle);
}
再跟进
AssociateWithResource(Graphics::g_Device, Name, BaseResource, D3D12_RESOURCE_STATE_PRESENT);
ColorBuffer 继承 PixelBuffer,跳转到这里
void PixelBuffer::AssociateWithResource( ID3D12Device* Device, const std::wstring& Name, ID3D12Resource* Resource, D3D12_RESOURCE_STATES CurrentState )
{
(Device); // Unused until we support multiple adapters
ASSERT(Resource != nullptr);
D3D12_RESOURCE_DESC ResourceDesc = Resource->GetDesc();
m_pResource.Attach(Resource);
m_UsageState = CurrentState;
m_Width = (uint32_t)ResourceDesc.Width; // We don't care about large virtual textures yet
m_Height = ResourceDesc.Height;
m_ArraySize = ResourceDesc.DepthOrArraySize;
m_Format = ResourceDesc.Format;
#ifndef RELEASE
m_pResource->SetName(Name.c_str());
#else
(Name);
#endif
}
这个函数只是调用 ID312DResources 指针获取了资源描述 GetDesc(),然后把这些描述的属性记在了自己的成员里
跳出 AssociateWithResource 函数返回
void ColorBuffer::CreateFromSwapChain( const std::wstring& Name, ID3D12Resource* BaseResource )
{
AssociateWithResource(Graphics::g_Device, Name, BaseResource, D3D12_RESOURCE_STATE_PRESENT);//跳出堆栈
//m_UAVHandle[0] = Graphics::AllocateDescriptor(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
//Graphics::g_Device->CreateUnorderedAccessView(m_pResource.Get(), nullptr, nullptr, m_UAVHandle[0]);
//到这里
m_RTVHandle = Graphics::AllocateDescriptor(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
Graphics::g_Device->CreateRenderTargetView(m_pResource.Get(), nullptr, m_RTVHandle);
}
m_RTVHandle 大概是一个 RTV 对象描述 的句柄。
跟进 Graphics::AllocateDescriptor(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);内部
inline D3D12_CPU_DESCRIPTOR_HANDLE AllocateDescriptor( D3D12_DESCRIPTOR_HEAP_TYPE Type, UINT Count = 1 )
{
return g_DescriptorAllocator[Type].Allocate(Count);
}
实际上最后是用 g_DescriptorAllocator[Type].Allocate(Count)返回了一个句柄,看下 g_DescriptorAllocator 定义
DescriptorAllocator g_DescriptorAllocator[D3D12_DESCRIPTOR_HEAP_TYPE_NUM_TYPES] =
{
D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV,
D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER,
D3D12_DESCRIPTOR_HEAP_TYPE_RTV,
D3D12_DESCRIPTOR_HEAP_TYPE_DSV,
};
看的到这是一个 DescriptorAllocator 类型的数组,这个 g_DescriptorAllocator 数组有 4 个成员,每个成员都是一个
DescriptorAllocator 类型对象,这个类型对堆申请以及句柄申请做了一个池的封装
跟到 Allocate 里看一下
D3D12_CPU_DESCRIPTOR_HANDLE DescriptorAllocator::Allocate( uint32_t Count )
{
if (m_CurrentHeap == nullptr || m_RemainingFreeHandles < Count)
{
m_CurrentHeap = RequestNewHeap(m_Type);
m_CurrentHandle = m_CurrentHeap->GetCPUDescriptorHandleForHeapStart();
m_RemainingFreeHandles = sm_NumDescriptorsPerHeap;
if (m_DescriptorSize == 0)
m_DescriptorSize = Graphics::g_Device->GetDescriptorHandleIncrementSize(m_Type);
}
D3D12_CPU_DESCRIPTOR_HANDLE ret = m_CurrentHandle;
m_CurrentHandle.ptr += Count * m_DescriptorSize;
m_RemainingFreeHandles -= Count;
return ret;
}
没有堆就创建一个堆
跟到 m_CurrentHeap = RequestNewHeap(m_Type);这里
参数 m_Type 就跟 g_DescriptorAllocator[Type]的 Type 一样就是 D3D12_DESCRIPTOR_HEAP_TYPE_RTV
ID3D12DescriptorHeap* DescriptorAllocator::RequestNewHeap(D3D12_DESCRIPTOR_HEAP_TYPE Type)
{
std::lock_guard LockGuard(sm_AllocationMutex);
D3D12_DESCRIPTOR_HEAP_DESC Desc;
Desc.Type = Type;
Desc.NumDescriptors = sm_NumDescriptorsPerHeap;//256个描述
Desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
Desc.NodeMask = 1;
Microsoft::WRL::ComPtr<ID3D12DescriptorHeap> pHeap;
ASSERT_SUCCEEDED(Graphics::g_Device->CreateDescriptorHeap(&Desc, MY_IID_PPV_ARGS(&pHeap)));
sm_DescriptorHeapPool.emplace_back(pHeap);
return pHeap.Get();
}
函数实现还带锁,那应该是提供给多线程使用的,这里也就是创建了一个堆描述对象,这个堆描述对象的 NumDescriptors 为 256(sm_NumDescriptorsPerHeap = 256)
最后还是通过
g_Device->CreateDescriptorHeap(&Desc, MY_IID_PPV_ARGS(&pHeap);
创建了一个描述堆。
返回了创建的 ID3D12DescriptorHeap 指针
m_CurrentHandle = m_CurrentHeap->GetCPUDescriptorHandleForHeapStart();
这个意思就是取得描述堆起始的句柄
if (m_DescriptorSize == 0)
m_DescriptorSize = Graphics::g_Device->GetDescriptorHandleIncrementSize(m_Type);
这个就是取得每 2 个相邻句柄的指针要偏移多少
D3D12_CPU_DESCRIPTOR_HANDLE ret = m_CurrentHandle;
m_CurrentHandle.ptr += Count * m_DescriptorSize;
m_RemainingFreeHandles -= Count;
return ret;
这个就是说 Count 参数,申请多少个句柄,用 ret 存储返回的句柄,把原句柄按照数量偏移到新位置去。返回 ret(保存着偏移前的位置的句柄)
返回到
m_RTVHandle = Graphics::AllocateDescriptor(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
接着
Graphics::g_Device->CreateRenderTargetView(m_pResource.Get(), nullptr, m_RTVHandle);
这句话意思类似:创建一个如 m_pResource(ID3D12Resource 指针)描述的资源对象,并把对象句柄存储在 m_RTVHandle 里,从此 m_RTVHandle 就真正指向了一个 RTV 对象
返回到堆栈上
for (uint32_t i = 0; i < SWAP_CHAIN_BUFFER_COUNT; ++i)
{
ComPtr DisplayPlane;
ASSERT_SUCCEEDED(s_SwapChain1->GetBuffer(i, MY_IID_PPV_ARGS(&DisplayPlane)));
g_DisplayPlane[i].CreateFromSwapChain(L"Primary SwapChain Buffer", DisplayPlane.Detach());
}
循环里每一次运行,就申请了一个 RTVHandler,然后创建了一个 RTV 对象放到这个 RTVHandler 中,这个循环一共运行了 3 次(SWAP_CHAIN_BUFFER_COUNT = 3)
往下
太多代码还未理解。
觉得 dx12 还是需要先把概念扔出来,而不是这里这样那里那样,有了整体的概念,应该好理解很多
这里有一部分描述
https://www.csdn.net/article/a/2016-01-04/15833516
代码里有些类似乎涉及到一些算法,搜索了些资料保存下来慢慢看
双调排序 Bitonic Sort
一些名词
环境光遮蔽
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于