“铁打的图形,流水的API。”最近闲来无事,想去学习一下各种底层的渲染API,这一学就是好几天,而且我只画出了一个三角形。我感慨颇多故有以下这篇文章,我不会讲图形,只是从开发者的角度说说感受。

OpenGL

源代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
#include <glad/glad.h>
#include <GLFW/glfw3.h>

#include <iostream>

int width = 1280;
int height = 720;

#pragma comment(lib, "glfw3.lib")

int main() {
//window
glfwInit();
auto window = glfwCreateWindow(width, height, "window", nullptr, nullptr);
glfwMakeContextCurrent(window);
gladLoadGLLoader((GLADloadproc)glfwGetProcAddress);
glViewport(0, 0, width, height);

//data
GLuint vertex_array_object;
glGenVertexArrays(1, &vertex_array_object);
glBindVertexArray(vertex_array_object);

const float triangle[] = {
-0.5f, -0.5f, 0.0f,
0.5f, -0.5f, 0.0f,
0.0f, 0.5f, 0.0f
};

GLuint vertex_buffer_object;
glGenBuffers(1, &vertex_buffer_object);
glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer_object);
glBufferData(GL_ARRAY_BUFFER, sizeof(triangle), triangle, GL_STATIC_DRAW);

unsigned int indices[] = {
0, 1, 2
};

GLuint element_buffer_object;
glGenBuffers(1, &element_buffer_object);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, element_buffer_object);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);

//shader
const char* vertex_shader_source =
"#version 330 core\n"
"layout (location = 0) in vec3 aPos;\n"
"void main()\n"
"{\n"
" gl_Position = vec4(aPos, 1.0);\n"
"}\n\0";
const char* fragment_shader_source =
"#version 330 core\n"
"out vec4 FragColor;\n"
"void main()\n"
"{\n"
" FragColor = vec4(1.0f, 1.0f, 1.0f, 1.0f);\n"
"}\n\0";

int vertex_shader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertex_shader, 1, &vertex_shader_source, NULL);
glCompileShader(vertex_shader);

int fragment_shader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragment_shader, 1, &fragment_shader_source, NULL);
glCompileShader(fragment_shader);

int shader_program = glCreateProgram();
glAttachShader(shader_program, vertex_shader);
glAttachShader(shader_program, fragment_shader);
glLinkProgram(shader_program);

glDeleteShader(vertex_shader);
glDeleteShader(fragment_shader);

//draw
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);

glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_INT, 0);

glfwSwapBuffers(window);

//event
while (!glfwWindowShouldClose(window)) {
glfwPollEvents();
}
glfwTerminate();

return 0;
}

运行结果

这里的OpenGL是现代版的,包含Buffer和Shader,并不是那种老式的glBeginglEnd。注意我们写得API都是极度简化的,比如没有包含调试相关代码,所以千万别学啊,不然会难受的要死,特别是后面讲的更现代的API。OpenGL有点像第三方API,代码实现是在显卡驱动里面,但我们的Windows系统更偏爱的是它自己的DirectX,所以系统提供的gl库和接口都太老了,似乎还停留在1.X。所以我们需要一些辅助库来从驱动中拉出相关函数,当然也可以通过名称手动拉出函数指针,不过我们还是使用已经写好的工具比较好,这就是我们的glad。glfw主要提供窗口上下文,至于window自带的窗口API其实能用,就是十分麻烦,还没有跨平台性。还有一件值得说的是,比较新的glfw里已经集成了glad的功能,只不过初始化使用的是gladLoadGL而不是原来的gladLoadGLLoader,还有头文件是glad/gl.h,从目前的实际开发来看,两者的功能基本一样。值得注意的是我这里链接库的时候使用了#pragma,这可别用上瘾了,它没有跨平台性,如果不跨平台还不如用DirectX,我这里用这个主要是防止链接出问题,32位和64位需要链接两个不同的库,干脆直接在代码里链接算了。

DirectX11

源代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
#include <Windows.h>
#include <wrl.h>
#include <d3d11.h>
#include <d3dcompiler.h>

#include <iostream>

#pragma comment(lib,"d3d11.lib")
#pragma comment(lib, "D3DCompiler.lib")

int width = 720;
int height = 540;

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR lpCmdLine, int nCmdShow)
{
// window
WNDCLASSEX wc = { 0 };
wc.cbSize = sizeof(wc);
wc.style = CS_OWNDC;
wc.lpfnWndProc = DefWindowProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = nullptr;
wc.hCursor = nullptr;
wc.hbrBackground = nullptr;
wc.lpszMenuName = nullptr;
wc.lpszClassName = TEXT("dx11Win");
wc.hIconSm = nullptr;
RegisterClassEx(&wc);

HWND hWnd = CreateWindow(L"dx11Win", L"DirectX11Window", WS_CAPTION | WS_MINIMIZEBOX | WS_SYSMENU, CW_USEDEFAULT, CW_USEDEFAULT, width, height, nullptr, nullptr, hInstance, nullptr);
ShowWindow(hWnd, SW_SHOWDEFAULT);

//init
DXGI_SWAP_CHAIN_DESC sd = {};
sd.BufferDesc.Width = width;
sd.BufferDesc.Height = height;
sd.BufferDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
sd.BufferDesc.RefreshRate.Numerator = 0;
sd.BufferDesc.RefreshRate.Denominator = 0;
sd.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;
sd.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
sd.SampleDesc.Count = 1;
sd.SampleDesc.Quality = 0;
sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
sd.BufferCount = 1;
sd.OutputWindow = hWnd;
sd.Windowed = TRUE;
sd.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
sd.Flags = 0;

Microsoft::WRL::ComPtr<ID3D11Device> pDevice;
Microsoft::WRL::ComPtr<IDXGISwapChain> pSwap;
Microsoft::WRL::ComPtr<ID3D11DeviceContext> pContext;
D3D11CreateDeviceAndSwapChain(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, 0, nullptr, 0, D3D11_SDK_VERSION, &sd, &pSwap, &pDevice, nullptr, &pContext);

Microsoft::WRL::ComPtr<ID3D11RenderTargetView> pTarget;
Microsoft::WRL::ComPtr<ID3D11Resource> pBackBuffer;
pSwap->GetBuffer(0, __uuidof(ID3D11Resource), &pBackBuffer);
pDevice->CreateRenderTargetView(pBackBuffer.Get(), nullptr, &pTarget);

//data
const float vertices[] = {
0.0f, 0.5f,
0.5f, -0.5f,
-0.5f, -0.5f
};

Microsoft::WRL::ComPtr<ID3D11Buffer> pVertexBuffer;
D3D11_BUFFER_DESC bd = {};
bd.BindFlags = D3D11_BIND_VERTEX_BUFFER;
bd.Usage = D3D11_USAGE_DEFAULT;
bd.CPUAccessFlags = 0u;
bd.MiscFlags = 0u;
bd.ByteWidth = sizeof(vertices);
bd.StructureByteStride = 2 * sizeof(float);
D3D11_SUBRESOURCE_DATA pData = {};
pData.pSysMem = vertices;
pDevice->CreateBuffer(&bd, &pData, &pVertexBuffer);

const UINT stride = 2 * sizeof(float);
const UINT offset = 0u;
pContext->IASetVertexBuffers(0u, 1u, pVertexBuffer.GetAddressOf(), &stride, &offset);

const unsigned short indices[] =
{
0, 1, 2
};
Microsoft::WRL::ComPtr<ID3D11Buffer> pIndexBuffer;
D3D11_BUFFER_DESC ibd = {};
ibd.BindFlags = D3D11_BIND_INDEX_BUFFER;
ibd.Usage = D3D11_USAGE_DEFAULT;
ibd.CPUAccessFlags = 0u;
ibd.MiscFlags = 0u;
ibd.ByteWidth = sizeof(indices);
ibd.StructureByteStride = sizeof(unsigned short);
D3D11_SUBRESOURCE_DATA isd = {};
isd.pSysMem = indices;
pDevice->CreateBuffer(&ibd, &isd, &pIndexBuffer);

pContext->IASetIndexBuffer(pIndexBuffer.Get(), DXGI_FORMAT_R16_UINT, 0u);

//shader
Microsoft::WRL::ComPtr<ID3DBlob> pBlob;
Microsoft::WRL::ComPtr<ID3D11PixelShader> pPixelShader;
D3DReadFileToBlob(L"PixelShader.cso", &pBlob);
pDevice->CreatePixelShader(pBlob->GetBufferPointer(), pBlob->GetBufferSize(), nullptr, &pPixelShader);

pContext->PSSetShader(pPixelShader.Get(), nullptr, 0u);

Microsoft::WRL::ComPtr<ID3D11VertexShader> pVertexShader;
D3DReadFileToBlob(L"VertexShader.cso", &pBlob);
pDevice->CreateVertexShader(pBlob->GetBufferPointer(), pBlob->GetBufferSize(), nullptr, &pVertexShader);

pContext->VSSetShader(pVertexShader.Get(), nullptr, 0u);

Microsoft::WRL::ComPtr<ID3D11InputLayout> pInputLayout;
const D3D11_INPUT_ELEMENT_DESC ied[] =
{
{"POSITION", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0}
};
pDevice->CreateInputLayout(ied, 1u, pBlob->GetBufferPointer(), pBlob->GetBufferSize(), &pInputLayout );
pContext->IASetInputLayout(pInputLayout.Get());

pContext->OMSetRenderTargets(1u, pTarget.GetAddressOf(), nullptr);

pContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

D3D11_VIEWPORT vp;
vp.Width = width;
vp.Height = height;
vp.MinDepth = 0;
vp.MaxDepth = 1;
vp.TopLeftX = 0;
vp.TopLeftY = 0;
pContext->RSSetViewports(1u, &vp);

//draw
const float color[] = { 0.0f, 0.0f, 0.0f, 1.0f };
pContext->ClearRenderTargetView(pTarget.Get(), color);
pContext->DrawIndexed(3u, 0u, 0u);
pSwap->Present(1u, 0u);

//event
while (true)
{
MSG msg;
while (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}

return 0;
}

顶点着色器

1
2
3
4
float4 main(float3 pos : POSITION) : SV_POSITION
{
return float4(pos.x, pos.y, pos.z, 1.0f);
}

像素(片段)着色器

1
2
3
4
float4 main() : SV_TARGET
{
return float4(1.0f, 1.0f, 1.0f, 1.0f);
}

运行结果

我使用DirectX的第一感受就是,都是什么奇葩的API,都什么时代了,还用大写的函数,而且还非得给基本变量类型大写重新命名一遍,真的有必要吗?可能是我基本没接触过C++版的WinAPI的原因吧,因为大部分用的都是C#和.Net。还有就是这离谱的配置系统,使用结构体保存配置就算了,有些本身没配置必要的就不能给一个默认值嘛,不配置还会运行异常,说到这个,我使用的时候遇到的最多错误的就是,指针访问冲突,当然其实不算啥,主要这个配置系统牵一发而动全身,某处配置了,另一处就得和它对应。还有这离谱的事件管理,果然老就是老,一个窗口关闭,结束程序都那么麻烦,当然我没写就是了。其实啊,窗口系统是可以使用glfw的,不过对于窗口系统最后我们要完成的就是封装而已,何必再多一层glfw来封装,而且DirectX只能在windows上,跨平台也没有必要吧,嗯,总之,这只是一种思路而已。虽然看起来DirectX似乎不太比得上OpenGL,当然我不想说效率之类的,这没有比较的意义,单纯从开发者来看,DirectX提供提供了更加丰富的组件,但opengl还得依赖外部库,如数学运算的glm,同时对内存管理上,DirectX有自己的智能指针来管理COM对象,而对opengl则必需封装对应对象以后,写了对应析构函数之后,才能使用C++标准提供的智能指针来管理内存。OpenGL嘛,跨平台,写起来简单又优美,这就足够了。

metal

源代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
@import simd;
@import MetalKit;

@interface AAPLRenderer : NSObject<MTKViewDelegate>

- (nonnull instancetype)initWithMetalKitView:(nonnull MTKView *)mtkView;

@end

typedef enum AAPLVertexInputIndex
{
AAPLVertexInputIndexVertices = 0,
AAPLVertexInputIndexViewportSize = 1,
} AAPLVertexInputIndex;

typedef struct
{
vector_float2 position;
vector_float4 color;
} AAPLVertex;

@implementation AAPLRenderer
{
id<MTLDevice> _device;

id<MTLRenderPipelineState> _pipelineState;

id<MTLCommandQueue> _commandQueue;

vector_uint2 _viewportSize;
}

- (nonnull instancetype)initWithMetalKitView:(nonnull MTKView *)mtkView
{
self = [super init];
if(self)
{
NSError *error;

_device = mtkView.device;

id<MTLLibrary> defaultLibrary = [_device newDefaultLibrary];

id<MTLFunction> vertexFunction = [defaultLibrary newFunctionWithName:@"vertexShader"];
id<MTLFunction> fragmentFunction = [defaultLibrary newFunctionWithName:@"fragmentShader"];

MTLRenderPipelineDescriptor *pipelineStateDescriptor = [[MTLRenderPipelineDescriptor alloc] init];
pipelineStateDescriptor.label = @"Simple Pipeline";
pipelineStateDescriptor.vertexFunction = vertexFunction;
pipelineStateDescriptor.fragmentFunction = fragmentFunction;
pipelineStateDescriptor.colorAttachments[0].pixelFormat = mtkView.colorPixelFormat;

_pipelineState = [_device newRenderPipelineStateWithDescriptor:pipelineStateDescriptor
error:&error];

NSAssert(_pipelineState, @"Failed to create pipeline state: %@", error);

_commandQueue = [_device newCommandQueue];
}

return self;
}

- (void)mtkView:(nonnull MTKView *)view drawableSizeWillChange:(CGSize)size
{
_viewportSize.x = size.width;
_viewportSize.y = size.height;
}

- (void)drawInMTKView:(nonnull MTKView *)view
{
static const AAPLVertex triangleVertices[] =
{
// 2D positions, RGBA colors
{ { 250, -250 }, { 1, 0, 0, 1 } },
{ { -250, -250 }, { 0, 1, 0, 1 } },
{ { 0, 250 }, { 0, 0, 1, 1 } },
};

id<MTLCommandBuffer> commandBuffer = [_commandQueue commandBuffer];
commandBuffer.label = @"MyCommand";

MTLRenderPassDescriptor *renderPassDescriptor = view.currentRenderPassDescriptor;

if(renderPassDescriptor != nil)
{
id<MTLRenderCommandEncoder> renderEncoder =
[commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor];
renderEncoder.label = @"MyRenderEncoder";

[renderEncoder setViewport:(MTLViewport){0.0, 0.0, _viewportSize.x, _viewportSize.y, 0.0, 1.0 }];

[renderEncoder setRenderPipelineState:_pipelineState];

[renderEncoder setVertexBytes:triangleVertices
length:sizeof(triangleVertices)
atIndex:AAPLVertexInputIndexVertices];

[renderEncoder setVertexBytes:&_viewportSize
length:sizeof(_viewportSize)
atIndex:AAPLVertexInputIndexViewportSize];

[renderEncoder drawPrimitives:MTLPrimitiveTypeTriangle
vertexStart:0
vertexCount:3];

[renderEncoder endEncoding];

[commandBuffer presentDrawable:view.currentDrawable];
}

[commandBuffer commit];
}

@end

着色器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <metal_stdlib>

using namespace metal;

#include "AAPLShaderTypes.h"

struct RasterizerData
{
float4 position [[position]];

float4 color;
};

vertex RasterizerData
vertexShader(uint vertexID [[vertex_id]],
constant AAPLVertex *vertices [[buffer(AAPLVertexInputIndexVertices)]],
constant vector_uint2 *viewportSizePointer [[buffer(AAPLVertexInputIndexViewportSize)]])
{
RasterizerData out;

float2 pixelSpacePosition = vertices[vertexID].position.xy;

vector_float2 viewportSize = vector_float2(*viewportSizePointer);

out.position = vector_float4(0.0, 0.0, 0.0, 1.0);
out.position.xy = pixelSpacePosition / (viewportSize / 2.0);

out.color = vertices[vertexID].color;

return out;
}

fragment float4 fragmentShader(RasterizerData in [[stage_in]])
{
return in.color;
}

我们没有苹果的设备,所以没有运行结果,因为虚拟机上运行Xcode效率太差了,所以我直接把官方那的例子拿了过来,是的,唯有这个实在没办法实践,不过我们看看还是可以的。这里使用的语言是Object-C,不过别被这不习惯的写法搞害怕了,其实就是类型和名称倒过来而已,习惯go和kotlin的话应该不难,还有这“[]”,里面东西按顺序读的话,其实就是对象调用方法再加参数,总之习惯就好。metal这里已经开始使用一些现代的一些API思维了,如命令队列之类的,这里的上下文是直接由metal提供的view。其实,metal最让我觉得羡慕的是没有繁琐的配置过程,应该是它提供了大量默认值,与此同时,它还能在提供较为底层的API情况下写得如OpenGL差不多的简洁。在这里说可能没什么感受,大的要来了。

directX12

源代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
#include <Windows.h>
#include <wrl.h>
#include <d3d12.h>
#include <dxgi.h>
#include <d3dcompiler.h>

#pragma comment(lib,"d3d12.lib")
#pragma comment(lib, "dxgi.lib")
#pragma comment(lib, "D3DCompiler.lib")

int width = 720;
int height = 540;

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR lpCmdLine, int nCmdShow)
{
// window
WNDCLASSEX wc = { 0 };
wc.cbSize = sizeof(wc);
wc.style = CS_OWNDC;
wc.lpfnWndProc = DefWindowProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = nullptr;
wc.hCursor = nullptr;
wc.hbrBackground = nullptr;
wc.lpszMenuName = nullptr;
wc.lpszClassName = TEXT("dx12Win");
wc.hIconSm = nullptr;
RegisterClassEx(&wc);

HWND hWnd = CreateWindow(L"dx12Win", L"DirectX12Window", WS_CAPTION | WS_MINIMIZEBOX | WS_SYSMENU, CW_USEDEFAULT, CW_USEDEFAULT, width, height, nullptr, nullptr, hInstance, nullptr);
ShowWindow(hWnd, SW_SHOWDEFAULT);

//init
Microsoft::WRL::ComPtr<IDXGIFactory> dxgiFactory;
CreateDXGIFactory(IID_PPV_ARGS(&dxgiFactory));

Microsoft::WRL::ComPtr<ID3D12Device> device;
Microsoft::WRL::ComPtr<IDXGIAdapter> dxgiAdapter;
for (UINT adapterIndex = 0; DXGI_ERROR_NOT_FOUND != dxgiFactory->EnumAdapters(adapterIndex, &dxgiAdapter); ++adapterIndex)
{
if (SUCCEEDED(D3D12CreateDevice(dxgiAdapter.Get(), D3D_FEATURE_LEVEL_11_0, _uuidof(ID3D12Device), nullptr)))
break;
}
D3D12CreateDevice(dxgiAdapter.Get(), D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&device));

Microsoft::WRL::ComPtr<ID3D12CommandQueue> commandQueue;
D3D12_COMMAND_QUEUE_DESC cqDesc = {};
cqDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
cqDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
device->CreateCommandQueue(&cqDesc, IID_PPV_ARGS(&commandQueue));

Microsoft::WRL::ComPtr<IDXGISwapChain> swapChain;
DXGI_SWAP_CHAIN_DESC swapChainDesc = {};
swapChainDesc.BufferCount = 2;
swapChainDesc.BufferDesc.Width = width;
swapChainDesc.BufferDesc.Height = height;
swapChainDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
swapChainDesc.OutputWindow = hWnd;
swapChainDesc.SampleDesc.Count = 1;
swapChainDesc.Windowed = TRUE;
dxgiFactory->CreateSwapChain(commandQueue.Get(), &swapChainDesc, &swapChain);
int currentFrameIndex = 0;

Microsoft::WRL::ComPtr<ID3D12DescriptorHeap> rtvDescriptorHeap;
Microsoft::WRL::ComPtr<ID3D12Resource> pBackBuffer[2];
D3D12_DESCRIPTOR_HEAP_DESC rtvDesc = {};
rtvDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
rtvDesc.NumDescriptors = 2;
rtvDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
device->CreateDescriptorHeap(&rtvDesc, IID_PPV_ARGS(&rtvDescriptorHeap));

D3D12_CPU_DESCRIPTOR_HANDLE rtvHandle = rtvDescriptorHeap->GetCPUDescriptorHandleForHeapStart();
int rtvDescriptorSize = device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
for (int i = 0; i < 2; i++)
{
swapChain->GetBuffer(i, IID_PPV_ARGS(&pBackBuffer[i]));
device->CreateRenderTargetView(pBackBuffer[i].Get(), nullptr, rtvHandle);
rtvHandle.ptr += rtvDescriptorSize;
}

Microsoft::WRL::ComPtr<ID3D12CommandAllocator> commandAllocator;
device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&commandAllocator));
Microsoft::WRL::ComPtr<ID3D12GraphicsCommandList> commandList;

Microsoft::WRL::ComPtr<ID3D12Fence> fence;
device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&fence));
int fenceValue = 0;
HANDLE fenceEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);

//shader
Microsoft::WRL::ComPtr<ID3D12RootSignature> rootSignature;
D3D12_ROOT_SIGNATURE_DESC rsDesc = { 0, nullptr, 0, nullptr, D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT };
Microsoft::WRL::ComPtr<ID3DBlob> signatureBlob;
D3D12SerializeRootSignature(&rsDesc, D3D_ROOT_SIGNATURE_VERSION_1_0, &signatureBlob, nullptr);
device->CreateRootSignature(0, signatureBlob->GetBufferPointer(), signatureBlob->GetBufferSize(), IID_PPV_ARGS(&rootSignature));

Microsoft::WRL::ComPtr<ID3DBlob> vertexShaderBlob;
D3DCompileFromFile(L"VertexShader.hlsl", nullptr, nullptr, "main", "vs_5_0", D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION, 0, &vertexShaderBlob, nullptr);
Microsoft::WRL::ComPtr<ID3DBlob> pixelShaderBlob;
D3DCompileFromFile(L"PixelShader.hlsl", nullptr, nullptr, "main", "ps_5_0", D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION, 0, &pixelShaderBlob, nullptr);

const D3D12_INPUT_ELEMENT_DESC vertexLayout[] = {
{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }
};

Microsoft::WRL::ComPtr<ID3D12PipelineState> pso;
D3D12_GRAPHICS_PIPELINE_STATE_DESC psoDesc = {};
psoDesc.pRootSignature = rootSignature.Get();
psoDesc.VS = { vertexShaderBlob->GetBufferPointer(), vertexShaderBlob->GetBufferSize() };
psoDesc.PS = { pixelShaderBlob->GetBufferPointer(), pixelShaderBlob->GetBufferSize() };
psoDesc.SampleMask = 0xffffffff;
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,
};
psoDesc.BlendState.AlphaToCoverageEnable = FALSE;
psoDesc.BlendState.IndependentBlendEnable = FALSE;
psoDesc.BlendState.RenderTarget[0] = defaultRenderTargetBlendDesc;
psoDesc.RasterizerState.FillMode = D3D12_FILL_MODE_SOLID;
psoDesc.RasterizerState.CullMode = D3D12_CULL_MODE_BACK;
psoDesc.DepthStencilState.DepthEnable = FALSE;
psoDesc.DepthStencilState.StencilEnable = FALSE;
psoDesc.InputLayout.pInputElementDescs = vertexLayout;
psoDesc.InputLayout.NumElements = sizeof(vertexLayout) / sizeof(D3D12_INPUT_ELEMENT_DESC);
psoDesc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;
psoDesc.NumRenderTargets = 1;
psoDesc.RTVFormats[0] = DXGI_FORMAT_R8G8B8A8_UNORM;
psoDesc.DSVFormat = DXGI_FORMAT_D32_FLOAT;
psoDesc.SampleDesc = { 1, 0 };
device->CreateGraphicsPipelineState(&psoDesc, IID_PPV_ARGS(&pso));
device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, commandAllocator.Get(), pso.Get(), IID_PPV_ARGS(&commandList));
commandList->Close();

//data
const float vertices[] = {
0.0f, 0.5f, 0.5f,
0.3f, -0.0f, 0.5f,
-0.3f, -0.0f, 0.5f
};

Microsoft::WRL::ComPtr<ID3D12Resource> vertexBuffer;
D3D12_VERTEX_BUFFER_VIEW vertexBufferView;
D3D12_HEAP_PROPERTIES hp = {};
hp.Type = D3D12_HEAP_TYPE_UPLOAD;
D3D12_RESOURCE_DESC rd = {};
rd.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
rd.Width = sizeof(vertices);
rd.Height = 1;
rd.DepthOrArraySize = 1;
rd.MipLevels = 1;
rd.Flags = D3D12_RESOURCE_FLAG_NONE;
rd.Format = DXGI_FORMAT_UNKNOWN;
rd.SampleDesc = {1, 0};
rd.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
device->CreateCommittedResource(&hp, D3D12_HEAP_FLAG_NONE, &rd, D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, IID_PPV_ARGS(&vertexBuffer));

void* pVertexDataBegin;
const D3D12_RANGE readRange = { 0, 0 };
vertexBuffer->Map(0, &readRange, &pVertexDataBegin);
memcpy(pVertexDataBegin, vertices, sizeof(vertices));
vertexBuffer->Unmap(0, nullptr);
vertexBufferView.BufferLocation = vertexBuffer->GetGPUVirtualAddress();
vertexBufferView.StrideInBytes = 3 * sizeof(float);
vertexBufferView.SizeInBytes = sizeof(vertices);

D3D12_VIEWPORT viewport;
viewport.TopLeftX = 0;
viewport.TopLeftY = 0;
viewport.Width = (float)width;
viewport.Height = (float)height;
viewport.MinDepth = 0.0f;
viewport.MaxDepth = 1.0f;
D3D12_RECT scissorRect;
scissorRect.left = 0;
scissorRect.top = 0;
scissorRect.right = width;
scissorRect.bottom = height;

commandQueue->Signal(fence.Get(), 1);

//render
const float clearColor[] = { 0.0f, 0.0f, 0.0f, 1.0f };
rtvHandle = rtvDescriptorHeap->GetCPUDescriptorHandleForHeapStart();
D3D12_RESOURCE_BARRIER barrierB = {};
barrierB.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
barrierB.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
barrierB.Transition.pResource = pBackBuffer[0].Get();
barrierB.Transition.StateBefore = D3D12_RESOURCE_STATE_PRESENT;
barrierB.Transition.StateAfter = D3D12_RESOURCE_STATE_RENDER_TARGET;
barrierB.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
D3D12_RESOURCE_BARRIER barrierA = {};
barrierA.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
barrierA.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
barrierA.Transition.pResource = pBackBuffer[0].Get();
barrierA.Transition.StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET;
barrierA.Transition.StateAfter = D3D12_RESOURCE_STATE_PRESENT;
barrierA.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;

commandAllocator->Reset();
commandList->Reset(commandAllocator.Get(), pso.Get());
commandList->SetGraphicsRootSignature(rootSignature.Get());
commandList->RSSetViewports(1, &viewport);
commandList->RSSetScissorRects(1, &scissorRect);

commandList->ResourceBarrier(1, &barrierB);
commandList->OMSetRenderTargets(1, &rtvHandle, FALSE, nullptr);
commandList->ClearRenderTargetView(rtvHandle, clearColor, 0, nullptr);

commandList->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
commandList->IASetVertexBuffers(0, 1, &vertexBufferView);
commandList->DrawInstanced(3, 1, 0, 0);
commandList->ResourceBarrier(1, &barrierA);
commandList->Close();

ID3D12CommandList* ppCommandLists[] = { commandList.Get() };
commandQueue->ExecuteCommandLists(_countof(ppCommandLists), ppCommandLists);
swapChain->Present(1, 0);
commandQueue->Signal(fence.Get(), 2);

//event
while (true)
{
MSG msg;
while (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}

return 0;
}

着色器与DierctX11相同,代码量差距如下

其实并没网上那么夸张,运行结果如下

网上大多例子,包括官方的,基本都是类的封装,使用的设备是dgxi1_4.h,而引入dx12用的都是辅助头d3dx12.h,这是官方推荐的,也是基本所有人的做法,不过我嘛,可是技术宅啊,当然要用更底层的东西了,而且我本身就不喜欢windows这种十分古老的API写法,却还得在类名称后面加数字什么的,这不得气死我这个强迫症啊。辅助头提供了什么?我们先说点其它的吧,依据官方的意思与理想,在这种API下,开发者可以更加的接近底层,拥有更多的控制权,这样就能更好得优化游戏了。但我对此提出了质疑,这好比什么,给你车让你开和给你车的部件让你造车再开一样,开发者做了更多的事情,但开发者真得有精力去思考更底层的优化吗?给我设备选择,给我命令队列,来进行优化选择吗?但就目前来看,做的事基本都是封装,然后退化到上一个时代,这是十分讽刺的一件事,甚至连官方的例子都是贴脸的告诉你,给我封装起来。或许是我见识短浅吧,但是如此多的配置不都是copy来的嘛,那些东西基本没啥好改的吧,却还非得再写一遍,开发者致力于上一层调优,又哪有时间来管这一层呢?API的目的本身就是来节约开发的,但却在图形方面往反方向发展,这绝对知道思考。d3dx12.h头文件其实就是在说明这件事情,它实际就是拿来简化配置用的,其实越是底层配置关联性就越大,出错概率就越大,虽说有调试层,但对开发者来说综究还是会厌烦的,而且我们都知道这到底有啥好优化的?最后,我想说一句话,你的那个d3dx12.h能不能好好封装,我已经出现好多次右值不能取地址的情况了。

vulkan

源代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
#define GLFW_INCLUDE_VULKAN
#include <GLFW/glfw3.h>

#include <fstream>
#include <algorithm>
#include <vector>
#include <optional>
#include <set>
#include <array>

#pragma comment(lib, "glfw3-x64.lib")
#pragma comment(lib, "vulkan-1.lib")

const uint32_t WIDTH = 800;
const uint32_t HEIGHT = 600;

int main() {
//window
glfwInit();
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
GLFWwindow* window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr);

//init
VkApplicationInfo appInfo{};
appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
appInfo.pApplicationName = "Hello Triangle";
appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0);
appInfo.pEngineName = "No Engine";
appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0);
appInfo.apiVersion = VK_API_VERSION_1_0;
VkInstanceCreateInfo instanceInfo{};
instanceInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
instanceInfo.pApplicationInfo = &appInfo;

uint32_t glfwExtensionCount = 0;
const char** glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);
std::vector<const char*> extensions(glfwExtensions, glfwExtensions + glfwExtensionCount);
instanceInfo.enabledExtensionCount = static_cast<uint32_t>(extensions.size());
instanceInfo.ppEnabledExtensionNames = extensions.data();
instanceInfo.enabledLayerCount = 0;
instanceInfo.pNext = nullptr;
VkInstance instance;
vkCreateInstance(&instanceInfo, nullptr, &instance);

VkSurfaceKHR surface;
glfwCreateWindowSurface(instance, window, nullptr, &surface);

uint32_t deviceCount = 0;
vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr);
std::vector<VkPhysicalDevice> devices(deviceCount);
vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data());
VkPhysicalDevice physicalDevice = devices[0];

std::vector<VkDeviceQueueCreateInfo> queueCreateInfos;
std::set<uint32_t> uniqueQueueFamilies = { 0, 0 };
float queuePriority = 1.0f;
for (uint32_t queueFamily : uniqueQueueFamilies) {
VkDeviceQueueCreateInfo queueCreateInfo{};
queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
queueCreateInfo.queueFamilyIndex = queueFamily;
queueCreateInfo.queueCount = 1;
queueCreateInfo.pQueuePriorities = &queuePriority;
queueCreateInfos.push_back(queueCreateInfo);
}
VkPhysicalDeviceFeatures deviceFeatures{};
VkDeviceCreateInfo deviceInfo{};
deviceInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
deviceInfo.queueCreateInfoCount = static_cast<uint32_t>(queueCreateInfos.size());
deviceInfo.pQueueCreateInfos = queueCreateInfos.data();
deviceInfo.pEnabledFeatures = &deviceFeatures;
const std::vector<const char*> deviceExtensions = {
VK_KHR_SWAPCHAIN_EXTENSION_NAME
};
deviceInfo.enabledExtensionCount = static_cast<uint32_t>(deviceExtensions.size());
deviceInfo.ppEnabledExtensionNames = deviceExtensions.data();
deviceInfo.enabledLayerCount = 0;
VkDevice device;
vkCreateDevice(physicalDevice, &deviceInfo, nullptr, &device);
VkQueue graphicsQueue;
VkQueue presentQueue;
vkGetDeviceQueue(device, 0, 0, &graphicsQueue);
vkGetDeviceQueue(device, 0, 0, &presentQueue);

VkSurfaceCapabilitiesKHR capabilities;
vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physicalDevice, surface, &capabilities);
uint32_t imageCount = capabilities.minImageCount + 1;
VkSwapchainCreateInfoKHR swapchainInfo{};
swapchainInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
swapchainInfo.surface = surface;
swapchainInfo.minImageCount = imageCount;
swapchainInfo.imageFormat = VK_FORMAT_B8G8R8A8_SRGB;
swapchainInfo.imageColorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR;
swapchainInfo.imageExtent = capabilities.currentExtent;
swapchainInfo.imageArrayLayers = 1;
swapchainInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
swapchainInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
swapchainInfo.preTransform = capabilities.currentTransform;
swapchainInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
swapchainInfo.presentMode = VK_PRESENT_MODE_MAILBOX_KHR;
swapchainInfo.clipped = VK_TRUE;
VkSwapchainKHR swapChain;
vkCreateSwapchainKHR(device, &swapchainInfo, nullptr, &swapChain);
vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr);
std::vector<VkImage> swapChainImages(imageCount);
vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data());

std::vector<VkImageView> swapChainImageViews(swapChainImages.size());
for (size_t i = 0; i < swapChainImages.size(); i++) {
VkImageViewCreateInfo createInfo{};
createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
createInfo.image = swapChainImages[i];
createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
createInfo.format = VK_FORMAT_B8G8R8A8_SRGB;
createInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY;
createInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY;
createInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY;
createInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY;
createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
createInfo.subresourceRange.baseMipLevel = 0;
createInfo.subresourceRange.levelCount = 1;
createInfo.subresourceRange.baseArrayLayer = 0;
createInfo.subresourceRange.layerCount = 1;
vkCreateImageView(device, &createInfo, nullptr, &swapChainImageViews[i]);
}

VkAttachmentDescription colorAttachment{};
colorAttachment.format = VK_FORMAT_B8G8R8A8_SRGB;
colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT;
colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
VkAttachmentReference colorAttachmentRef{};
colorAttachmentRef.attachment = 0;
colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
VkSubpassDescription subpass{};
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
subpass.colorAttachmentCount = 1;
subpass.pColorAttachments = &colorAttachmentRef;
VkSubpassDependency dependency{};
dependency.srcSubpass = VK_SUBPASS_EXTERNAL;
dependency.dstSubpass = 0;
dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
dependency.srcAccessMask = 0;
dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
VkRenderPassCreateInfo renderPassInfo{};
renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
renderPassInfo.attachmentCount = 1;
renderPassInfo.pAttachments = &colorAttachment;
renderPassInfo.subpassCount = 1;
renderPassInfo.pSubpasses = &subpass;
renderPassInfo.dependencyCount = 1;
renderPassInfo.pDependencies = &dependency;
VkRenderPass renderPass;
vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass);

//data
VkBufferCreateInfo bufferInfo{};
bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
bufferInfo.size = 3 * 2 * sizeof(float);
bufferInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT;
bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
VkBuffer vertexBuffer;
vkCreateBuffer(device, &bufferInfo, nullptr, &vertexBuffer);
VkMemoryRequirements memRequirements;
vkGetBufferMemoryRequirements(device, vertexBuffer, &memRequirements);
VkMemoryAllocateInfo vbAllocInfo{};
vbAllocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
vbAllocInfo.allocationSize = memRequirements.size;
VkPhysicalDeviceMemoryProperties memProperties;
vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memProperties);
for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) {
if ((memRequirements.memoryTypeBits & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & (VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)) == (VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)) {
vbAllocInfo.memoryTypeIndex = i;
break;
}
}
VkDeviceMemory vertexBufferMemory;
vkAllocateMemory(device, &vbAllocInfo, nullptr, &vertexBufferMemory);
vkBindBufferMemory(device, vertexBuffer, vertexBufferMemory, 0);
const float vertices[6] = {
0.0f, -0.5f,
0.5f, 0.5f,
-0.5f, 0.5f
};
void* data;
vkMapMemory(device, vertexBufferMemory, 0, bufferInfo.size, 0, &data);
memcpy(data, vertices, (size_t)bufferInfo.size);
vkUnmapMemory(device, vertexBufferMemory);

//shader
std::ifstream file("vert.spv", std::ios::ate | std::ios::binary);
size_t fileSize = (size_t)file.tellg();
std::vector<char> vertShaderCode(fileSize);
file.seekg(0);
file.read(vertShaderCode.data(), fileSize);
file.close();
VkShaderModuleCreateInfo vertCreateInfo{};
vertCreateInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
vertCreateInfo.codeSize = vertShaderCode.size();
vertCreateInfo.pCode = reinterpret_cast<const uint32_t*>(vertShaderCode.data());
VkShaderModule vertShaderModule;
vkCreateShaderModule(device, &vertCreateInfo, nullptr, &vertShaderModule);

file.open("frag.spv", std::ios::ate | std::ios::binary);
fileSize = (size_t)file.tellg();
std::vector<char> fragShaderCode(fileSize);
file.seekg(0);
file.read(fragShaderCode.data(), fileSize);
file.close();
VkShaderModuleCreateInfo fragCreateInfo{};
fragCreateInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
fragCreateInfo.codeSize = fragShaderCode.size();
fragCreateInfo.pCode = reinterpret_cast<const uint32_t*>(fragShaderCode.data());
VkShaderModule fragShaderModule;
vkCreateShaderModule(device, &fragCreateInfo, nullptr, &fragShaderModule);

VkPipelineShaderStageCreateInfo vertShaderStageInfo{};
vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT;
vertShaderStageInfo.module = vertShaderModule;
vertShaderStageInfo.pName = "main";
VkPipelineShaderStageCreateInfo fragShaderStageInfo{};
fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
fragShaderStageInfo.module = fragShaderModule;
fragShaderStageInfo.pName = "main";
VkPipelineShaderStageCreateInfo shaderStages[] = { vertShaderStageInfo, fragShaderStageInfo };
VkPipelineVertexInputStateCreateInfo vertexInputInfo{};
vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
VkVertexInputBindingDescription bindingDescription{};
bindingDescription.binding = 0;
bindingDescription.stride = 2 * sizeof(float);
bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
std::array<VkVertexInputAttributeDescription, 1> attributeDescriptions{};
attributeDescriptions[0].binding = 0;
attributeDescriptions[0].location = 0;
attributeDescriptions[0].format = VK_FORMAT_R32G32_SFLOAT;
attributeDescriptions[0].offset = (uint32_t)0;
vertexInputInfo.vertexBindingDescriptionCount = 1;
vertexInputInfo.vertexAttributeDescriptionCount = static_cast<uint32_t>(attributeDescriptions.size());
vertexInputInfo.pVertexBindingDescriptions = &bindingDescription;
vertexInputInfo.pVertexAttributeDescriptions = attributeDescriptions.data();

VkPipelineInputAssemblyStateCreateInfo inputAssembly{};
inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
inputAssembly.primitiveRestartEnable = VK_FALSE;
VkViewport viewport{};
viewport.x = 0.0f;
viewport.y = 0.0f;
viewport.width = WIDTH;
viewport.height = HEIGHT;
viewport.minDepth = 0.0f;
viewport.maxDepth = 1.0f;
VkRect2D scissor{};
scissor.offset = { 0, 0 };
scissor.extent = capabilities.currentExtent;
VkPipelineViewportStateCreateInfo viewportState{};
viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
viewportState.viewportCount = 1;
viewportState.pViewports = &viewport;
viewportState.scissorCount = 1;
viewportState.pScissors = &scissor;
VkPipelineRasterizationStateCreateInfo rasterizer{};
rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
rasterizer.depthClampEnable = VK_FALSE;
rasterizer.rasterizerDiscardEnable = VK_FALSE;
rasterizer.polygonMode = VK_POLYGON_MODE_FILL;
rasterizer.lineWidth = 1.0f;
rasterizer.cullMode = VK_CULL_MODE_BACK_BIT;
rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE;
rasterizer.depthBiasEnable = VK_FALSE;
VkPipelineMultisampleStateCreateInfo multisampling{};
multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
multisampling.sampleShadingEnable = VK_FALSE;
multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
VkPipelineColorBlendAttachmentState colorBlendAttachment{};
colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
colorBlendAttachment.blendEnable = VK_FALSE;
VkPipelineColorBlendStateCreateInfo colorBlending{};
colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
colorBlending.logicOpEnable = VK_FALSE;
colorBlending.logicOp = VK_LOGIC_OP_COPY;
colorBlending.attachmentCount = 1;
colorBlending.pAttachments = &colorBlendAttachment;
colorBlending.blendConstants[0] = 0.0f;
colorBlending.blendConstants[1] = 0.0f;
colorBlending.blendConstants[2] = 0.0f;
colorBlending.blendConstants[3] = 0.0f;
VkPipelineLayoutCreateInfo pipelineLayoutInfo{};
pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
pipelineLayoutInfo.setLayoutCount = 0;
pipelineLayoutInfo.pushConstantRangeCount = 0;
VkPipelineLayout pipelineLayout;
vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &pipelineLayout);
VkGraphicsPipelineCreateInfo pipelineInfo{};
pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
pipelineInfo.stageCount = 2;
pipelineInfo.pStages = shaderStages;
pipelineInfo.pVertexInputState = &vertexInputInfo;
pipelineInfo.pInputAssemblyState = &inputAssembly;
pipelineInfo.pViewportState = &viewportState;
pipelineInfo.pRasterizationState = &rasterizer;
pipelineInfo.pMultisampleState = &multisampling;
pipelineInfo.pColorBlendState = &colorBlending;
pipelineInfo.layout = pipelineLayout;
pipelineInfo.renderPass = renderPass;
pipelineInfo.subpass = 0;
pipelineInfo.basePipelineHandle = VK_NULL_HANDLE;
VkPipeline graphicsPipeline;
vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &graphicsPipeline);
vkDestroyShaderModule(device, fragShaderModule, nullptr);
vkDestroyShaderModule(device, vertShaderModule, nullptr);

std::vector<VkFramebuffer> swapChainFramebuffers(swapChainImageViews.size());
for (size_t i = 0; i < swapChainImageViews.size(); i++) {
VkImageView attachments[] = {
swapChainImageViews[i]
};
VkFramebufferCreateInfo framebufferInfo{};
framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
framebufferInfo.renderPass = renderPass;
framebufferInfo.attachmentCount = 1;
framebufferInfo.pAttachments = attachments;
framebufferInfo.width = WIDTH;
framebufferInfo.height = HEIGHT;
framebufferInfo.layers = 1;
vkCreateFramebuffer(device, &framebufferInfo, nullptr, &swapChainFramebuffers[i]);
}

VkCommandPoolCreateInfo poolInfo{};
poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
poolInfo.queueFamilyIndex = 0;
VkCommandPool commandPool;
vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool);

std::vector<VkCommandBuffer> commandBuffers(swapChainFramebuffers.size());
VkCommandBufferAllocateInfo allocInfo{};
allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
allocInfo.commandPool = commandPool;
allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
allocInfo.commandBufferCount = (uint32_t)commandBuffers.size();
vkAllocateCommandBuffers(device, &allocInfo, commandBuffers.data());
for (size_t i = 0; i < commandBuffers.size(); i++) {
VkCommandBufferBeginInfo beginInfo{};
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
vkBeginCommandBuffer(commandBuffers[i], &beginInfo);
VkRenderPassBeginInfo renderPassInfo{};
renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
renderPassInfo.renderPass = renderPass;
renderPassInfo.framebuffer = swapChainFramebuffers[i];
renderPassInfo.renderArea.offset = { 0, 0 };
renderPassInfo.renderArea.extent = capabilities.currentExtent;
VkClearValue clearColor = { {{0.0f, 0.0f, 0.0f, 1.0f}} };
renderPassInfo.clearValueCount = 1;
renderPassInfo.pClearValues = &clearColor;
vkCmdBeginRenderPass(commandBuffers[i], &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE);
vkCmdBindPipeline(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline);
VkBuffer vertexBuffers[] = { vertexBuffer };
VkDeviceSize offsets[] = { 0 };
vkCmdBindVertexBuffers(commandBuffers[i], 0, 1, vertexBuffers, offsets);
vkCmdDraw(commandBuffers[i], 3, 1, 0, 0);
vkCmdEndRenderPass(commandBuffers[i]);
vkEndCommandBuffer(commandBuffers[i]);
}

std::vector<VkSemaphore> imageAvailableSemaphores(2);
std::vector<VkSemaphore> renderFinishedSemaphores(2);
std::vector<VkFence> inFlightFences(2);
std::vector<VkFence> imagesInFlight(swapChainImages.size(), VK_NULL_HANDLE);
VkSemaphoreCreateInfo semaphoreInfo{};
semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
VkFenceCreateInfo fenceInfo{};
fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT;
for (size_t i = 0; i < 2; i++) {
vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAvailableSemaphores[i]);
vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderFinishedSemaphores[i]);
vkCreateFence(device, &fenceInfo, nullptr, &inFlightFences[i]);
}

//draw
uint32_t imageIndex = 0;
vkWaitForFences(device, 1, &inFlightFences[0], VK_TRUE, UINT64_MAX);
VkSubmitInfo submitInfo{};
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
VkSemaphore waitSemaphores[] = { imageAvailableSemaphores[0] };
VkPipelineStageFlags waitStages[] = { VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT };
submitInfo.waitSemaphoreCount = 1;
submitInfo.pWaitSemaphores = waitSemaphores;
submitInfo.pWaitDstStageMask = waitStages;
submitInfo.commandBufferCount = 1;
submitInfo.pCommandBuffers = &commandBuffers[0];
VkSemaphore signalSemaphores[] = { renderFinishedSemaphores[0] };
submitInfo.signalSemaphoreCount = 1;
submitInfo.pSignalSemaphores = signalSemaphores;
vkResetFences(device, 1, &inFlightFences[0]);
vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFences[0]);
VkPresentInfoKHR presentInfo{};
presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
presentInfo.waitSemaphoreCount = 1;
presentInfo.pWaitSemaphores = signalSemaphores;
VkSwapchainKHR swapChains[] = { swapChain };
presentInfo.swapchainCount = 1;
presentInfo.pSwapchains = swapChains;
presentInfo.pImageIndices = &imageIndex;
vkQueuePresentKHR(presentQueue, &presentInfo);

//event
while (!glfwWindowShouldClose(window)) {
glfwPollEvents();
}

//clear
//大量清除代码,实在不行写了
glfwDestroyWindow(window);
glfwTerminate();

return 0;
}

顶点着色器

1
2
3
4
5
6
7
#version 450

layout(location = 0) in vec2 inPosition;

void main() {
gl_Position = vec4(inPosition, 0.0, 1.0);
}

片段着色器

1
2
3
4
5
6
7
#version 450

layout(location = 0) out vec4 outColor;

void main() {
outColor = vec4(1.0, 1.0, 1.0, 1.0);
}

运行结果

接近400多行,而且还没写相关的清除代码,说实在的与其用这个,还不如dx12,错了,应该是OpenGL。对于OpenGL,目前我觉得缺少的一个功能就是提前对着色器的编译,而其它的功能,真得太够了。vulkan感觉就是跨平台版dx12的复刻,学啥不好,非得把这离谱的配置系统给学过来。vulkan需要依赖glfw这没得说,但清除真得太繁琐了,这是跨平台的劣势,还有一个就是这里对着色器代码的读取,文件管理属于平台相关的东西,所以并不能直接像dx12一样直接从文件获得着色器对象,而要通过C++标准库来读取文件,然后再转化为着色器对象。我们还要说一个事,官方的例子同样是封装过的,我是通过学习解剖才得到上面这些代码的,但千万别和我学,因为相关检测和调试代码全被我删了,也就是说硬件与我不同的话,极有可能出问题,我想表达什么呢?跨平台的本质应该是与硬件无关的,但现在的API却还要我们去自己进行相关的平台检测,自己选择相应设备,自己构造渲染队列,但是我们真得会去思考其中的优化处吗?就像之前所说,我只会把封装直接拿来,然后回到上一个时代。

结尾

到这里,这场旅行也该告一段落了,至于更老的API就算了吧。最后我需要找出适合自己开发研究的API了,一些低耗能的那必定是OpenGL了,跨平台和易用性这就够了,而高性能的嘛,当然是游戏引擎了,除了游戏难道有什么App会需要如此多的性能呢?不过有点值得肯定,我对新API并不看好,除非计算机的底层体系发生改变。