欢迎光临散文网 会员登陆 & 注册

【知乎】DirectX 12 简明入门教程

2023-08-03 10:23 作者:ACFUN-AK  | 我要投稿


【译】DirectX 12 简明入门教程

RYHarrison

编程/摄影/翻译

原文链接:https://alain.xyz/blog/raw-directx12
作者:Alain Galvan

本文将介绍如何编写一个简单的 Hello Triangle DirectX 12 应用程序。DirectX 12 是最新的底层图形 API,用来编写 Windows 应用程序,支持光线追踪、计算、光栅化等特性。

Github仓库

下载范例

DirectX 12是微软专有计算机图形 API 的最新版本,用于 Windows 和 Xbox 平台。与 Vulkan、Apple Metal 和 WebGPU 等较新的图形 API 一样,DirectX 12旨在让开发者可以更接近 GPU 的底层架构,从而能开发出不再复杂、运行更快的驱动程序。这意味着应用程序开发者能直接创建管线状态,指定使用队列执行哪些命令,并能选择使用多线程来构建这些数据结构。

DirectX 专注于实时渲染,因此专为游戏开发人员计算机辅助设计(CAD)软件工程师而设计。作为计算机图形 API 的行业标准,几乎所有兼容的硬件都能对 DirectX 提供强大支持,使它成为商业项目的标准。

从 Marmoset Toolbag、Adobe PhotoShop、Unreal Engine 等三维或图像创作软件,再到像 Activision Blizzard 的 OverWatch 和 Call of Duty、Epic 的 Fortnite 的商业游戏、以及 Valve 的 Steam 上的绝大多数游戏等等,DirectX 这个图形 API 是最受欢迎并且无处不在,尽管它对某些平台不兼容。

当然,这并不是说 DirectX 在 3D 渲染之外的领域没有用途。Google Chrome 使用 DirectX 渲染 HTML 和 SVG 元素。Tensorflow 采用了 DirectX 12 后端为使用 DirectML 执行机器学习提供支持。采用计算管线(compute pipeline)的 GPGPU 计算可以执行各种工作负载,包括物理仿真、构造 BVH 等等。

DirectX 12 目前支持以下设备:

  • Windows 7 - 11(Windows 7 可以通过微软的 D3D12 On 7 部分支持DirectX 12)

  • ✖️ Xbox One - Xbox Series X/S

虽然 DirectX 12 的官方语言或许是 C 和 C++,但还有不少语言同样支持 DirectX 12:

  • C

  • C++

  • C#

  • Rust(借助 dx-sys)

我已经准备了一个Github仓库,其中包含入门所需的一切内容。我将用 C++ 介绍一个 Hello Triangle 应用程序,这个程序使用了光栅图形管线创建三角形并将它渲染到屏幕上。

安装

首先安装:

  • Git

  • CMake

  • 集成开发环境(IDE),比如 Visual Studio、XCode,或者是编译器,比如 GCC

然后在任何终端(如 VS Code 的集成终端)中,输入以下内容。

#   Clone 代码仓git clone https://github.com/alaingalvan/directx12-seed --recurse-submodules#   定位至文件夹cd directx12-seed#   如果你忘记了 `recurse-submodules` ,你始终可以运行:git submodule update --init#   创建一个 build 文件夹,并将你的项目文件存放在里面:#  ️ 在 Windows 上构建 Visual Studio 64位版本的解决方案cmake . -B build -A x64#   在 Mac OS 上构建 XCode 项目cmake . -B build -G Xcode#   在 Linux 上构建 .make 文件cmake . -B build#   适用在任何平台上进行构建cmake --build build

概述

DirectX 12 的开发文档建议使用ComPtr<T>来作为std::shared_ptr<T>的替代,这样做的好处是可以更好地调试,更容易初始化 DirectX 12 的数据结构。

无论你是否选择使用ComPtr<T>,使用 DirectX 12 渲染光栅图形的步骤与其他现代图形 API 非常相似:

  1. 初始化 API:创建IDXGIFactory 、IDXGIAdapterID3D12DeviceID3D12CommandQueueID3D12CommandAllocatorID3D12GraphicsCommandList 。

  2. 设置帧后台缓冲:为后台缓冲区创建IDXGISwapChainID3D12DescriptorHeap,为ID3D12Resource后台缓冲区渲染目标视图,创建ID3D12Fence以检测帧何时完成渲染。

  3. 初始化资源:创建三角形数据,例如ID3D12Resource顶点缓冲区、ID3D12Resource索引缓冲区等,创建ID3D12Fence以检测上传到 GPU 内存的时间。加载着色器ID3DBlob、常量缓冲区ID3D12Resource及其ID3D12DescriptorHeap,描述使用ID3D12RootSignature可以访问哪些资源,并构建ID3D12PipelineState

  4. 编码指令:ID3D12GraphicsCommandList编写你希望执行的管线指令,确保将ResourceBarrier放在适当的位置。

  5. 渲染:更新 GPU 常量缓冲区数据(Uniforms),使用ExecuteCommandLists将命令提交至ID3D12CommandQueuePresent交换链,然后等待下一帧。

  6. 销毁:使用Release()来销毁正在使用的数据结构,或者使用ComPtr<T>对数据结构解除分配。

下面我将对 Github 仓库中代码片段进行解释,省略了某些部分,并且成员变量 (mMemberVariable) 内联声明,不带前缀m,以便你可以更容易地分辨它们的类型,这里给出的示例代码可以独立运行。

创建窗口(Window Creation)

我们使用 CrossWindow (译者注:原文给出的链接失效了,可以从这个 github仓库 来访问)来创建支持跨平台的窗口。创建 Win32 窗口并对其进行更新,操作非常简单:

#include "CrossWindow/CrossWindow.h"#include "Renderer.h"#include <iostream>void xmain(int argc, const char** argv){    //  ️ 创建窗口    xwin::WindowDesc wdesc;    wdesc.title = "DirectX 12 Seed";    wdesc.name = "MainWindow";    wdesc.visible = true;    wdesc.width = 640;    wdesc.height = 640;    wdesc.fullscreen = false;    xwin::Window window;    xwin::EventQueue eventQueue;    if (!window.create(wdesc, eventQueue))    { return; };    //   创建一个渲染器    Renderer renderer(window);    //   引擎循环    bool isRunning = true;    while (isRunning)        {            bool shouldRender = true;            // ♻️ 更新事件队列            eventQueue.update();            //   事件迭代:            while (!eventQueue.empty())                {                    // 更新事件                    const xwin::Event& event = eventQueue.front();                    //   响应窗口改变大小:                    if (event.type == xwin::EventType::Resize)                    {                        const xwin::ResizeData data = event.data.resize;                        renderer.resize(data.width, data.height);                        shouldRender = false;                    }                    // ❌ 响应窗口关闭:                    if (event.type == xwin::EventType::Close)                    {                        window.close();                        shouldRender = false;                        isRunning = false;                    }                    eventQueue.pop();                }            // ✨ 更新可视化部分            if (shouldRender)            {                renderer.render();            }        }}

如果你想替代 CrossWindow,可以使用其它库,比如 GLFW,SFML,SDL,QT,或者直接调用 Win32 或 UWP API 接口。

初始化 API Initialize API)

工厂(Factory)

工厂是 DirectX 12 API 的入口,它允许你查找那些支持 DirectX 12 命令的适配器(译者注:可以理解为显卡)。

你还可以在工厂附近创建一个调试控制器(Debug Controller),它可以启用 API 使用情况验证。这只能在 debug 模式中使用。

  • 参考:IDXGIFactory7

//   声明 DirectX 12 句柄(Handle)IDXGIFactory4* factory;ID3D12Debug1* debugController;//   创建 FactoryUINT dxgiFactoryFlags = 0;#if defined(_DEBUG)//   创建一个 Debug Controller 来追踪错误ID3D12Debug* dc;ThrowIfFailed(D3D12GetDebugInterface(IID_PPV_ARGS(&dc)));ThrowIfFailed(dc->QueryInterface(IID_PPV_ARGS(&debugController)));debugController->EnableDebugLayer();debugController->SetEnableGPUBasedValidation(true);dxgiFactoryFlags |= DXGI_CREATE_FACTORY_DEBUG;dc->Release();dc = nullptr;#endifHRESULT result = CreateDXGIFactory2(dxgiFactoryFlags, IID_PPV_ARGS(&factory));

适配器(Adapter)

Adapter 提供了有关 DirectX 设备物理属性的信息。你可以查询当前 GPU 的名称、制造商、显存容量以及其它更多信息。

有两种类型的适配器:软件适配器和硬件适配器。微软 Windows 系统总是会包含一个基于软件层面的DirectX 实现,让你可以在没有专用硬件(如独立或集成 GPU )的情况下使用 API 。

  • 参考:IDXGIAdapter4

//   声明句柄IDXGIAdapter1* adapter;//   创建 Adapterfor (UINT adapterIndex = 0;     DXGI_ERROR_NOT_FOUND != factory->EnumAdapters1(adapterIndex, &adapter);     ++adapterIndex){    DXGI_ADAPTER_DESC1 desc;    adapter->GetDesc1(&desc);    // ❌ 不要选用 Basic Render Driver adapter.    if (desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE)    {        continue;    }    // ✔️ 检查适配器是否支持 Direct3D 12, 如果支持就选择它来供程序运行并返回    if (SUCCEEDED(D3D12CreateDevice(adapter, D3D_FEATURE_LEVEL_12_0,                                    _uuidof(ID3D12Device), nullptr)))    {        break;    }    // ❌ 否则的话,不再使用这个迭代器 adapter,所以要进行释放    adapter->Release();}

设备(Device)

设备是 DirectX 12 API 的主要入口点,可用来访问 API 的内部。这是访问重要数据结构和函数(如管线、着色器 blob、渲染状态、资源屏障等)的关键。

  • 参考:IDXGIDevice4

//   声明句柄ID3D12Device* device;//   创建 DeviceThrowIfFailed(D3D12CreateDevice(adapter, D3D_FEATURE_LEVEL_12_0,                                IID_PPV_ARGS(&device)));

调试 DirectX 创建的数据结构可能非常困难。而一个 Debug Device 对象允许你使用 DirectX 12 的调试模式。这样,你将能够防止数据泄露,或者能验证程序是否正确创建或使用 API。

  • 参考:ID3D12DebugDevice

//   声明句柄ID3D12DebugDevice* debugDevice;#if defined(_DEBUG)//   获取 debug deviceThrowIfFailed(device->QueryInterface(&debugDevice));#endif

命令队列(Command Queue)

命令队列让你可以一次性提交多组 draw call(称为命令列表 command lists)来按顺序来执行命令,从而让 GPU 保持充分工作并优化执行速度。

  • 参考:ID3D12CommandQueue

//   声明句柄ID3D12CommandQueue* commandQueue;//   创建 Command QueueD3D12_COMMAND_QUEUE_DESC queueDesc = {};queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;ThrowIfFailed(device->CreateCommandQueue(&queueDesc,                                         IID_PPV_ARGS(&commandQueue)));

命令分配器(Command Allocator)

命令分配器让你可以创建命令列表,你可以在其中定义希望 GPU 执行的功能。

  • 参考:ID3D12CommandAllocator

//   声明句柄ID3D12CommandAllocator* commandAllocator;//   创建命令分配器ThrowIfFailed(device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT,                                             IID_PPV_ARGS(&commandAllocator)));

同步(Synchronization)

DirectX 12 具有大量同步图元(synchronization primitives),可以告知驱动程序如何使用资源、GPU 何时完成任务等。

围栏(Fence)能让程序知道 GPU 在什么时候执行了哪些特定任务,无论是上传了哪些资源到 GPU 专用内存,还是程序什么时候完成向屏幕的提交(present),都能获取到这些信息。

  • 参考:ID3D12Fence1

//   声明句柄UINT frameIndex;HANDLE fenceEvent;ID3D12Fence* fence;UINT64 fenceValue;//   创建 fenceThrowIfFailed(device->CreateFence(0, D3D12_FENCE_FLAG_NONE,                                  IID_PPV_ARGS(&fence)));

屏障(Barrier)能让驱动程序知道如何在即将提交的命令中使用资源。比如说,程序正在写入纹理,并且想要将这个纹理复制到另一个纹理(例如交换链的渲染附件),这会很有用。

//   声明句柄ID3D12GraphicsCommandList* commandList;//   创建 BarrierD3D12_RESOURCE_BARRIER barrier = {};barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;barrier.Transition.pResource = texResource;barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_COPY_SOURCE;barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_UNORDERED_ACCESS;barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;commandList->ResourceBarrier(1, &barrier);

交换链(Swap Chain)

交换链能处理交换和分配后台缓冲区,用来显示正在渲染到给定窗口的内容。

//   声明数据变量unsigned width = 640;unsigned height = 640;//   声明句柄static const UINT backbufferCount = 2;UINT currentBuffer;ID3D12DescriptorHeap* renderTargetViewHeap;ID3D12Resource* renderTargets[backbufferCount];UINT rtvDescriptorSize;// ⛓️ 声明交换链IDXGISwapChain3* swapchain;D3D12_VIEWPORT viewport;D3D12_RECT surfaceSize;surfaceSize.left = 0;surfaceSize.top = 0;surfaceSize.right = static_cast<LONG>(width);surfaceSize.bottom = static_cast<LONG>(height);viewport.TopLeftX = 0.0f;viewport.TopLeftY = 0.0f;viewport.Width = static_cast<float>(width);viewport.Height = static_cast<float>(height);viewport.MinDepth = .1f;viewport.MaxDepth = 1000.f;if (swapchain != nullptr){    // 通过交换链创建渲染目标附件(Render Target Attachments)    swapchain->ResizeBuffers(backbufferCount, width, height,                             DXGI_FORMAT_R8G8B8A8_UNORM, 0);}else{    // ⛓️ 创建交换链    DXGI_SWAP_CHAIN_DESC1 swapchainDesc = {};    swapchainDesc.BufferCount = backbufferCount;    swapchainDesc.Width = width;    swapchainDesc.Height = height;    swapchainDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;    swapchainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;    swapchainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;    swapchainDesc.SampleDesc.Count = 1;    IDXGISwapChain1* newSwapchain =        xgfx::createSwapchain(window, factory, commandQueue, &swapchainDesc);    HRESULT swapchainSupport = swapchain->QueryInterface(        __uuidof(IDXGISwapChain3), (void**)&newSwapchain);    if (SUCCEEDED(swapchainSupport))    {        swapchain = (IDXGISwapChain3*)newSwapchain;    }}frameIndex = swapchain->GetCurrentBackBufferIndex();// 描述并创建渲染目标视图(render target view, RTV) 描述符堆D3D12_DESCRIPTOR_HEAP_DESC rtvHeapDesc = {};rtvHeapDesc.NumDescriptors = backbufferCount;rtvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;rtvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;ThrowIfFailed(device->CreateDescriptorHeap(    &rtvHeapDesc, IID_PPV_ARGS(&renderTargetViewHeap)));rtvDescriptorSize =    device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);//  ️ 创建帧资源D3D12_CPU_DESCRIPTOR_HANDLE    rtvHandle(renderTargetViewHeap->GetCPUDescriptorHandleForHeapStart());// 为每帧创建RTVfor (UINT n = 0; n < backbufferCount; n++){    ThrowIfFailed(swapchain->GetBuffer(n, IID_PPV_ARGS(&renderTargets[n])));    device->CreateRenderTargetView(renderTargets[n], nullptr, rtvHandle);    rtvHandle.ptr += (1 * rtvDescriptorSize);}

初始化资源(Initialize Resources)

描述符堆(Descriptor Heaps)

描述符堆是用来处理内存分配的,这些内存存储着色器引用的对象描述。

  • 参考:ID3D12DescriptorHeap

ID3D12DescriptorHeap* renderTargetViewHeap;D3D12_DESCRIPTOR_HEAP_DESC rtvHeapDesc = {};rtvHeapDesc.NumDescriptors = backbufferCount;rtvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;rtvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;ThrowIfFailed(device->CreateDescriptorHeap(    &rtvHeapDesc, IID_PPV_ARGS(&renderTargetViewHeap)));

根签名(Root Signature)

根签名定义了着色器可以访问资源类型的对象,包括常量缓冲区、结构化缓冲区、采样器、纹理、结构化缓冲区,等等。

  • 参考:ID3D12RootSignature

//   声明句柄ID3D12RootSignature* rootSignature;//   判断是否能得到1.1版本的根签名:D3D12_FEATURE_DATA_ROOT_SIGNATURE featureData = {};featureData.HighestVersion = D3D_ROOT_SIGNATURE_VERSION_1_1;if (FAILED(device->CheckFeatureSupport(D3D12_FEATURE_ROOT_SIGNATURE,                                       &featureData, sizeof(featureData)))){    featureData.HighestVersion = D3D_ROOT_SIGNATURE_VERSION_1_0;}//   私有的GPU资源D3D12_DESCRIPTOR_RANGE1 ranges[1];ranges[0].BaseShaderRegister = 0;ranges[0].RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_CBV;ranges[0].NumDescriptors = 1;ranges[0].RegisterSpace = 0;ranges[0].OffsetInDescriptorsFromTableStart = 0;ranges[0].Flags = D3D12_DESCRIPTOR_RANGE_FLAG_NONE;// ️ GPU资源组D3D12_ROOT_PARAMETER1 rootParameters[1];rootParameters[0].ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE;rootParameters[0].ShaderVisibility = D3D12_SHADER_VISIBILITY_VERTEX;rootParameters[0].DescriptorTable.NumDescriptorRanges = 1;rootParameters[0].DescriptorTable.pDescriptorRanges = ranges;//    所有布局D3D12_VERSIONED_ROOT_SIGNATURE_DESC rootSignatureDesc;rootSignatureDesc.Version = D3D_ROOT_SIGNATURE_VERSION_1_1;rootSignatureDesc.Desc_1_1.Flags =    D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT;rootSignatureDesc.Desc_1_1.NumParameters = 1;rootSignatureDesc.Desc_1_1.pParameters = rootParameters;rootSignatureDesc.Desc_1_1.NumStaticSamplers = 0;rootSignatureDesc.Desc_1_1.pStaticSamplers = nullptr;ID3DBlob* signature;ID3DBlob* error;try{    //   创建根签名    ThrowIfFailed(D3D12SerializeVersionedRootSignature(&rootSignatureDesc,                                                       &signature, &error));    ThrowIfFailed(device->CreateRootSignature(0, signature->GetBufferPointer(),                                              signature->GetBufferSize(),                                              IID_PPV_ARGS(&rootSignature)));    rootSignature->SetName(L"Hello Triangle Root Signature");}catch (std::exception e){    const char* errStr = (const char*)error->GetBufferPointer();    std::cout << errStr;    error->Release();    error = nullptr;}if (signature){    signature->Release();    signature = nullptr;}

虽然程序运行起来没有问题,但如果使用无绑定资源(bindless resources),那么开发起来要容易得多,Matt Pettineo(@MyNameIsMJP)在《Ray Tracing Gems II》中写过关于这方面的内容。

堆(Heaps)

堆是 GPU 显存中的对象。你可以使用堆将顶点缓冲区或纹理等资源上传到 GPU 显存中。

  • 参考:ID3D12Resource

//   上传://   声明句柄ID3D12Resource* uploadBuffer;std::vector<unsigned char> sourceData;D3D12_HEAP_PROPERTIES uploadHeapProps = {D3D12_HEAP_TYPE_UPLOAD,                                         D3D12_CPU_PAGE_PROPERTY_UNKNOWN,                                         D3D12_MEMORY_POOL_UNKNOWN, 1u, 1u};D3D12_RESOURCE_DESC uploadBufferDesc = {D3D12_RESOURCE_DIMENSION_BUFFER,                                        65536ull,                                        65536ull,                                        1u,                                        1,                                        1,                                        DXGI_FORMAT_UNKNOWN,                                        {1u, 0u},                                        D3D12_TEXTURE_LAYOUT_ROW_MAJOR,                                        D3D12_RESOURCE_FLAG_NONE};result = device->CreateCommittedResource(    &uploadHeapProps, D3D12_HEAP_FLAG_NONE, &uploadBufferDesc,    D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, __uuidof(ID3D12Resource),    ((void**)&uploadBuffer));uint8_t* data = nullptr;D3D12_RANGE range{0, SIZE_T(chunkSize)};auto hr = spStaging -> Map(0, &range, reinterpret_cast<void**>(&data));if (FAILED(hr)){    std::cout << "Could not map resource";}// 拷贝数据if (resourceDesc.Dimension == D3D12_RESOURCE_DIMENSION_BUFFER){    memcpy(data, sourceData.data(), sourceData.size());}//   回读://   声明句柄ID3D12Resource* readbackBuffer;D3D12_HEAP_PROPERTIES heapPropsRead = {D3D12_HEAP_TYPE_READBACK,                                       D3D12_CPU_PAGE_PROPERTY_UNKNOWN,                                       D3D12_MEMORY_POOL_UNKNOWN, 1u, 1u};D3D12_RESOURCE_DESC resourceDescDimBuffer = {    D3D12_RESOURCE_DIMENSION_BUFFER,    D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT,    2725888ull,    1u,    1,    1,    DXGI_FORMAT_UNKNOWN,    {1u, 0u},    D3D12_TEXTURE_LAYOUT_ROW_MAJOR,    D3D12_RESOURCE_FLAG_DENY_SHADER_RESOURCE};result = device->CreateCommittedResource(    &heapPropsRead, D3D12_HEAP_FLAG_NONE, &resourceDescDimBuffer,    D3D12_RESOURCE_STATE_COPY_DEST, nullptr, __uuidof(ID3D12Resource),    ((void**)&readbackBuffer));

通过创建自己的堆,你可以更加精细地管理内存。不过,可管理的堆可能很少,因此你可以改用内存分配库。

顶点缓冲区(Vertex Buffer)

顶点缓冲区将每个顶点信息作为属性存储在顶点着色器中。所有缓冲区都是 DirectX 12 中的ID3D12Resource对象,无论是顶点缓冲区、索引缓冲区、常量缓冲区等。

  • 参考:D3D12_VERTEX_BUFFER_VIEW

//   声明数据结构struct Vertex{    float position[3];    float color[3];};Vertex vertexBufferData[3] = {{{1.0f, -1.0f, 0.0f}, {1.0f, 0.0f, 0.0f}},                              {{-1.0f, -1.0f, 0.0f}, {0.0f, 1.0f, 0.0f}},                              {{0.0f, 1.0f, 0.0f}, {0.0f, 0.0f, 1.0f}}};//   声明句柄ID3D12Resource* vertexBuffer;D3D12_VERTEX_BUFFER_VIEW vertexBufferView;const UINT vertexBufferSize = sizeof(vertexBufferData);D3D12_HEAP_PROPERTIES heapProps;heapProps.Type = D3D12_HEAP_TYPE_UPLOAD;heapProps.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN;heapProps.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN;heapProps.CreationNodeMask = 1;heapProps.VisibleNodeMask = 1;D3D12_RESOURCE_DESC vertexBufferResourceDesc;vertexBufferResourceDesc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;vertexBufferResourceDesc.Alignment = 0;vertexBufferResourceDesc.Width = vertexBufferSize;vertexBufferResourceDesc.Height = 1;vertexBufferResourceDesc.DepthOrArraySize = 1;vertexBufferResourceDesc.MipLevels = 1;vertexBufferResourceDesc.Format = DXGI_FORMAT_UNKNOWN;vertexBufferResourceDesc.SampleDesc.Count = 1;vertexBufferResourceDesc.SampleDesc.Quality = 0;vertexBufferResourceDesc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;vertexBufferResourceDesc.Flags = D3D12_RESOURCE_FLAG_NONE;ThrowIfFailed(device->CreateCommittedResource(    &heapProps, D3D12_HEAP_FLAG_NONE, &vertexBufferResourceDesc,    D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, IID_PPV_ARGS(&vertexBuffer)));//   向顶点缓冲区拷贝三角形数据UINT8* pVertexDataBegin;//   不会在 CPU 中读取这些资源D3D12_RANGE readRange;readRange.Begin = 0;readRange.End = 0;ThrowIfFailed(vertexBuffer->Map(0, &readRange,                                reinterpret_cast<void**>(&pVertexDataBegin)));memcpy(pVertexDataBegin, vertexBufferData, sizeof(vertexBufferData));vertexBuffer->Unmap(0, nullptr);//   初始化顶点缓冲视图.vertexBufferView.BufferLocation = vertexBuffer->GetGPUVirtualAddress();vertexBufferView.StrideInBytes = sizeof(Vertex);vertexBufferView.SizeInBytes = vertexBufferSize;

索引缓冲区(Index Buffer)

索引缓冲区包含要绘制的每个三角形、线、点的各个索引。

  • 参考:D3D12_INDEX_BUFFER_VIEW

//   声明数组uint32_t indexBufferData[3] = {0, 1, 2};//   声明句柄ID3D12Resource* indexBuffer;D3D12_INDEX_BUFFER_VIEW indexBufferView;const UINT indexBufferSize = sizeof(indexBufferData);D3D12_HEAP_PROPERTIES heapProps;heapProps.Type = D3D12_HEAP_TYPE_UPLOAD;heapProps.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN;heapProps.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN;heapProps.CreationNodeMask = 1;heapProps.VisibleNodeMask = 1;D3D12_RESOURCE_DESC vertexBufferResourceDesc;vertexBufferResourceDesc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;vertexBufferResourceDesc.Alignment = 0;vertexBufferResourceDesc.Width = indexBufferSize;vertexBufferResourceDesc.Height = 1;vertexBufferResourceDesc.DepthOrArraySize = 1;vertexBufferResourceDesc.MipLevels = 1;vertexBufferResourceDesc.Format = DXGI_FORMAT_UNKNOWN;vertexBufferResourceDesc.SampleDesc.Count = 1;vertexBufferResourceDesc.SampleDesc.Quality = 0;vertexBufferResourceDesc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;vertexBufferResourceDesc.Flags = D3D12_RESOURCE_FLAG_NONE;ThrowIfFailed(device->CreateCommittedResource(    &heapProps, D3D12_HEAP_FLAG_NONE, &vertexBufferResourceDesc,    D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, IID_PPV_ARGS(&indexBuffer)));//   向 DirectX 12 显存拷贝数据:UINT8* pVertexDataBegin;//   不会在 CPU 中读取这些资源D3D12_RANGE readRange;readRange.Begin = 0;readRange.End = 0;ThrowIfFailed(indexBuffer->Map(0, &readRange,                               reinterpret_cast<void**>(&pVertexDataBegin)));memcpy(pVertexDataBegin, indexBufferData, sizeof(indexBufferData));indexBuffer->Unmap(0, nullptr);//   初始化索引缓冲区视图indexBufferView.BufferLocation = indexBuffer->GetGPUVirtualAddress();indexBufferView.Format = DXGI_FORMAT_R32_UINT;indexBufferView.SizeInBytes = indexBufferSize;

常量缓冲区(Constant Buffer)

常量缓冲区描述了在程序绘制时将发送到着色器阶段的数据。通常,你在这里存放着模型视图投影矩阵或任何特定的变量数据,如颜色。

//   声明数据结构struct{    glm::mat4 projectionMatrix;    glm::mat4 modelMatrix;    glm::mat4 viewMatrix;} cbVS;//   声明句柄ID3D12Resource* constantBuffer;ID3D12DescriptorHeap* constantBufferHeap;UINT8* mappedConstantBuffer;//   创建常量缓冲区D3D12_HEAP_PROPERTIES heapProps;heapProps.Type = D3D12_HEAP_TYPE_UPLOAD;heapProps.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN;heapProps.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN;heapProps.CreationNodeMask = 1;heapProps.VisibleNodeMask = 1;D3D12_DESCRIPTOR_HEAP_DESC heapDesc = {};heapDesc.NumDescriptors = 1;heapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;heapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;ThrowIfFailed(device->CreateDescriptorHeap(&heapDesc,                                           IID_PPV_ARGS(&constantBufferHeap)));D3D12_RESOURCE_DESC cbResourceDesc;cbResourceDesc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;cbResourceDesc.Alignment = 0;cbResourceDesc.Width = (sizeof(cbVS) + 255) & ~255;cbResourceDesc.Height = 1;cbResourceDesc.DepthOrArraySize = 1;cbResourceDesc.MipLevels = 1;cbResourceDesc.Format = DXGI_FORMAT_UNKNOWN;cbResourceDesc.SampleDesc.Count = 1;cbResourceDesc.SampleDesc.Quality = 0;cbResourceDesc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;cbResourceDesc.Flags = D3D12_RESOURCE_FLAG_NONE;ThrowIfFailed(device->CreateCommittedResource(    &heapProps, D3D12_HEAP_FLAG_NONE, &cbResourceDesc,    D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, IID_PPV_ARGS(&constantBuffer)));constantBufferHeap->SetName(L"Constant Buffer Upload Resource Heap");//   创建常量缓冲区视图(CBV)D3D12_CONSTANT_BUFFER_VIEW_DESC cbvDesc = {};cbvDesc.BufferLocation = constantBuffer->GetGPUVirtualAddress();cbvDesc.SizeInBytes =    (sizeof(cbVS) + 255) & ~255; // CB size is required to be 256-byte aligned.D3D12_CPU_DESCRIPTOR_HANDLE    cbvHandle(constantBufferHeap->GetCPUDescriptorHandleForHeapStart());cbvHandle.ptr = cbvHandle.ptr + device->GetDescriptorHandleIncrementSize(                                    D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV) *                                    0;device->CreateConstantBufferView(&cbvDesc, cbvHandle);//   不会在 CPU 中读取这些资源D3D12_RANGE readRange;readRange.Begin = 0;readRange.End = 0;ThrowIfFailed(constantBuffer->Map(    0, &readRange, reinterpret_cast<void**>(&mappedConstantBuffer)));memcpy(mappedConstantBuffer, &cbVS, sizeof(cbVS));constantBuffer->Unmap(0, &readRange);

顶点着色器(Vertex Shader)

顶点着色器是逐顶点执行的,它非常适合对给定对象进行转换处理,比如根据混合形状执行每个顶点动画、GPU 蒙皮,等等。

cbuffer cb : register(b0){    row_major float4x4 projectionMatrix : packoffset(c0);    row_major float4x4 modelMatrix : packoffset(c4);    row_major float4x4 viewMatrix : packoffset(c8);};struct VertexInput{    float3 inPos : POSITION;    float3 inColor : COLOR;};struct VertexOutput{    float3 color : COLOR;    float4 position : SV_Position;};VertexOutput main(VertexInput vertexInput){    float3 inColor = vertexInput.inColor;    float3 inPos = vertexInput.inPos;    float3 outColor = inColor;    float4 position = mul(float4(inPos, 1.0f), mul(modelMatrix, mul(viewMatrix, projectionMatrix)));    VertexOutput output;    output.position = position;    output.color = outColor;    return output;}

你可以使用传统的 DirectX 着色器编译器(已包含在 DirectX 11/12 API 中)来编译 shader ,但最好使用较新的官方编译器。

dxc.exe -T lib_6_3 -Fo assets/triangle.vert.dxil assets/triangle.vert.hlsl

然后,你就能以为二进制文件形式加载着色器:

inline std::vector<char> readFile(const std::string& filename){    std::ifstream file(filename, std::ios::ate | std::ios::binary);    bool exists = (bool)file;    if (!exists || !file.is_open())    {        throw std::runtime_error("failed to open file!");    }    size_t fileSize = (size_t)file.tellg();    std::vector<char> buffer(fileSize);    file.seekg(0);    file.read(buffer.data(), fileSize);    file.close();    return buffer;};//   声明句柄D3D12_SHADER_BYTECODE vsBytecode;std::string compiledPath;std::vector<char> vsBytecodeData = readFile(compCompiledPath);vsBytecode.pShaderBytecode = vsBytecodeData.data();vsBytecode.BytecodeLength = vsBytecodeData.size();

像素着色器(Pixel Shader)

像素着色器是按输出的每个像素来执行的,包括与这个像素坐标对应的其他附件。

struct PixelInput{    float3 color : COLOR;};struct PixelOutput{    float4 attachment0 : SV_Target0;};PixelOutput main(PixelInput pixelInput){    float3 inColor = pixelInput.color;    PixelOutput output;    output.attachment0 = float4(inColor, 1.0f);    return output;}

管线状态(Pipeline State)

管线状态描述了执行给定的光栅绘制指令所用到的全部内容。

  • 参考:ID3D12GraphicsCommandList5

//   声明句柄ID3D12PipelineState* pipelineState;// ⚗️ 定义图形管线D3D12_GRAPHICS_PIPELINE_STATE_DESC psoDesc = {};//   输入装配布局D3D12_INPUT_ELEMENT_DESC inputElementDescs[] = {    {"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0,     D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0},    {"COLOR", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12,     D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0}};psoDesc.InputLayout = {inputElementDescs, _countof(inputElementDescs)};//   资源psoDesc.pRootSignature = rootSignature;//   顶点着色器D3D12_SHADER_BYTECODE vsBytecode;vsBytecode.pShaderBytecode = vertexShaderBlob->GetBufferPointer();vsBytecode.BytecodeLength = vertexShaderBlob->GetBufferSize();psoDesc.VS = vsBytecode;//  ️ 像素着色器D3D12_SHADER_BYTECODE psBytecode;psBytecode.pShaderBytecode = pixelShaderBlob->GetBufferPointer();psBytecode.BytecodeLength = pixelShaderBlob->GetBufferSize();psoDesc.PS = psBytecode;//   光栅化D3D12_RASTERIZER_DESC rasterDesc;rasterDesc.FillMode = D3D12_FILL_MODE_SOLID;rasterDesc.CullMode = D3D12_CULL_MODE_NONE;rasterDesc.FrontCounterClockwise = FALSE;rasterDesc.DepthBias = D3D12_DEFAULT_DEPTH_BIAS;rasterDesc.DepthBiasClamp = D3D12_DEFAULT_DEPTH_BIAS_CLAMP;rasterDesc.SlopeScaledDepthBias = D3D12_DEFAULT_SLOPE_SCALED_DEPTH_BIAS;rasterDesc.DepthClipEnable = TRUE;rasterDesc.MultisampleEnable = FALSE;rasterDesc.AntialiasedLineEnable = FALSE;rasterDesc.ForcedSampleCount = 0;rasterDesc.ConservativeRaster = D3D12_CONSERVATIVE_RASTERIZATION_MODE_OFF;psoDesc.RasterizerState = rasterDesc;psoDesc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;//   颜色/混合D3D12_BLEND_DESC blendDesc;blendDesc.AlphaToCoverageEnable = FALSE;blendDesc.IndependentBlendEnable = FALSE;const D3D12_RENDER_TARGET_BLEND_DESC defaultRenderTargetBlendDesc = {    FALSE,    FALSE,    D3D12_BLEND_ONE,    D3D12_BLEND_ZERO,    D3D12_BLEND_OP_ADD,    D3D12_BLEND_ONE,    D3D12_BLEND_ZERO,    D3D12_BLEND_OP_ADD,    D3D12_LOGIC_OP_NOOP,    D3D12_COLOR_WRITE_ENABLE_ALL,};for (UINT i = 0; i < D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT; ++i)    blendDesc.RenderTarget[i] = defaultRenderTargetBlendDesc;psoDesc.BlendState = blendDesc;//   深度/缓冲状态psoDesc.DepthStencilState.DepthEnable = FALSE;psoDesc.DepthStencilState.StencilEnable = FALSE;psoDesc.SampleMask = UINT_MAX;//  ️ 输出psoDesc.NumRenderTargets = 1;psoDesc.RTVFormats[0] = DXGI_FORMAT_R8G8B8A8_UNORM;psoDesc.SampleDesc.Count = 1;//   创建光栅管线状态try{    ThrowIfFailed(device->CreateGraphicsPipelineState(        &psoDesc, IID_PPV_ARGS(&pipelineState)));}catch (std::exception e){    std::cout << "Failed to create Graphics Pipeline!";}

编码指令(Encoding Commands)

为了执行 draw call,你需要一个编写命令的地方。命令列表(Command List)可以对 GPU 要执行的许多命令进行编码,包括设置屏障(barrier)、设置根签名等等。

  • 参考:ID3D12GraphicsCommandList5

//   声明句柄ID3D12CommandAllocator* commandAllocator;ID3D12PipelineState* initialPipelineState;ID3D12GraphicsCommandList* commandList;//   创建命令列表ThrowIfFailed(device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT,                                        commandAllocator, initialPipelineState,                                        IID_PPV_ARGS(&commandList)));

然后,对这些命令进行编码并提交:

//   重置命令列表并添加新的命令ThrowIfFailed(commandAllocator->Reset());//  ️ 开始调用光栅图形管线ThrowIfFailed(commandList->Reset(commandAllocator, pipelineState));//   设置资源commandList->SetGraphicsRootSignature(rootSignature);ID3D12DescriptorHeap* pDescriptorHeaps[] = {constantBufferHeap};commandList->SetDescriptorHeaps(_countof(pDescriptorHeaps), pDescriptorHeaps);D3D12_GPU_DESCRIPTOR_HANDLE    cbvHandle(constantBufferHeap->GetGPUDescriptorHandleForHeapStart());commandList->SetGraphicsRootDescriptorTable(0, cbvHandle);//  ️ 指派back buffer,用作render targetD3D12_RESOURCE_BARRIER renderTargetBarrier;renderTargetBarrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;renderTargetBarrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;renderTargetBarrier.Transition.pResource = renderTargets[frameIndex];renderTargetBarrier.Transition.StateBefore = D3D12_RESOURCE_STATE_PRESENT;renderTargetBarrier.Transition.StateAfter = D3D12_RESOURCE_STATE_RENDER_TARGET;renderTargetBarrier.Transition.Subresource =    D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;commandList->ResourceBarrier(1, &renderTargetBarrier);D3D12_CPU_DESCRIPTOR_HANDLE    rtvHandle(rtvHeap->GetCPUDescriptorHandleForHeapStart());rtvHandle.ptr = rtvHandle.ptr + (frameIndex * rtvDescriptorSize);commandList->OMSetRenderTargets(1, &rtvHandle, FALSE, nullptr);//   记录光栅命令.const float clearColor[] = {0.2f, 0.2f, 0.2f, 1.0f};commandList->RSSetViewports(1, &viewport);commandList->RSSetScissorRects(1, &surfaceSize);commandList->ClearRenderTargetView(rtvHandle, clearColor, 0, nullptr);commandList->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);commandList->IASetVertexBuffers(0, 1, &vertexBufferView);commandList->IASetIndexBuffer(&indexBufferView);commandList->DrawIndexedInstanced(3, 1, 0, 0, 0);//  ️ 指派back buffer 随即提交(present)D3D12_RESOURCE_BARRIER presentBarrier;presentBarrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;presentBarrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;presentBarrier.Transition.pResource = renderTargets[frameIndex];presentBarrier.Transition.StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET;presentBarrier.Transition.StateAfter = D3D12_RESOURCE_STATE_PRESENT;presentBarrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;commandList->ResourceBarrier(1, &presentBarrier);ThrowIfFailed(commandList->Close());

渲染(Render)

在 DirectX 12 中渲染非常简单:你需要在刷新时改动常量缓冲区数据、提交要执行的命令列表、提交交换链来更新 Win32 或 UWP 窗口,同时向程序发出已完成提交的信号。

//   声明句柄std::chrono::time_point<std::chrono::steady_clock> tStart, tEnd;float elapsedTime = 0.0f;void render(){    // ⌚ 将帧率锁定至60fps    tEnd = std::chrono::high_resolution_clock::now();    float time =        std::chrono::duration<float, std::milli>(tEnd - tStart).count();    if (time < (1000.0f / 60.0f))    {        return;    }    tStart = std::chrono::high_resolution_clock::now();    //  ️ 更新 Uniforms    elapsedTime += 0.001f * time;    elapsedTime = fmodf(elapsedTime, 6.283185307179586f);    cbVS.modelMatrix = Matrix4::rotationY(elapsedTime);    D3D12_RANGE readRange;    readRange.Begin = 0;    readRange.End = 0;    ThrowIfFailed(constantBuffer->Map(        0, &readRange, reinterpret_cast<void**>(&mappedConstantBuffer)));    memcpy(mappedConstantBuffer, &cbVS, sizeof(cbVS));    constantBuffer->Unmap(0, &readRange);    setupCommands();    ID3D12CommandList* ppCommandLists[] = {commandList};    commandQueue->ExecuteCommandLists(_countof(ppCommandLists), ppCommandLists);    //   提交,然后等待GPU执行    swapchain->Present(1, 0);    const UINT64 fence = fenceValue;    ThrowIfFailed(commandQueue->Signal(fence, fence));    fenceValue++;    if (fence->GetCompletedValue() < fence)    {        ThrowIfFailed(fence->SetEventOnCompletion(fence, fenceEvent));        WaitForSingleObject(fenceEvent, INFINITE);    }    frameIndex = swapchain->GetCurrentBackBufferIndex();}

销毁句柄(Destroy Handles)

如果你使用的是ComPtr<T>数据结构,那么就像使用共享指针一样,你无需担心什么时候销毁已创建的句柄。如果没使用ComPtr<T>,那你可以调用内置在每个 DirectX 数据结构中的Release()函数进行销毁。

结论

DirectX 12 是一个功能丰富、性能强大的计算机图形 API,非常适合商业项目。它在设计上遵循现代图形 API,同时它也是硬件驱动程序工程师和商业项目工程师主要在维护的 API。这篇文章回顾了基于光栅的绘图,但没有涉及到更多方面,而它们同样值得探讨,例如:

  • DirectML:硬件加速机器学习模型。

  • DirectX Raytracing:硬件加速光线追踪和场景遍历。

  • 计算着色器(Compute Shader):基于 GPGPU 执行任意任务,例如图像处理、物理等。

  • 网格着色器(Mesh Shader):一种替代传统基于光栅渲染技术构建基元的方法。

  • 可变速率着色(Variable Rate Shading,VRS):对给定命令集的着色速率进行更精细的控制。

我还写了更多关于 DirectX 的文章:

  • Raw DirectX 12 book 介绍了计算管线、光线追踪管线等。

  • Raw DirectX 11 blog post 回顾了早期的 DirectX 11,并且可以把这篇文章作为学习 DirectX 很不错的入门材料,因为这里面的许多概念都沿用到了 DirectX 12。

更多资源

你一定不要错过下面这些文章、工具和项目:

文章

  • 在微软 DirectX 12文档中有一个页面,包含了初始化 DirectX 的所用到的全部数据结构;

  • 微软在这个链接里有关于 D3D12 和 11 的规范文档;

  • Jendrik Illner(@jendrikillner)写了一篇 DirectX 12 学习计划;

  • Braynzarsoft,一个 DirectX 教程社区;

  • Riko Ophorst 的一篇 DirectX 12 光线追踪论文;

  • Riccardo Loggini 的 D3D12 博客文章;

  • 英特尔关于 D3D12 博客文章;

  • NVIDIA 关于 D3D12 和相关主题的博客文章;

  • Diligent Graphics (@diligentengine) 写了篇关于 DirectX 12 的文章;

  • Jeremiah van Oosten的 DirectX 12 系列教程;

  • Alex Tardif (@longbool)的 A Gentle Introduction to D3D12;

范例

  • 微软官方的 DirectX 12 范例代码仓库;

  • 英特尔官方的 GitHub Organization Game Tech Dev;

  • NVIDIA 的官方 GitHub Organization GameWorks;

  • Matthäus G. Chajdas(@NIV_Anteru)发布的 HelloD3D12,它是 AMD 的 GPUOpen 库和 SDK 的一部分;

工具

  • 由 Adam Sawicki(@Reg__)开发的 D3D12 Memory Allocator ,他同样还是 Vulkan Memory Allocator 的开发者;

  • Tim Jones(@tim_jones) 发布了一个VS Code 插件 HLSL Tools,可让你更轻松地对编写 shader;

你可以在这个Github仓库中找到本篇文章提到的所有源代码。

发布于 2023-01-29 19:41・IP 属地上海 directx 12是什么意思?directx12有什么功能和效果?

来源: 酷狗科技网

时间:2022-08-09 15:57:54

directx 12是什么意思?

DirectX全称为Direct eXtension,是由微软公司创建的多媒体编程接口。

DirectX由C++编程语言实现,遵循COM。被广泛使用于Microsoft Windows、Microsoft Xbox和Microsoft Xbox 360电子游戏开发,并且只能支持这些平台,DirectX 12是目前DirectX最新的版本,大家熟悉的XP系统内置的版本为DirectX 9.0,Win7/8系统则内置版本为DirectX 11,而Win10正式版中,将内置最新的DirectX12,而且DX12只会支持Windows10,

微软在2014年的GDC上正式发布。全球首款支持DirectX 12的游戏是奇点灰烬。

directx12有什么功能和效果?

像之前的DirectX规范一样,DX12实际上也可以分为多个功能不同的功能层(Feature Level),不过DX12这次还多了一个底层优化,所以DX12规范可以视作三个不同层级:

·D3D 12 Low Level API:这部分实际上是见诸报道最多的一部分,DX12相比DX11性能大提升就是底层优化的功劳,这也是DX12最吸引人的一点,不论是对游戏开发者还是对游戏玩家来说都是如此。底层优化部分包括Low Overhead(低开销)、更多的控制及异步计算(Async Compute)等多个部分,低开销类似AMD提出的Mantle优化,后者也是大幅改善了游戏的多线程效率,降低了驱动层开销,现在这部分已经可以使用3DMark的Driver Overhead做测试了。

·DX12 Feature Level 12_0:前面的底层优化部分实际上是帮助DX12打通了“经脉”,提高了开发者的潜力,但那些并不涉及具体的招式——Feature Level 12_0这部分开始涉及更新的3D渲染方法,包括平铺资源(Tiled Resoure)、归类UAV访问、无绑定(Bindless)等等,其中多项功能实际上DX11.1中就有了,不过DX11中多是T1级别的,现在的则是T2级别的。

·DX12高级功能Feature Level 12_1:跟以往的DX11.1/11.2一样,DX12还有比Feature Level 12_0更高级的Feature Level 12_1功能,包括立体平铺资源(Volume Tiled Resources)、保守光栅(Conservative Rasterization)、光栅顺序视图(Raster Order Views)等,这些功能通常属于可选支持,但它们可以更好地提升开发者的效率或者游戏画质,同时对显卡的要求也更高。

以上三部分是DX12规范的主要内容,但这些还不是DX12的全部功能,还记得之前曝光过的DX12黑科技——A、N显卡混合交火吗?微软确实在DX12中尝试了不同显卡的混搭技术,该技术名为Muti-Adapter(多显卡适配器),它就可以把不同架构的GPU联合起来渲染。

directx12官方版拥有更加强效的驱动效率,可以允许游戏开发者对特定的硬件进行优化,能够为 Windows 系统提供高性能的硬件加速多煤体支持,让游戏最大程度的与电脑硬件相配合。


【知乎】DirectX 12 简明入门教程的评论 (共 条)

分享到微博请遵守国家法律