* Artikel ini adalah hasil kerja sama dengan Intel Developer Zone. Artikel asli bisa dilihat di link ini.
Contoh ini menunjukkan bagaimana mengimplementasikan aplikasi multi-adapter menggunakan DirectX 12. GPU terintegrasi dari Intel (iGPU) dan NVIDIA GPU (dGPU) digunakan untuk berbagi beban kerja dalam ray-tracing sebuah adegan. Penggunaan paralel di antara GPU memungkinkan terjadinya peningkatan performa dan dapat melalkukan beban kinerja yang lebih kompleks.
Contoh ini menggunakan beberapa adapter untuk membuat adegan ray-traced sederhana menggunakan pixel shader. Kedua adapter me-render sebagian dari adegan secara paralel.
Dukungan untuk multi-adapter eksplisit adalah fitur baru pada Directx 12. Fitur ini memungkinkan penggunaan beberapa GPU secara paralel tanpa memperhatikan produsen dan jenis GPU-nya (sebagai contoh, terintegrasi atau tersendiri).
Kemampuan untuk memisahkan pekerjaan di beberapa GPU disediakan oleh sebuah manajemen resource independen dan antrian paralel untuk setiap GPU di tingkat API.
DirectX 12 memperkenalkan dua API fitur utama yang membantu memungkinkan aplikasi multi-adapter
// Describe cross-adapter shared resources on primaryDevice adapter D3D12_RESOURCE_DESC crossAdapterDesc = mRenderTargets[0]->GetDesc(); crossAdapterDesc.Flags = D3D12_RESOURCE_FLAG_ALLOW_CROSS_ADAPTER; crossAdapterDesc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; // Create a shader resource and shared handle for (int i = 0; i < NumRenderTargets; i++) { mPrimaryDevice->CreateCommittedResource( &CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT), D3D12_HEAP_FLAG_SHARED | D3D12_HEAP_FLAG_SHARED_CROSS_ADAPTER, &crossAdapterDesc, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE, nullptr, IID_PPV_ARGS(&shaderResources[i])); HANDLE heapHandle = nullptr; mPrimaryDevice->CreateSharedHandle( mShaderResources[i].Get(), nullptr, GENERIC_ALL, nullptr, &heapHandle); // Open shared handle on secondaryDevice device mSecondaryDevice->OpenSharedHandle(heapHandle, IID_PPV_ARGS(&shaderResourceViews[i])); CloseHandle(heapHandle); } // Create a shader resource view (SRV) for each of the cross adapter resources CD3DX12_CPU_DESCRIPTOR_HANDLE secondarySRVHandle(mSecondaryCbvSrvUavHeap->GetCPUDescriptorHandleForHeapStart()); for (int i = 0; i < NumRenderTargets; i++) { mSecondaryDevice->CreateShaderResourceView(shaderResourceViews[i].Get(), nullptr, secondarySRVHandle); secondarySRVHandle.Offset(mSecondaryCbvSrvUavDescriptorSize); }
// Create fence for cross adapter resources mPrimaryDevice->CreateFence(mCurrentFenceValue, D3D12_FENCE_FLAG_SHARED | D3D12_FENCE_FLAG_SHARED_CROSS_ADAPTER, IID_PPV_ARGS(&primaryFence)); // Create a shared handle to the cross adapter fence HANDLE fenceHandle = nullptr; mPrimaryDevice->CreateSharedHandle( primaryFence.Get(), nullptr, GENERIC_ALL, nullptr, &fenceHandle)); // Open shared handle to fence on secondaryDevice GPU mSecondaryDevice->OpenSharedHandle(fenceHandle, IID_PPV_ARGS(&secondaryFence));
// Render scene on primary device mPrimaryCommandQueue->ExecuteCommandLists(1, primaryCommandList);; // Signal primary device command queue to indicate render is complete mPrimaryCommandQueue->Signal(mPrimaryFence.Get(), currentFenceValue)); fenceValues[currentFrameIndex] = currentFenceValue; mCurrentFenceValue++;
// Wait for primary device to finish rendering the frame mCopyCommandQueue->Wait(mPrimaryFence.Get(), fenceValues[currentFrameIndex]); // Copy from off-screen render target to cross-adapter resource mCopyCommandQueue->ExecuteCommandLists(1, crossAdapterResources->mCopyCommandLists.Get()); // Signal secondary device to indicate copy is complete mCopyCommandQueue->Signal(mPrimaryCrossAdapterFence.Get(), mCurrentCrossAdapterFenceValue)); mCrossAdapterFenceValues[mCurrentFrameIndex] = mCurrentCrossAdapterFenceValue; mCurrentCrossAdapterFenceValue++;
// Wait for primary device to finish copying mSecondaryCommandQueue->Wait(mSecondaryCrossAdapterFence.Get(), mCrossAdapterFenceValues[mCurrentFrameIndex])); // Render cross adapter resources and segmented texture overlay on secondary device mSecondaryCommandQueue->ExecuteCommandLists(1, secondaryCommandList);
mSwapChain->Present(0, 0); MoveToNextFrame();
Perhatikan bahwa kode yang diberikan di atas telah dimodifikasi demi penyederhanaan dengan semua pengecekan eror telah dihapus. Kode ini tidak diharapkan untuk di-compile.
Menggunakan beberapa adapter untuk me-render sebuah adegan secara paralel menyebabkan terjadinya peningkatan yang signifikan dalam performa dibandingkan dengan mengandalkan satu adapter untuk melakukan seluruh pekerjaan render.
Gambar 1. Frametime dari 100 frame dalam milidetik dibandingkan perpecahan kinerja antara kartu terintegrasi dan tersendiri.
Dalam contoh adegan ray-traced, terjadi penurunan sekitar 25 milidetik ketika kedua NVIDIA GeForce 840M dan Intel HD Graphics 5500 digunakan untuk melakukan proses render bersama-sama.
Dengan beban kerja yang paralel, ini memungkinkan untuk mengurangi frame time yang dibutuhkan hingga sekitar 50% dibandingkan dengan menggunakan satu adapter.
Contoh ini dapat diunduh di Github. Dengan arsitektur sebagai berikut:
DX multi AdapterRenderer.cpu terdiri dari fungsi-fungsi berikut
public: DXMultiAdapterRenderer(std::vector<DXDevice*> devices, MS::ComPtr<IDXGIFactory4> dxgiFactory, UINT width, UINT height, HWND hwnd); virtual void OnUpdate() override; float GetSharePercentage(); void IncrementSharePercentage(); void DecrementSharePercentage(); protected: virtual void CreateRootSignatures() override; virtual void LoadPipeline() override; virtual void LoadAssets() override; virtual void CreateCommandLists() override; virtual void PopulateCommandLists() override; virtual void ExecuteCommandLists() override; virtual void MoveToNextFrame() override;
Class ini mengimplementasi semua fungsi render inti. LoadPipeline() dan LoadAssets() berfungsi untuk menciptakan semua yang signature root yang diperlukan, kompilasi shader, dan menciptakan objek pipeline serta menentukan dan membuat semua tekstur, buffer konstan, dan vertex buffers dan view terkait mereka. Semua daftar perintah dibuat pada saat tersebut juga.
Untuk setiap frame, PopulateCommandList() dan ExecuteCommandList() dipanggil.
Untuk memisahkan fungsionalitas render DirectX 12 tradisional dengan yang diperlukan untuk menggunakan multiple-adapter, semua fungsi cross-adapter di enkapsulasi dalam class AXCrossAdapterResources yang berisi fungsi-fungsi sebagai berikut:
public: DXCrossAdapterResources(DXDevice* primaryDevice, DXDevice* secondaryDevice); void CreateResources(); void CreateCommandList(); void PopulateCommandList(int currentFrameIndex); void SetupFences();
Fungsi CreateResources(), CreateCommandList(), and SetupFences() dipanggil inisialisasinya untuk membuat resource cross-adapter dan menginisialisasi objek sinkronasi.
Pada setiap frame, fungsi PopulateCommandList() dipanggil untuk menyalin daftar perintah.
Class DXCrossAdapterResources berisi daftar perintah alokasi yang terpisah, antrian perintah, dan perintah yang digunakan untuk menyalin resource dari target render dalam adapter primer ke dalam resource cross-adapter.
* Artikel ini adalah hasil kerja sama dengan Intel Developer Zone. Artikel asli bisa dilihat di link ini.
Cisco mengungkapkan tiga kerentanan dalam layanannya. Ini dia penanganannya!
Ini ulasan mengenai keuntungan OptimalCloud Partner Platform, platform baru milik Optimal idM!
Google kenalkan dua koleksi baru dari Coral. Dua koleksi baru ini bakal menambah kemampuan pengembangan…
Raksasa Google baru saja mengembangkan sistem pemindaian kanker payudara berbasis kecerdasan buatan. Bagaimana hasilnya, berikut…
Meski dikenalkan bersamaan dengan Android 10 Beta, sampai kini Bubbles Notifications masih dalam tahap pengembangan.…
Samsung akan kembali memamerkan hasil program C-Lab ke ajang CES 2020. Ini dia proyek dan…