打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
【译】DirectX 12 简明入门教程
userphoto

2023.08.03 辽宁

关注
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 ToolbagAdobe 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 非常相似:
初始化 API:创建IDXGIFactory 、IDXGIAdapter、ID3D12Device、ID3D12CommandQueue、ID3D12CommandAllocator、ID3D12GraphicsCommandList 。
设置帧后台缓冲:为后台缓冲区创建IDXGISwapChain、ID3D12DescriptorHeap,为ID3D12Resource后台缓冲区渲染目标视图,创建ID3D12Fence以检测帧何时完成渲染。
初始化资源:创建三角形数据,例如ID3D12Resource顶点缓冲区、ID3D12Resource索引缓冲区等,创建ID3D12Fence以检测上传到 GPU 内存的时间。加载着色器ID3DBlob、常量缓冲区ID3D12Resource及其ID3D12DescriptorHeap,描述使用ID3D12RootSignature可以访问哪些资源,并构建ID3D12PipelineState
编码指令:向ID3D12GraphicsCommandList编写你希望执行的管线指令,确保将ResourceBarrier放在适当的位置。
渲染:更新 GPU 常量缓冲区数据(Uniforms),使用ExecuteCommandLists将命令提交至ID3D12CommandQueue,Present交换链,然后等待下一帧。
销毁:使用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 Xbox360电子游戏开发,并且只能支持这些平台,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 系统提供高性能的硬件加速多煤体支持,让游戏最大程度的与电脑硬件相配合。
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
Windows API 大全
c10k问题
Windows API函数大全---附:windows运行命令详解
进程线程伪句柄转为真实句柄
Windows系统内存的问题集
【云和恩墨大讲堂】故障分析| library cache latch 竞争案例分享
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服