
Sudah ada banyak artikel tentang Habr tentang penggunaan shader komputasi dengan Unity, namun, sulit untuk menemukan artikel tentang menggunakan shader komputasi pada "Win32 API + DirectX 11" yang bersih. Namun, tugas ini tidak jauh lebih rumit, lebih detail - di bawah jalan pintas.
Untuk melakukan ini, kami akan menggunakan:
- Windows 10
- Visual Studio 2017 Community Edition dengan modul "Pengembangan aplikasi klasik di C ++"
Setelah membuat proyek, kami akan memberi tahu tautan untuk menggunakan pustaka `d3d11.lib`.

File tajukUntuk menghitung jumlah frame per detik, kami akan menggunakan perpustakaan standar
#include <time.h>
Kami akan menampilkan jumlah bingkai per detik melalui judul jendela, yang kami perlukan untuk membentuk garis yang sesuai
#include <stdio.h>
Kami tidak akan mempertimbangkan penanganan kesalahan secara terperinci, dalam kasus kami cukup bahwa aplikasi mogok dalam versi debug dan menunjukkan pada saat kerusakan:
#include <assert.h>
File header untuk WinAPI:
#define WIN32_LEAN_AND_MEAN #include <tchar.h> #include <Windows.h>
File header untuk Direct3D 11:
#include <dxgi.h> #include <d3d11.h>
ID sumber daya untuk memuat shader. Sebagai gantinya, Anda dapat memuat file objek shader yang dihasilkan oleh kompiler HLSL ke dalam memori. Membuat file sumber daya dijelaskan nanti.
#include "resource.h"
Konstanta yang umum untuk shader dan bagian pemanggilan akan dideklarasikan dalam file header yang terpisah.
#include "SharedConst.h"
Kami mendeklarasikan fungsi untuk memproses acara Windows, yang akan ditentukan kemudian:
LRESULT CALLBACK WndProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam);
Kami akan menulis fungsi untuk membuat dan menghancurkan jendela
int windowWidth, windowHeight; HINSTANCE hInstance; HWND hWnd; void InitWindows() {
Berikutnya adalah inisialisasi antarmuka untuk mengakses kartu video (Device dan DeviceContext) dan rantai buffer output (SwapChain):
IDXGISwapChain *swapChain; ID3D11Device *device; ID3D11DeviceContext *deviceContext; void InitSwapChain() { HRESULT result; DXGI_SWAP_CHAIN_DESC swapChainDesc;
Inisialisasi akses dari shader ke buffer tempat rendering akan dilakukan:
ID3D11RenderTargetView *renderTargetView; void InitRenderTargetView() { HRESULT result; ID3D11Texture2D *backBuffer;
Sebelum menginisialisasi shader, Anda harus membuatnya. Visual Studio dapat mengenali ekstensi file, jadi kita cukup membuat sumber dengan ekstensi .hlsl
, atau langsung membuat shader melalui menu. Saya memilih metode pertama, karena Lagi pula, melalui properti Anda harus mengatur penggunaan Shader Model 5.


Demikian pula, buat vertex dan pixel shader.
Di vertex shader, kita cukup mengonversi koordinat dari vektor dua dimensi (karena posisi titik yang kita miliki adalah dua dimensi) menjadi empat dimensi (diterima oleh kartu video):
float4 main(float2 input: POSITION): SV_POSITION { return float4(input, 0, 1); }
Dalam pixel shader, kami akan mengembalikan putih:
float4 main(float4 input: SV_POSITION): SV_TARGET { return float4(1, 1, 1, 1); }
Sekarang shader komputasi. Kami mendefinisikan rumus ini untuk interaksi poin:
Dengan massa yang diadopsi 1
Ini adalah bagaimana implementasi ini pada HLSL akan terlihat:
#include "SharedConst.h"
Anda mungkin memperhatikan bahwa file SharedConst.h
termasuk dalam shader. Ini adalah file header dengan konstanta, yang termasuk dalam main.cpp
. Inilah isi dari file ini:
#ifndef PARTICLE_COUNT #define PARTICLE_COUNT (1 << 15) #endif #ifndef NUMTHREADS #define NUMTHREADS 64 #endif
Hanya mendeklarasikan jumlah partikel dan jumlah aliran dalam satu kelompok. Kami akan mengalokasikan satu aliran untuk setiap partikel, jadi kami akan PARTICLE_COUNT / NUMTHREADS
jumlah grup sebagai PARTICLE_COUNT / NUMTHREADS
. Angka ini harus bilangan bulat, sehingga perlu bahwa jumlah partikel dibagi dengan jumlah aliran dalam kelompok.
Kami akan memuat bytecode shader yang dikompilasi menggunakan mekanisme sumber daya Windows. Untuk melakukan ini, buat file berikut:
resource.h
, yang akan berisi ID dari sumber daya yang sesuai:
#pragma once #define IDR_BYTECODE_COMPUTE 101 #define IDR_BYTECODE_VERTEX 102 #define IDR_BYTECODE_PIXEL 103
Dan resource.rc
, file untuk menghasilkan sumber daya yang sesuai dari konten berikut:
#include "resource.h" IDR_BYTECODE_COMPUTE ShaderObject "compute.cso" IDR_BYTECODE_VERTEX ShaderObject "vertex.cso" IDR_BYTECODE_PIXEL ShaderObject "pixel.cso"
Di mana ShaderObject
adalah jenis sumber daya, dan vertex.cso
, vertex.cso
dan pixel.cso
adalah nama-nama yang sesuai dari file Objek Shader yang Dikompilasi di direktori output.
Agar file dapat ditemukan, Anda harus menentukan path ke direktori output proyek di properti resource.rc
:

Visual Studio secara otomatis mengenali file sebagai deskripsi sumber daya dan menambahkannya ke majelis, Anda tidak perlu melakukan ini secara manual
Sekarang Anda dapat menulis kode inisialisasi shader:
ID3D11ComputeShader *computeShader; ID3D11VertexShader *vertexShader; ID3D11PixelShader *pixelShader; ID3D11InputLayout *inputLayout; void InitShaders() { HRESULT result; HRSRC src; HGLOBAL res;
Kode Inisialisasi Buffer:
ID3D11Buffer *positionBuffer; ID3D11Buffer *velocityBuffer; void InitBuffers() { HRESULT result; float *data = new float[2 * PARTICLE_COUNT];
Dan kode inisialisasi akses buffer dari shader komputasi:
ID3D11UnorderedAccessView *positionUAV; ID3D11UnorderedAccessView *velocityUAV; void InitUAV() { HRESULT result;
Selanjutnya, Anda harus memberi tahu pengemudi untuk menggunakan shader dan bundel yang dibuat dengan buffer:
void InitBindings() {
Untuk menghitung waktu bingkai rata-rata, kami akan menggunakan kode berikut:
const int FRAME_TIME_COUNT = 128; clock_t frameTime[FRAME_TIME_COUNT]; int currentFrame = 0; float AverageFrameTime() { frameTime[currentFrame] = clock(); int nextFrame = (currentFrame + 1) % FRAME_TIME_COUNT; clock_t delta = frameTime[currentFrame] - frameTime[nextFrame]; currentFrame = nextFrame; return (float)delta / CLOCKS_PER_SEC / FRAME_TIME_COUNT; }
Dan pada setiap frame - panggil fungsi ini:
void Frame() { float frameTime = AverageFrameTime();
Jika ukuran jendela telah berubah, kita juga perlu mengubah ukuran buffer render:
void ResizeSwapChain() { HRESULT result; RECT rect;
Akhirnya, Anda dapat menentukan fungsi pemrosesan pesan:
LRESULT CALLBACK WndProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) { switch (Msg) { case WM_CLOSE: PostQuitMessage(0); break; case WM_KEYDOWN: if (wParam == VK_ESCAPE) PostQuitMessage(0); break; case WM_SIZE: ResizeSwapChain(); break; default: return DefWindowProc(hWnd, Msg, wParam, lParam); } return 0; }
Dan fungsi main
:
int main() { InitWindows(); InitSwapChain(); InitRenderTargetView(); InitShaders(); InitBuffers(); InitUAV(); InitBindings(); ShowWindow(hWnd, SW_SHOW); bool shouldExit = false; while (!shouldExit) { Frame(); MSG msg; while (!shouldExit && PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { TranslateMessage(&msg); DispatchMessage(&msg); if (msg.message == WM_QUIT) shouldExit = true; } } DisposeUAV(); DisposeBuffers(); DisposeShaders(); DisposeRenderTargetView(); DisposeSwapChain(); DisposeWindows(); }
Tangkapan layar program yang sedang berjalan dapat dilihat pada judul artikel.
โ Proyek ini sepenuhnya diunggah ke GitHub