-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathatom.xml
411 lines (220 loc) · 975 KB
/
atom.xml
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
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>Sekyoro的博客小屋</title>
<link href="https://www.sekyoro.top/atom.xml" rel="self"/>
<link href="https://www.sekyoro.top/"/>
<updated>2025-01-09T14:57:52.654Z</updated>
<id>https://www.sekyoro.top/</id>
<author>
<name>Sekyoro</name>
</author>
<generator uri="https://hexo.io/">Hexo</generator>
<entry>
<title>前端运行时、打包与构建简单介绍</title>
<link href="https://www.sekyoro.top/2025/01/09/%E5%89%8D%E7%AB%AF%E6%9E%84%E5%BB%BA%E4%B8%8E%E6%89%93%E5%8C%85%E7%AE%80%E5%8D%95%E4%BB%8B%E7%BB%8D/"/>
<id>https://www.sekyoro.top/2025/01/09/%E5%89%8D%E7%AB%AF%E6%9E%84%E5%BB%BA%E4%B8%8E%E6%89%93%E5%8C%85%E7%AE%80%E5%8D%95%E4%BB%8B%E7%BB%8D/</id>
<published>2025-01-09T07:49:05.000Z</published>
<updated>2025-01-09T14:57:52.654Z</updated>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="\assets\js\Meting.min.js"></script><p>对比前两年,今年前端的工具进展相比可能确实慢了一些. 但还是有许多新的工具以及一些常用工具的新版本. 这里简单介绍一些在开发中关于js一些工具使用.</p><span id="more"></span><h2 id="JS三大运行时"><a href="#JS三大运行时" class="headerlink" title="JS三大运行时"></a>JS三大运行时</h2><p>js可以操作浏览器DOM以及浏览器提供的Web API等,这些都是在强大的浏览器下的功能. 除此之外还有最常用的node和较新的bun,Deno.</p><p>Deno和Bun的优点在于速度和对一些新特性,比如对typescript的支持更快.</p><p>此外Bun还致力于打造整个开发生态而不只是一个运行时。</p><h3 id="Deno"><a href="#Deno" class="headerlink" title="Deno"></a>Deno</h3><p>最近deno迎来2.0</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">greet</span>(<span class="params">name: <span class="built_in">string</span></span>): <span class="title">string</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="string">`Hello, <span class="subst">${name}</span>!`</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(greet(<span class="string">"world"</span>));</span><br></pre></td></tr></table></figure><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">deno main.ts</span><br></pre></td></tr></table></figure><h3 id="Bun"><a href="#Bun" class="headerlink" title="Bun"></a>Bun</h3><p>最近bun迎来1.1. 它提供一整套js和ts的开发工具,使用Bun作为运行时</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">bun run index.tsx </span><br></pre></td></tr></table></figure><h2 id="包管理器"><a href="#包管理器" class="headerlink" title="包管理器"></a>包管理器</h2><p>npm,yarn以及pnpm都是常用的包管理器,除此之外,还有bun install以及包管理器的管理器corepack. bun install是bun提供的包管理器,而Corepack是node官方做的包括npm,yarn,pnpm的管理器,目前新版的node是自带corepack并且应该是未来的新使用方式</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">corepack <span class="built_in">enable</span></span><br><span class="line">corepack use yarn@latest</span><br></pre></td></tr></table></figure><p>同时会在<code>package.json</code>中写入对应<code>`packageManager</code>字段</p><h2 id="打包工具"><a href="#打包工具" class="headerlink" title="打包工具"></a>打包工具</h2><p>打包工具(Bundler)主要目的是<code>compiles small pieces of code into something larger and more complex</code>,包括js,css以及图像等静态文件.</p><blockquote><p>打包工具的主要目标是将 JavaScript、CSS 等文件打包在一起,打包后的文件可以在浏览器、Node.js 等环境中使用。当 Bundler 处理 Web 应用时,它会构建一个依赖关系图,其中包含应用需要的各个模块,然后将所有模块打包成一个或多个 bundle</p></blockquote><h3 id="esbuild"><a href="#esbuild" class="headerlink" title="esbuild"></a>esbuild</h3><p><img data-src="https://s2.loli.net/2025/01/09/Rf7M9ecZYbOSyTC.png" alt="image-20250109201151034"></p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">npm install --save-exact --save-dev esbuild</span><br><span class="line">.\node_modules\.bin\esbuild --version</span><br><span class="line">.\node_modules\.bin\esbuild app.jsx --bundle --outfile=out.js <span class="comment"># 命令行</span></span><br></pre></td></tr></table></figure><p>esbuild优势是速度快,本身支持js,css,ts,jsx</p><p>通过配置文件</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> <span class="attr">"scripts"</span>: {</span><br><span class="line"> <span class="attr">"build"</span>: <span class="string">"esbuild app.jsx --bundle --outfile=out.js"</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>为浏览器js打包</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">esbuild app.jsx --bundle --minify --sourcemap --target=chrome58,firefox57,safari11,edge16</span><br></pre></td></tr></table></figure><p>默认情况下,打包器为浏览器输出代码,因此不需要额外的配置即可开始。对于开发构建,使用——sourcemap来启用源映射,而对于生产构建,使用——minify来启用最小化。</p><h3 id="Parcel"><a href="#Parcel" class="headerlink" title="Parcel"></a>Parcel</h3><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">yarn add --dev parcel</span><br></pre></td></tr></table></figure><p>类似esbuild,但功能更多,比如同时支持commonjs和ES模块的输出.</p><h3 id="Rollup"><a href="#Rollup" class="headerlink" title="Rollup"></a>Rollup</h3><p>rollup是将小的代码片段编译成更大、更复杂的代码,例如库或应用程序。它使用 JavaScript 的 ES6 版本中包含的新标准化代码模块格式。</p><p><strong>它适合ES模块编写的js打包</strong></p><p>Vite目前使用的打包工具,但马上将被换为RollDown. rollup突出一个使用简洁方便</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">rollup index.js -o bundle.js -f cjs</span><br></pre></td></tr></table></figure><p>此外也可以配置文件<code>rollup.config.mjs</code></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">// rollup.config.mjs</span><br><span class="line">// ---cut-start---</span><br><span class="line">/** @type {import('rollup').RollupOptions} */</span><br><span class="line">// ---cut-end---</span><br><span class="line">export default {</span><br><span class="line">input: 'src/main.js',</span><br><span class="line">output: {</span><br><span class="line">file: 'bundle.js',</span><br><span class="line">format: 'cjs'</span><br><span class="line">}</span><br><span class="line">};</span><br></pre></td></tr></table></figure><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">rollup -c</span><br></pre></td></tr></table></figure><h3 id="RollDown"><a href="#RollDown" class="headerlink" title="RollDown"></a>RollDown</h3><p><a href="https://rolldown.rs/guide/">Introduction | Rolldown</a></p><figure class="highlight gradle"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">npm install -D rolldown</span><br><span class="line">.<span class="regexp">/node_modules/</span>.bin<span class="regexp">/rolldown src/m</span>ain.js --<span class="keyword">file</span> bundle.js</span><br></pre></td></tr></table></figure><p>看起来跟esbuild,rollup没有很大差异.</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> <span class="attr">"name"</span>: <span class="string">"my-rolldown-project"</span>,</span><br><span class="line"> <span class="attr">"type"</span>: <span class="string">"module"</span>,</span><br><span class="line"> <span class="attr">"scripts"</span>: {</span><br><span class="line"> <span class="attr">"build"</span>: <span class="string">"rolldown src/main.js --file bundle.js"</span></span><br><span class="line"> },</span><br><span class="line"> <span class="attr">"devDependencies"</span>: {</span><br><span class="line"> <span class="attr">"rolldown"</span>: <span class="string">"^1.0.0-beta.1"</span></span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br></pre></td></tr></table></figure><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> <span class="attr">"name"</span>: <span class="string">"my-rolldown-project"</span>,</span><br><span class="line"> <span class="attr">"type"</span>: <span class="string">"module"</span>,</span><br><span class="line"> <span class="attr">"scripts"</span>: {</span><br><span class="line"> <span class="attr">"build"</span>: <span class="string">"rolldown -c"</span></span><br><span class="line"> },</span><br><span class="line"> <span class="attr">"devDependencies"</span>: {</span><br><span class="line"> <span class="attr">"rolldown"</span>: <span class="string">"^1.0.0-beta.1"</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="Rspack"><a href="#Rspack" class="headerlink" title="Rspack"></a>Rspack</h3><p>更新的工具,包含RsBuild,Rspress等生态.</p><h2 id="构建工具"><a href="#构建工具" class="headerlink" title="构建工具"></a>构建工具</h2><p>相对来说构建工具的包含概念更大,让更类似于一个项目启动器,可以负责整个项目开发各个阶段的整合,一些具体的处理可能需要使用一些打包、混淆压缩以及热更新.</p><h3 id="webpack"><a href="#webpack" class="headerlink" title="webpack"></a>webpack</h3><p>webpack有自带的许多loader<a href="https://www.webpackjs.com/concepts/loaders/">loader</a>以及插件<a href="https://www.webpackjs.com/concepts/plugins/">plugin </a>,功能强大,但配置相对复杂.目前使用Vite更多.</p><p><code>webpack.config.js</code></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> path = <span class="built_in">require</span>(<span class="string">'path'</span>);</span><br><span class="line"></span><br><span class="line"><span class="built_in">module</span>.exports = {</span><br><span class="line"> <span class="attr">entry</span>: <span class="string">'./src/index.js'</span>,</span><br><span class="line"> <span class="attr">output</span>: {</span><br><span class="line"> <span class="attr">filename</span>: <span class="string">'main.js'</span>,</span><br><span class="line"> <span class="attr">path</span>: path.resolve(__dirname, <span class="string">'dist'</span>),</span><br><span class="line"> },</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>使用loader,loader 用于对模块的源代码进行转换。loader 可以使你在 <code>import</code> 或 “load(加载)” 模块时预处理文件。因此,loader 类似于其他构建工具中“任务(task)”,并提供了处理前端构建步骤的得力方式。loader 可以将文件从不同的语言(如 TypeScript)转换为 JavaScript 或将内联图像转换为 data URL。loader 甚至允许直接在 JavaScript 模块中 <code>import</code> CSS 文件</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install --save-dev css-loader ts-loade</span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">module</span>.exports = {</span><br><span class="line"> <span class="attr">module</span>: {</span><br><span class="line"> <span class="attr">rules</span>: [</span><br><span class="line"> { <span class="attr">test</span>: <span class="regexp">/\.css$/</span>, use: <span class="string">'css-loader'</span> },</span><br><span class="line"> { <span class="attr">test</span>: <span class="regexp">/\.ts$/</span>, use: <span class="string">'ts-loader'</span> },</span><br><span class="line"> ],</span><br><span class="line"> },</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="Vite"><a href="#Vite" class="headerlink" title="Vite"></a>Vite</h3><p>Vite开箱即用,支持许多预设</p><p>插件</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">npm add -D @vitejs/plugin-legacy</span><br><span class="line"><span class="keyword">import</span> legacy <span class="keyword">from</span> <span class="string">'@vitejs/plugin-legacy'</span></span><br><span class="line"><span class="keyword">import</span> { defineConfig } <span class="keyword">from</span> <span class="string">'vite'</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> defineConfig({</span><br><span class="line"> <span class="attr">plugins</span>: [</span><br><span class="line"> legacy({</span><br><span class="line"> <span class="attr">targets</span>: [<span class="string">'defaults'</span>, <span class="string">'not IE 11'</span>],</span><br><span class="line"> }),</span><br><span class="line"> ],</span><br><span class="line">})</span><br></pre></td></tr></table></figure><h3 id="Rsbuild"><a href="#Rsbuild" class="headerlink" title="Rsbuild"></a>Rsbuild</h3><p><img data-src="https://assets.rspack.dev/rsbuild/assets/rsbuild-1-0-build-tools.png" alt="img"></p><p>可以将 Rsbuild 理解为一个现代化的 Create React App 或 Vue CLI,它与这些工具的主要区别在于:</p><ul><li>底层的打包工具由 Webpack 替换为 Rspack,提供 5 ~ 10 倍的构建性能。</li><li>与前端 UI 框架解耦,并通过 <a href="https://rsbuild.dev/zh/plugins/list/index">插件</a> 来支持所有 UI 框架,包括 React、Vue、Svelte、Solid 等。</li><li>提供更好的扩展性,你可以通过 <a href="https://rsbuild.dev/zh/config/index">配置</a>、 <a href="https://rsbuild.dev/zh/plugins/dev/index">插件 API</a> 和 <a href="https://rsbuild.dev/zh/api/start/index">JavaScript API</a> 来灵活地扩展 Rsbuild。</li></ul><h3 id="Farm"><a href="#Farm" class="headerlink" title="Farm"></a>Farm</h3><p>引用官方的话,Farm类似Webpack和Vite但更快. farm <code>resolve, load, transform</code> 所有 <code>asset(js/jsx/ts/tsx、css/sass/less、html、静态资源、json 等)</code>,并将它们打包成一系列<code>可部署文件</code>。 Farm 是一个速度极快的构建工具,可帮助您构建更快的 <code>web/nodejs</code> 应用程序。</p><h2 id="转码器"><a href="#转码器" class="headerlink" title="转码器"></a>转码器</h2><h3 id="Babel"><a href="#Babel" class="headerlink" title="Babel"></a>Babel</h3><p>babel用于ES2015+以上的ECMAScripit在较低版本的浏览器上兼容,此外也支持jsx和es模块转换</p><p>babel配置文件</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> <span class="attr">"presets"</span>: [</span><br><span class="line"> [</span><br><span class="line"> <span class="string">"@babel/preset-env"</span>,</span><br><span class="line"> {</span><br><span class="line"> <span class="attr">"targets"</span>: {</span><br><span class="line"> <span class="attr">"edge"</span>: <span class="string">"17"</span>,</span><br><span class="line"> <span class="attr">"firefox"</span>: <span class="string">"60"</span>,</span><br><span class="line"> <span class="attr">"chrome"</span>: <span class="string">"67"</span>,</span><br><span class="line"> <span class="attr">"safari"</span>: <span class="string">"11.1"</span></span><br><span class="line"> },</span><br><span class="line"> <span class="attr">"useBuiltIns"</span>: <span class="string">"usage"</span>,</span><br><span class="line"> <span class="attr">"corejs"</span>: <span class="string">"3.6.5"</span></span><br><span class="line"> }</span><br><span class="line"> ]</span><br><span class="line"> ]</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="SWC"><a href="#SWC" class="headerlink" title="SWC"></a>SWC</h3><p>Vite中所使用的代码转换器</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">pnpm add -D @swc/cli @swc/core</span><br><span class="line">npx swc ./file.js</span><br></pre></td></tr></table></figure><p>转译工具和构建工具的使用都不复杂,swc也支持配置支持浏览器,转换模块,压缩. 配置<code>.swcrc</code>文件</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> "$schema": "https://swc.nodejs.cn/schema.json",</span><br><span class="line"> "module": {</span><br><span class="line"> "type": "commonjs",</span><br><span class="line"> </span><br><span class="line"> // These are defaults.</span><br><span class="line"> "strict": false,</span><br><span class="line"> "strictMode": true,</span><br><span class="line"> "lazy": false,</span><br><span class="line"> "noInterop": false</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="Oxc"><a href="#Oxc" class="headerlink" title="Oxc"></a>Oxc</h3><p>与Rolldown类似,属于Vue,Vite生态圈. 它本身是一个提供了许多工具的总成,包括lint,parser,transformer和resolver</p><figure class="highlight ebnf"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">oxlint -c</span> </span><br></pre></td></tr></table></figure><p>此外值得一提的是考虑使用<a href="https://biomejs.dev/guides/getting-started/">Biome</a>作为prettier的替代工具,因为前者速度更快.</p><link rel="stylesheet" href="/css/spoiler.css" type="text/css"><script src="/js/spoiler.js" type="text/javascript" async></script>]]></content>
<summary type="html"><p>对比前两年,今年前端的工具进展相比可能确实慢了一些. 但还是有许多新的工具以及一些常用工具的新版本. 这里简单介绍一些在开发中关于js一些工具使用.</p></summary>
</entry>
<entry>
<title>Dive into fine-tuning methods for SD</title>
<link href="https://www.sekyoro.top/2025/01/02/Dive-into-fine-tuning-methods-for-SD/"/>
<id>https://www.sekyoro.top/2025/01/02/Dive-into-fine-tuning-methods-for-SD/</id>
<published>2025-01-02T02:08:06.000Z</published>
<updated>2025-01-02T08:16:26.050Z</updated>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="\assets\js\Meting.min.js"></script><p>文生图和图生图应用已经出现一段时间了,目前常用的应用就是根据用户需求修改图片,事实上这就是一种自定义. 因为需要模型重新生成整张图或者部分图,要么通过prompt、reference image,也就是改变输入的方式;要么通过修改模型,也就是微调模型的方式. 因此诞生出了许多微调模型的方式,目前常用的微调库是<a href="https://github.com/huggingface/peft">huggingface/peft: 🤗 PEFT</a>. 针对AI绘图应用的微调技术,倒是可以推出一道清晰的发展线. 这里简单整理一下.</p><span id="more"></span><h2 id="Before-LoRA"><a href="#Before-LoRA" class="headerlink" title="Before LoRA"></a>Before LoRA</h2><p>在大名鼎鼎的LoRA之前,绘画相关的微调技术主要有Textual Inversion<a href="https://arxiv.org/pdf/2208.01618">2208.01618</a>和DreamBooth<a href="https://arxiv.org/pdf/2208.12242">2208.12242</a>. 此外还有改变输入以及嵌入向量的Prefix Tuning<a href="https://arxiv.org/abs/2101.00190">Prefix-Tuning: Optimizing Continuous Prompts for Generation</a>和Prompt Tuning<a href="https://arxiv.org/abs/2104.08691">The Power of Scale for Parameter-Efficient Prompt Tuning</a>,由于与AI绘图相关微调联系不大,这里不深入.</p><h3 id="Textual-Inversion"><a href="#Textual-Inversion" class="headerlink" title="Textual Inversion"></a>Textual Inversion</h3><p> 文本到图像的模型为通过自然语言指导创作提供了前所未有的自由。然而,如何行使这种自由来生成特定独特概念的图像,修改它们的外观,或将它们组成新的角色和新的场景,尚不清楚。</p><p> 只使用用户提供的一个概念的3 - 5张图像,<strong>比如一个对象或一个样式,在一个冻结的文本到图像模型的嵌入空间中,我们学习通过新的”词”来表示它</strong>。这些”词”可以组合成自然语言句子,以直观的方式指导个性化创作。作者发现单个词嵌入足以捕获独特和多样的概念。</p><p> 将新概念引入模型的难点: 将新概念引入大规模模型往往是困难的。为每个新概念重新训练一个具有扩展数据集的模型是非常昂贵的,<strong>对少数例子进行微调通常会导致灾难性的遗忘</strong>。<strong>更多的度量方法在面对新概念时冻结模型并训练转换模块以适应其输出。然而,这些方法仍然容易遗忘先验知识,或者与新学习的概念融合时面临困难。</strong></p><p> 提出<strong>通过在预训练的文本到图像模型的文本嵌入空间中寻找新词来克服这些挑战</strong>。考虑文本编码过程的第一阶段。在这里输入字符串首先被转换为一组token(词元,理解为词典中的不可再分的词)。然后将每个token替换为自己的嵌入向量,这些向量通过下游模型进行反馈。<strong>目标是寻找新的嵌入向量来表示新的、特定的概念</strong>。</p><p> 用一个新的词(pseudo-word)表示一个新的嵌入向量,我们用S <em>表示。然后这个词像其他任何词一样被处理,并且可以用于为生成模型合成新的文本查询。因此,可以要求”一张沙滩上的S </em>照片”、”一幅挂在墙上的S <em>油画”,甚至可以组成两个概念,如”一幅S </em> 1的S * 2的画”。</p><p> 重要的是,这个过程没有触及生成模型。在这样做的过程中,我们保留了在新任务上微调视觉和语言模型时通常会丢失的丰富的文本理解和泛化能力。</p><p> 为了找到这些词,将任务定为求逆运算,<strong>给出了一个固定的预训练文本-图像模型和一个描述概念的小( 3-5 )图像集。目标是找到一个单词嵌入,这样’ A photo of S * ‘形式的句子将导致从我们的小集合中重建图像。这种嵌入是通过一个优化过程找到的,我们称之为”文本倒置”。</strong></p><p><img data-src="https://s2.loli.net/2025/01/02/B3wYEMNog5KPLa7.png" alt="image-20250102112045813"></p><p>具体来说,首先选取若干张相关概念的照片,假设pikachu,对应输入的prompt类似An image of pikachu, A photo of pikachu,然后就照着LDM训练的方式在原本的预训练大模型上继续训练即可.</p><h3 id="DreamBooth"><a href="#DreamBooth" class="headerlink" title="DreamBooth"></a>DreamBooth</h3><p><img data-src="https://s2.loli.net/2025/01/02/CjIZsPOXFoLDYai.png" alt="image-20250102121433867"></p><p> DreamBooth的目的和Textual Inversion类似,扩展模型的语言-视觉词典,使其能够将新词与用户想要生成的特定主题绑定在一起。 </p><p> 给定一个主体的几幅图像,目标是将主体植入模型的输出域,使其能够用唯一的标识符进行合成。为此提出一种技术,用稀有的标记标识符表示给定的主题,并微调一个预训练的、基于扩散的文本到图像框架。</p><p> 给定一个对象的3 - 5图像,微调一个文本到图像的扩散模型,输入图像与一个包含唯一标识符且对象所属类名为( e.g . , ‘ A [ V ] dog ‘)的文本提示配对,并行地应用一个特定于类的先验保存损失,该损失利用了模型在类上的语义先验,并鼓励它使用文本提示中的类名生成属于该对象类的多样实例。</p><p> 相比于textual inversion,它对模型而不只是embedding进行了微调,同时增加了新的损失, 对于同类但不同具体实例的图像进行训练.</p><script type="math/tex; mode=display">\begin{array}{l} \mathbb{E}_{\mathbf{x}, \mathbf{c}, \epsilon, \epsilon^{\prime}, t}\left[w_{t}\left\|\hat{\mathbf{x}}_{\theta}\left(\alpha_{t} \mathbf{x}+\sigma_{t} \boldsymbol{\epsilon}, \mathbf{c}\right)-\mathbf{x}\right\|_{2}^{2}+\right. \\\left.\lambda w_{t^{\prime}}\left\|\hat{\mathbf{x}}_{\theta}\left(\alpha_{t^{\prime}} \mathbf{x}_{\mathrm{pr}}+\sigma_{t^{\prime}} \epsilon^{\prime}, \mathbf{c}_{\mathrm{pr}}\right)-\mathbf{x}_{\mathrm{pr}}\right\|_{2}^{2}\right]\end{array}</script><h2 id="LoRA-era"><a href="#LoRA-era" class="headerlink" title="LoRA era"></a>LoRA era</h2><p>如果说上面几种方法都集中通过新的输入修改embedding,那么LoRA就是修改其中涉及重要计算模块的部分,比如Linear或Conv层 .</p><p>LoRA作为AI绘画模型微调技术不得不提的一环,一经提出就带来了一股LoRA潮<a href="https://towardsdatascience.com/an-overview-of-the-lora-family-515d81134725">An Overview of the LoRA Family. LoRA, DoRA, AdaLoRA, Delta-LoRA, and… | by Dorian Drost | Towards Data Science</a>. </p><p>此外LoRA也可以结合上面的修改embedding方法达到更好的效果.</p><h3 id="LoRA"><a href="#LoRA" class="headerlink" title="LoRA"></a>LoRA</h3><p><img data-src="https://s2.loli.net/2025/01/02/FogSW1IwGdLzfVK.png" alt="image-20250102132811471"></p><p>神经网络包含许多执行矩阵乘法的密集层。这些层中的权重矩阵通常具有满秩。在适应特定任务时,Aghajanyan等研究表明,<strong>预训练的语言模型具有较低的”特征维度”(rank),即使随机投影到较小的子空间,仍然可以高效地学习</strong>。受此启发,<strong>假设权重的更新在适应过程中也具有较低的”内在秩”</strong>。对于一个预训练的权重矩阵W^0^∈R^d×k^,用一个低秩分解W~0~ + $\Delta$W = W~0~ + BA来约束它的更新,其中B∈R^d×r^,A∈R^r×k^,秩为r=min( d , k)。在训练过程中,W^0^被冻结,不接受梯度更新,而A和B包含可训练参数。</p><p> 注意W~0~和∆W = BA都乘以相同的输入,并且它们各自的输出向量按位求和。</p><p>对于h = W~0~x,修正前向传递得到h = W~0~x +∆W x = W~0~x + BAx</p><p> 对A使用随机高斯初始化,对B使用零初始化,因此在训练开始时,W = BA为零。然后用α/r对∆W x进行缩放,其中α为r中的常数。</p><p> 在使用Adam进行优化时,如果对初始化进行适当的缩放,则调整α与调整学习率大致相同。因此,简单地将α设置为r,并不对其进行调整。这种缩放有助于减少当我们改变r时重新调整超参数的需要.</p><p> 在训练时,加上lora,冻结预训练模型,训练一个包含A和B的MLP,它更新参数时需要减去这个模型的参数</p><p> 测试时,模型的权重要加上LoRA的参数.</p><p> LoRA本身修改了微调时更新权重的方式,将更新的权重放到了一个可拆卸的模块中,同时由于只搭配预训练大模型中的某部分,使得微调过程更短,训练周期更短.</p><h4 id="代码实现"><a href="#代码实现" class="headerlink" title="代码实现"></a>代码实现</h4><p>参考<a href="https://github.com/microsoft/LoRA/blob/main/loralib/layers.py">LoRA/loralib/layers.py at main · microsoft/LoRA</a></p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">LoRALayer</span>():</span></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">__init__</span>(<span class="params"></span></span></span><br><span class="line"><span class="params"><span class="function"> self, </span></span></span><br><span class="line"><span class="params"><span class="function"> r: <span class="built_in">int</span>, </span></span></span><br><span class="line"><span class="params"><span class="function"> lora_alpha: <span class="built_in">int</span>, </span></span></span><br><span class="line"><span class="params"><span class="function"> lora_dropout: <span class="built_in">float</span>,</span></span></span><br><span class="line"><span class="params"><span class="function"> merge_weights: <span class="built_in">bool</span>,</span></span></span><br><span class="line"><span class="params"><span class="function"> </span>):</span></span><br><span class="line"> self.r = r</span><br><span class="line"> self.lora_alpha = lora_alpha</span><br><span class="line"> <span class="comment"># Optional dropout</span></span><br><span class="line"> <span class="keyword">if</span> lora_dropout > <span class="number">0.</span>:</span><br><span class="line"> self.lora_dropout = nn.Dropout(p=lora_dropout)</span><br><span class="line"> <span class="keyword">else</span>:</span><br><span class="line"> self.lora_dropout = <span class="keyword">lambda</span> x: x</span><br><span class="line"> <span class="comment"># Mark the weight as unmerged</span></span><br><span class="line"> self.merged = <span class="literal">False</span></span><br><span class="line"> self.merge_weights = merge_weights</span><br></pre></td></tr></table></figure><p>对于Embedding,Linear以及Conv层有不同的具体实现. 但总体来说,在训练时,通过减去B@A更新参数. 在测试时权重加上LoRA层.</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Linear</span>(<span class="params">nn.Linear, LoRALayer</span>):</span></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">__init__</span>(<span class="params">self,</span></span></span><br><span class="line"><span class="params"><span class="function"> in_features: <span class="built_in">int</span>,</span></span></span><br><span class="line"><span class="params"><span class="function"> out_features: <span class="built_in">int</span>,</span></span></span><br><span class="line"><span class="params"><span class="function"> r: <span class="built_in">int</span> = <span class="number">0</span>,</span></span></span><br><span class="line"><span class="params"><span class="function"> lora_alpha: <span class="built_in">int</span> = <span class="number">1</span>,</span></span></span><br><span class="line"><span class="params"><span class="function"> lora_dropout: <span class="built_in">float</span> = <span class="number">0.</span>,</span></span></span><br><span class="line"><span class="params"><span class="function"> fan_in_fan_out: <span class="built_in">bool</span> = <span class="literal">False</span>,</span></span></span><br><span class="line"><span class="params"><span class="function"> <span class="comment"># Set this to True if the layer to replace stores weight like (fan_in, fan_out)</span></span></span></span><br><span class="line"><span class="params"><span class="function"> merge_weights: <span class="built_in">bool</span> = <span class="literal">True</span>,</span></span></span><br><span class="line"><span class="params"><span class="function"> **kwargs</span>):</span></span><br><span class="line"> nn.Linear.__init__(self, in_features, out_features, **kwargs)</span><br><span class="line"> LoRALayer.__init__(self, r=r, lora_alpha=lora_alpha, lora_dropout=<span class="number">0</span>, merge_weights=merge_weights)</span><br><span class="line"> self.fan_in_fan_out = fan_in_fan_out</span><br><span class="line"> <span class="keyword">if</span> r > <span class="number">0</span>:</span><br><span class="line"> self.lora_A = nn.Parameter(self.weight.new_zeros((r, in_features)))</span><br><span class="line"> self.lora_B = nn.Parameter(self.weight.new_zeros((out_features, r)))</span><br><span class="line"> self.scaling = self.lora_alpha / self.r</span><br><span class="line"> self.weight.requires_grad = <span class="literal">False</span> <span class="comment"># 禁止梯度更新</span></span><br><span class="line"> self.reset_parameters()</span><br><span class="line"> <span class="keyword">if</span> fan_in_fan_out:</span><br><span class="line"> self.weight.data = self.weight.data.transpose(<span class="number">0</span>, <span class="number">1</span>)</span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="LyCORIS"><a href="#LyCORIS" class="headerlink" title="LyCORIS"></a>LyCORIS</h3><p><a href="https://arxiv.org/abs/2309.14859">2309.14859] Navigating Text-To-Image Customization: From LyCORIS Fine-Tuning to Model Evaluation</a></p><p>提出了一系列用在Stable Diffusion中基于LoRA的微调方式并设计了benchmark测试</p><p><img data-src="https://s2.loli.net/2025/01/02/KEoVxDuY3IiJOar.png" alt="image-20250102152745443"></p><p>主要包括LoHA和LoKr,其实光看图就很容易明白. LoHA另外引入一套BA,并通过点乘得到最终的更新,论文解释这种方法得到的矩阵的秩大于一般的低秩分解. 而LoKr就直接使用矩阵直积了.</p><p>这方面还有很多魔改的各种微调,就不一一介绍了.</p><h2 id="Adapters-for-preserving-Identity"><a href="#Adapters-for-preserving-Identity" class="headerlink" title="Adapters for preserving Identity"></a>Adapters for preserving Identity</h2><p>Adapter的实现和LoRA有类似点,但提出的目的不同与LoRA:LoRA强调用户拿几张个人照片让模型学习新的权重同时不过于遗忘已有知识,而Adapter的场景更偏向适应与融合. </p><h3 id="IP-Adapter"><a href="#IP-Adapter" class="headerlink" title="IP-Adapter"></a>IP-Adapter</h3><p><a href="https://github.com/tencent-ailab/IP-Adapter?tab=readme-ov-file">tencent-ailab/IP-Adapter: The image prompt adapter is designed to enable a pretrained text-to-image diffusion model to generate images with image prompt.</a></p><p><img data-src="https://s2.loli.net/2025/01/02/sItZgfVkolGeN4b.png" alt="image-20250102155440556"></p><p>针对现有的文本到图像扩散模型,提出了一种基于解耦交叉注意力策略的轻量级图像提示自适应方法IP-Adapter。</p><p>在图像编码器之后加入新的权重参数进行编码,并将编码后的特征用于训练一个新的cross attention layer. 论文的关键创新就是引入cross attention将图像特征用来微调模型.</p><p>给定图像特征c~i~,新的交叉注意力Z′′的输出计算如下</p><script type="math/tex; mode=display">\mathbf{Z}^{\prime \prime}=\operatorname{Attention}\left(\mathbf{Q}, \mathbf{K}^{\prime}, \mathbf{V}^{\prime}\right)=\operatorname{Softmax}\left(\frac{\mathbf{Q}\left(\mathbf{K}^{\prime}\right)^{\top}}{\sqrt{d}}\right) \mathbf{V}^{\prime}</script><p>其中Q=ZW~q~,Z是文本编码后的特征,K^’^=cW~k~,c是图像特征,V=cV~v~</p><p><img data-src="https://s2.loli.net/2025/01/02/RAhWYImDw6H2cpM.png" alt="image-20250102161435821"></p><h3 id="InstantID"><a href="#InstantID" class="headerlink" title="InstantID"></a>InstantID</h3><p><a href="https://github.com/instantX-research/InstantID?tab=readme-ov-file">instantX-research/InstantID: InstantID: Zero-shot Identity-Preserving Generation in Seconds 🔥</a></p><p><img data-src="https://s2.loli.net/2025/01/02/pBcPXm7JDOZL6It.png" alt="image-20250102155632207"></p><p>首先,采用人脸编码器代替CLIP提取语义人脸特征,并使用可训练的投影层将其投影到文本特征空间。将投影后的特征作为人脸嵌入。然后,引入解耦交叉注意力的轻量级自适应模块,以支持图像作为提示。最后提出IdentityNet对参考人脸图像中的复杂特征进行编码,并附加弱空间控制。在IdentityNet中,生成过程完全由人脸嵌入引导,无需任何文本信息。只更新新增加的模块,而预训练的文本到图像模型保持冻结,以确保灵活性。经过训练,用户可以自由地生成任意风格的高保真度的ID保持图像。</p><h2 id="Towards-Video-Diffusion-Models"><a href="#Towards-Video-Diffusion-Models" class="headerlink" title="Towards Video Diffusion Models"></a>Towards Video Diffusion Models</h2><p> <a href="https://github.com/ChenHsing/Awesome-Video-Diffusion-Models">CSUR] A Survey on Video Diffusion Models</a></p><p>生成图像已经不够了,可以通过prompt生成连续的多张图片,这样可以组成GIF甚至更长更高质量的视频.</p><p>比如<strong>AnimateDiff</strong></p><p><img data-src="https://s2.loli.net/2025/01/02/WEG5I47BVre3yA1.png" alt="image-20250102140534169"></p><h2 id="相关资料"><a href="#相关资料" class="headerlink" title="相关资料"></a>相关资料</h2><ol><li><a href="https://zhuanlan.zhihu.com/p/675231376">一文辨析清楚LORA、Prompt Tuning、P-Tuning、Adapter 、Prefix等大模型微调方法 - 知乎</a></li><li><a href="https://www.artvy.ai/resource/lora-vs-dreambooth-vs-textual-inversion-vs">LoRA vs Dreambooth vs Textual Inversion vs Hypernetworks: Exploring the World of Stable Diffusion Fine-Tuning Methods</a></li><li><a href="https://openreview.net/forum?id=wfzXa8e783">Navigating Text-To-Image Customization: From LyCORIS Fine-Tuning to Model Evaluation | OpenReview</a></li><li><a href="https://github.com/KohakuBlueleaf/LyCORIS?tab=readme-ov-file">KohakuBlueleaf/LyCORIS: Lora beYond Conventional methods, Other Rank adaptation Implementations for Stable diffusion.</a></li><li><a href="https://towardsdatascience.com/an-overview-of-the-lora-family-515d81134725">An Overview of the LoRA Family. LoRA, DoRA, AdaLoRA, Delta-LoRA, and… | by Dorian Drost | Towards Data Science</a></li><li><a href="https://arxiv.org/pdf/2106.09685">2106.09685</a></li><li><a href="https://arxiv.org/abs/2309.14859">[2309.14859] Navigating Text-To-Image Customization: From LyCORIS Fine-Tuning to Model Evaluation</a></li><li><a href="https://arxiv.org/pdf/2307.04725">2307.04725</a></li></ol><link rel="stylesheet" href="/css/spoiler.css" type="text/css"><script src="/js/spoiler.js" type="text/javascript" async></script>]]></content>
<summary type="html"><p>文生图和图生图应用已经出现一段时间了,目前常用的应用就是根据用户需求修改图片,事实上这就是一种自定义. 因为需要模型重新生成整张图或者部分图,要么通过prompt、reference image,也就是改变输入的方式;要么通过修改模型,也就是微调模型的方式. 因此诞生出了许多微调模型的方式,目前常用的微调库是<a href="https://github.com/huggingface/peft">huggingface/peft: 🤗 PEFT</a>. 针对AI绘图应用的微调技术,倒是可以推出一道清晰的发展线. 这里简单整理一下.</p></summary>
</entry>
<entry>
<title>An intro to Websocket and SSE</title>
<link href="https://www.sekyoro.top/2024/12/31/An-intro-to-Websocket-and-SSE/"/>
<id>https://www.sekyoro.top/2024/12/31/An-intro-to-Websocket-and-SSE/</id>
<published>2024-12-31T11:29:32.000Z</published>
<updated>2024-12-31T15:44:07.739Z</updated>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="\assets\js\Meting.min.js"></script><p>最近在看LLM的流式输出如何反映到json输出上,现有的解决方案包括 WebSocket 或 Server-Sent Events (SSE) 实时通信技术.<br><span id="more"></span></p><h2 id="WebSocket"><a href="#WebSocket" class="headerlink" title="WebSocket"></a>WebSocket</h2><p><strong>WebSocket API</strong> 可在用户浏览器和服务器之间开启双向交互式通信会话。利用该 API,可以向服务器发送信息,并接收事件驱动的响应,而无需轮询服务器以获得回复。</p><p><img data-src="https://www.ruanyifeng.com/blogimg/asset/2017/bg2017051502.png" alt="img"></p><p>(1)建立在 TCP 协议之上,服务器端的实现比较容易。</p><p>(2)与 HTTP 协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。</p><p>(3)数据格式比较轻量,性能开销小,通信高效。</p><p>(4)可以发送文本,也可以发送二进制数据。</p><p>(5)没有同源限制,客户端可以与任意服务器通信。</p><p>(6)协议标识符是<code>ws</code>(如果加密,则为<code>wss</code>),服务器网址就是 URL。</p><p><strong>客户端</strong></p><figure class="highlight qml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> webSocket = <span class="keyword">new</span> WebSocket(<span class="built_in">url</span>, protocols);</span><br><span class="line">ws.onopen = <span class="function"><span class="keyword">function</span>(<span class="params">evt</span>) </span>{ </span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">"Connection open ..."</span>); </span><br><span class="line"> ws.send(<span class="string">"Hello WebSockets!"</span>);</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line">ws.onmessage = <span class="function"><span class="keyword">function</span>(<span class="params">evt</span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log( <span class="string">"Received Message: "</span> + evt.data);</span><br><span class="line"> ws.close();</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line">ws.onclose = <span class="function"><span class="keyword">function</span>(<span class="params">evt</span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">"Connection closed."</span>);</span><br><span class="line">}; </span><br><span class="line"></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>发送JSON</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 服务器向所有用户发送文本</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">sendText</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="comment">// 构造一个 msg 对象,包含了服务器处理所需的数据</span></span><br><span class="line"> <span class="keyword">var</span> msg = {</span><br><span class="line"> <span class="attr">type</span>: <span class="string">"message"</span>,</span><br><span class="line"> <span class="attr">text</span>: <span class="built_in">document</span>.getElementById(<span class="string">"text"</span>).value,</span><br><span class="line"> <span class="attr">id</span>: clientID,</span><br><span class="line"> <span class="attr">date</span>: <span class="built_in">Date</span>.now(),</span><br><span class="line"> };</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 把 msg 对象作为 JSON 格式字符串发送</span></span><br><span class="line"> exampleSocket.send(<span class="built_in">JSON</span>.stringify(msg));</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 清空文本输入元素,为接收下一条消息做好准备。</span></span><br><span class="line"> <span class="built_in">document</span>.getElementById(<span class="string">"text"</span>).value = <span class="string">""</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><strong>服务端</strong></p><p>可以使用<a href="https://socket.io/">Socket.IO</a>,</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> { Server } = <span class="built_in">require</span>(<span class="string">"socket.io"</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> io = <span class="keyword">new</span> Server({ <span class="comment">/* options */</span> });</span><br><span class="line"></span><br><span class="line">io.on(<span class="string">"connection"</span>, <span class="function">(<span class="params">socket</span>) =></span> {</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line">});</span><br><span class="line"></span><br><span class="line">io.listen(<span class="number">3000</span>);</span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> { readFileSync } = <span class="built_in">require</span>(<span class="string">"fs"</span>);</span><br><span class="line"><span class="keyword">const</span> { createServer } = <span class="built_in">require</span>(<span class="string">"https"</span>);</span><br><span class="line"><span class="keyword">const</span> { Server } = <span class="built_in">require</span>(<span class="string">"socket.io"</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> httpsServer = createServer({</span><br><span class="line"> <span class="attr">key</span>: readFileSync(<span class="string">"/path/to/my/key.pem"</span>),</span><br><span class="line"> <span class="attr">cert</span>: readFileSync(<span class="string">"/path/to/my/cert.pem"</span>)</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> io = <span class="keyword">new</span> Server(httpsServer, { <span class="comment">/* options */</span> });</span><br><span class="line"></span><br><span class="line">io.on(<span class="string">"connection"</span>, <span class="function">(<span class="params">socket</span>) =></span> {</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line">});</span><br><span class="line"></span><br><span class="line">httpsServer.listen(<span class="number">3000</span>);</span><br></pre></td></tr></table></figure><h2 id="SSE"><a href="#SSE" class="headerlink" title="SSE"></a>SSE</h2><p>HTTP 协议本身不允许服务器主动给客户端发送信息,但有一种方法可以让服务器持续向客户端发送数据流。这种方法不是发送一次性数据包,而是保持连接开放,像视频播放那样连续发送数据。这种长时间的下载过程实际上是以数据流的形式进行的。</p><p>服务器发送事件(SSE)就是利用了这个特性,通过HTTP协议让服务器可以向浏览器推送实时更新的信息。传统上,网页需要先请求服务器才能获取新数据,但是使用SSE,服务器可以在任何时候主动向网页推送新的数据和消息,这些消息可以在网页内作为事件来处理。</p><p>服务端推送的数据是单向的,只从服务器到客户端流动。当不需要从客户端向服务器发送信息时,比如更新社交媒体状态、新闻推送或把数据传送到客户端存储(如IndexedDB或Web Storage),SSE就非常适合。</p><p>与SSE不同的是,WebSocket提供了一个更强大的双向通信通道,允许客户端和服务器之间互相发送信息。SSE则是单向的,主要用于服务器向浏览器发送信息。如果浏览器需要向服务器发送信息,它必须发起一个新的HTTP请求。</p><p>这里有几个关于SSE和WebSocket的区别:</p><ul><li>SSE基于HTTP,因此所有现有的服务器软件都能支持它;而WebSocket是一个独立的协议。</li><li>SSE设置起来简单得多,适合轻量级应用;WebSocket则更为复杂。</li><li>SSE自带断线重连功能,而WebSocket需要开发者自己实现这一功能。</li><li>SSE主要用于传输文本数据,若要发送二进制数据则需编码;WebSocket直接支持二进制数据传输。</li><li>SSE允许自定义消息类型,增加了灵活性。</li></ul><h3 id="事件流格式"><a href="#事件流格式" class="headerlink" title="事件流格式"></a>事件流格式</h3><p>事件流是一个简单的文本数据流,文本应该使用UTF-8格式的编码。事件流中的消息由一对换行符分开。以冒号开头的行为注释行,会被忽略。</p><p><strong>备注:</strong> 注释行可以用来防止连接超时,服务器可以定期发送一条消息注释行,以保持连接不断。</p><p>每条消息由一行或多行文字组成,列出该消息的字段。每个字段由字段名表示,后面是冒号,然后是该字段值的文本数据。</p><p>规范中规定了下面这些字段:</p><ul><li><p>event</p><p>一个用于标识事件类型的字符串。如果指定了这个字符串,浏览器会将具有指定事件名称的事件分派给相应的监听器;网站源代码应该使用 <code>addEventListener()</code> 来监听指定的事件。如果一个消息没有指定事件名称,那么 <code>onmessage</code> 处理程序就会被调用。</p></li><li><p>data</p><p>消息的数据字段。当 <code>EventSource</code> 接收到多个以 <code>data:</code> 开头的连续行时,<a href="https://html.spec.whatwg.org/multipage/#dispatchMessage">会将它们连接起来</a>,在它们之间插入一个换行符。末尾的换行符会被删除。</p></li><li><p>id</p><p>事件 ID,会成为当前 <a href="https://developer.mozilla.org/zh-CN/docs/Web/API/EventSource"><code>EventSource</code></a> 对象的内部属性“最后一个事件 ID”的属性值。</p></li><li><p>retry</p><p>重新连接的时间。如果与服务器的连接丢失,浏览器将等待指定的时间,然后尝试重新连接。这必须是一个整数,以毫秒为单位指定重新连接的时间。如果指定了一个非整数值,该字段将被忽略。</p></li></ul><p>所有其他的字段名都会被忽略</p><h3 id="事件流具体例子"><a href="#事件流具体例子" class="headerlink" title="事件流具体例子"></a>事件流具体例子</h3><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">Content-Type: text/event-stream</span><br><span class="line">Cache-Control: no-cache</span><br><span class="line">Connection: keep-alive</span><br></pre></td></tr></table></figure><p>每一次发送的信息,由若干个<code>message</code>组成,每个<code>message</code>之间用<code>\n\n</code>分隔。每个<code>message</code>内部由若干行组成,每一行都是如下格式。</p><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">[<span class="symbol">field</span>]: <span class="link">value\n</span></span><br></pre></td></tr></table></figure><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">: comment</span><br><span class="line">id: 2025\n </span><br><span class="line">event: foo\n</span><br><span class="line">retry: 100\n \\指定浏览器重新发起连接的时间间隔。</span><br><span class="line">data: This is the mesage\n</span><br><span class="line">data: test\n</span><br><span class="line">data: this is the end\n\n</span><br></pre></td></tr></table></figure><p>在浏览器上<code>lastEventId</code>属性读取这个值。一旦连接断线,浏览器会发送一个 HTTP 头,里面包含一个特殊的<code>Last-Event-ID</code>头信息,将这个值发送回来,用来帮助服务器端重建连接。因此,这个头信息可以被视为一种同步机制。</p><h4 id="命名事件"><a href="#命名事件" class="headerlink" title="命名事件"></a>命名事件</h4><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">event: userconnect</span><br><span class="line">data: {"username": "bobby", "time": "02:33:48"}</span><br><span class="line"></span><br><span class="line">event: usermessage</span><br><span class="line">data: {"username": "bobby", "time": "02:34:11", "text": "Hi everyone."}</span><br><span class="line"></span><br><span class="line">event: userdisconnect</span><br><span class="line">data: {"username": "bobby", "time": "02:34:23"}</span><br><span class="line"></span><br><span class="line">event: usermessage</span><br><span class="line">data: {"username": "sean", "time": "02:34:36", "text": "Bye, bobby."}</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>每个事件都有一个由 <code>event</code> 字段指定的事件名称和一个 <code>data</code> 字段,其值是一个适当的 JSON 字符串,包含客户端对该事件采取行动所需的数据。<code>data</code> 字段可以包含任何字符串数据,它不一定是 JSON。</p><h4 id="混合两种事件"><a href="#混合两种事件" class="headerlink" title="混合两种事件"></a>混合两种事件</h4><p>可以在一个事件流中同时使用命名事件和未命名事件。</p><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">event: userconnect</span><br><span class="line">data: {"username": "bobby", "time": "02:33:48"}</span><br><span class="line"></span><br><span class="line">data: Here's a system message of some kind that will get used</span><br><span class="line">data: to accomplish some task.</span><br><span class="line"></span><br><span class="line">event: usermessage</span><br><span class="line">data: {"username": "bobby", "time": "02:34:11", "text": "Hi everyone."}</span><br></pre></td></tr></table></figure><p><strong>客户端</strong></p><p>默认情况下,如果客户端和服务器之间的连接关闭,则连接将重新启动。可以使用 <code>.close()</code> 方法终止连接。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> evtSource = <span class="keyword">new</span> EventSource(<span class="string">"xxx"</span>);<span class="comment">//url可以与当前网址同域,也可以跨域。</span></span><br><span class="line">evtSource.onmessage = <span class="function"><span class="keyword">function</span>(<span class="params">event</span>) </span>{</span><br><span class="line"> xxx</span><br><span class="line">}</span><br><span class="line">evtSource.onerror = <span class="function">(<span class="params">err</span>) =></span> {</span><br><span class="line"> <span class="built_in">console</span>.error(<span class="string">"EventSource failed:"</span>, err);</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="comment">//自定义事件</span></span><br><span class="line">source.addEventListener(<span class="string">'foo'</span>, <span class="function"><span class="keyword">function</span> (<span class="params">event</span>) </span>{</span><br><span class="line"> <span class="keyword">var</span> data = event.data;</span><br><span class="line"> <span class="comment">// handle message</span></span><br><span class="line">}, <span class="literal">false</span>);</span><br><span class="line"></span><br></pre></td></tr></table></figure><p><strong>服务端</strong></p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line">date_default_timezone_set(<span class="string">"America/New_York"</span>);</span><br><span class="line">header(<span class="string">"Cache-Control: no-store"</span>);</span><br><span class="line">header(<span class="string">"Content-Type: text/event-stream"</span>);</span><br><span class="line"></span><br><span class="line"><span class="variable">$counter</span> = rand(<span class="number">1</span>, <span class="number">10</span>);</span><br><span class="line"><span class="keyword">while</span> (<span class="literal">true</span>) {</span><br><span class="line"> <span class="comment">// Every second, send a "ping" event.</span></span><br><span class="line"> <span class="keyword">echo</span> <span class="string">"event: ping\n"</span>; <span class="comment"># 声明事件</span></span><br><span class="line"> <span class="variable">$curDate</span> = date(DATE_ISO8601);</span><br><span class="line"> <span class="keyword">echo</span> <span class="string">'data: {"time": "'</span> . <span class="variable">$curDate</span> . <span class="string">'"}'</span>;</span><br><span class="line"> <span class="keyword">echo</span> <span class="string">"\n\n"</span>; <span class="comment"># 一个事件结束</span></span><br><span class="line"> <span class="comment">// Send a simple message at random intervals.</span></span><br><span class="line"> <span class="variable">$counter</span>--;</span><br><span class="line"> <span class="keyword">if</span> (!<span class="variable">$counter</span>) {</span><br><span class="line"> <span class="keyword">echo</span> <span class="string">'data: This is a message at time '</span> . <span class="variable">$curDate</span> . <span class="string">"\n\n"</span>; <span class="comment"># 默认onmessage事件处理</span></span><br><span class="line"> <span class="variable">$counter</span> = rand(<span class="number">1</span>, <span class="number">10</span>);</span><br><span class="line"> }</span><br><span class="line"> ob_end_flush();</span><br><span class="line"> flush();</span><br><span class="line"> <span class="comment">// Break the loop if the client aborted the connection (closed the page)</span></span><br><span class="line"> <span class="keyword">if</span> (connection_aborted()) <span class="keyword">break</span>;</span><br><span class="line"> sleep(<span class="number">1</span>);</span><br><span class="line">}</span><br><span class="line"></span><br></pre></td></tr></table></figure><h2 id="相关资料"><a href="#相关资料" class="headerlink" title="相关资料"></a>相关资料</h2><ol><li><a href="https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events">Using server-sent events - Web APIs | MDN</a></li><li><a href="https://developer.mozilla.org/en-US/docs/Web/API/WebSocket">WebSocket - Web APIs | MDN</a></li><li><a href="https://www.ruanyifeng.com/blog/2017/05/websocket.html">WebSocket 教程 - 阮一峰的网络日志</a></li><li><a href="https://www.ruanyifeng.com/blog/2017/05/server-sent_events.html">Server-Sent Events 教程 - 阮一峰的网络日志</a></li></ol><link rel="stylesheet" href="/css/spoiler.css" type="text/css"><script src="/js/spoiler.js" type="text/javascript" async></script>]]></content>
<summary type="html"><p>最近在看LLM的流式输出如何反映到json输出上,现有的解决方案包括 WebSocket 或 Server-Sent Events (SSE) 实时通信技术.<br></summary>
</entry>
<entry>
<title>Learn OpenGL(二):模型加载与高级OpenGL</title>
<link href="https://www.sekyoro.top/2024/12/27/Learn-OpenGL-%E4%BA%8C/"/>
<id>https://www.sekyoro.top/2024/12/27/Learn-OpenGL-%E4%BA%8C/</id>
<published>2024-12-27T14:19:31.000Z</published>
<updated>2025-01-07T13:59:29.911Z</updated>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="\assets\js\Meting.min.js"></script><p>从手动设置顶点坐标到加载模型以及OpenGL高级技巧<br><span id="more"></span></p><h2 id="模型加载"><a href="#模型加载" class="headerlink" title="模型加载"></a>模型加载</h2><p>通过一些建模软件,比如Blender,可以方便地对一些复杂物体进行建模并导出为模型,而这些模型文件包含许多信息,比如顶点坐标,法线以及纹理坐标.</p><p>不同的模型文件格式封装的信息也不同.常见的有<code>.obj</code>,<code>.fbx</code>,<code>s.tl</code>等等</p><p>通常使用<code>Assimp</code>加载模型,Assimp能够导入很多种不同的模型文件格式(并也能够导出部分的格式),<strong>它会将所有的模型数据加载至Assimp的通用数据结构中</strong>。当Assimp加载完模型之后就能够从Assimp的数据结构中提取我们所需的所有数据了。</p><p>由于Assimp的数据结构保持不变,不论导入的是什么种类的文件格式,它都能够将我们从这些不同的文件格式中抽象出来,用同一种方式访问需要的数据。</p><p>当使用Assimp导入一个模型的时候,它通常会将整个模型加载进一个<strong>场景</strong>(Scene)对象,它会包含导入的模型/场景中的所有数据。Assimp会将场景载入为一系列的节点(Node),每个节点包含了场景对象中所储存数据的索引,每个节点都可以有任意数量的子节点。</p><p><img data-src="https://learnopengl-cn.github.io/img/03/01/assimp_structure.png" alt="img"></p><ul><li>和材质,网格(Mesh)一样,所有的场景/模型数据都包含在Scene对象中。Scene对象也包含了场景根节点的引用。</li><li>场景的Root node(根节点)可能包含子节点(和其它的节点一样),它会有一系列指向场景对象中mMeshes数组中储存的网格数据的索引。Scene下的mMeshes数组储存了真正的Mesh对象,节点中的mMeshes数组保存的只是场景中网格数组的索引。</li><li>一个Mesh对象本身包含了渲染所需要的所有相关数据,像是顶点位置、法向量、纹理坐标、面(Face)和物体的材质。</li><li>一个网格包含了多个面。Face代表的是物体的渲染图元(Primitive)(三角形、方形、点)。一个面包含了组成图元的顶点的索引。由于顶点和索引是分开的,使用一个索引缓冲来渲染是非常简单的</li><li>最后,一个网格也包含了一个Material对象,它包含了一些函数能让我们获取物体的材质属性,比如说颜色和纹理贴图(比如漫反射和镜面光贴图)</li></ul><p>使用Assimp可以加载不同的模型到程序中,但是载入后它们都被储存为Assimp的数据结构。最终仍要将这些数据转换为OpenGL能够理解的格式,这样才能渲染这个物体。我们从上一节中学到,网格(Mesh)代表的是单个的可绘制实体</p><p><img data-src="https://proanimer-img.oss-cn-shanghai.aliyuncs.com/alimg/image-20241228205757945.png" alt="image-20241228205757945"></p><p>一个网格应该至少需要一系列的顶点,每个顶点包含一个位置向量、一个法向量和一个纹理坐标向量。一个网格还应该包含用于索引绘制的索引以及纹理形式的材质数据(漫反射/镜面光贴图)。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">Vertex</span> {</span></span><br><span class="line"> glm::vec3 position;</span><br><span class="line"> glm::vec3 norm;</span><br><span class="line"> glm::vec2 textcoord;</span><br><span class="line">};</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">Texture</span> {</span></span><br><span class="line"> <span class="keyword">unsigned</span> <span class="keyword">int</span> id;</span><br><span class="line"> std::string type;</span><br><span class="line"> aiString dir;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">Mesh</span> {</span></span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line"> std::vector<Vertex> vertices;</span><br><span class="line"> std::vector<Texture> textures;</span><br><span class="line"> std::vector<<span class="keyword">unsigned</span> <span class="keyword">int</span>> indices;</span><br><span class="line"> <span class="comment">// 处理方法</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">Model</span>{</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">std::vector<Mesh> meshes;</span><br><span class="line"> std::vector<Textures> loadad_textures;</span><br><span class="line"> <span class="comment">// 处理方法</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="深度测试"><a href="#深度测试" class="headerlink" title="深度测试"></a>深度测试</h2><blockquote><p><strong>提前深度测试</strong></p><p>提前深度测试允许深度测试<strong>在片段着色器之前运行。只要清楚一个片段永远不会是可见的(它在其他物体之后),我们就能提前丢弃这个片段。</strong></p><p>片段着色器通常开销都是很大的,所以我们应该尽可能避免运行它们。<strong>当使用提前深度测试时,片段着色器的一个限制是你不能写入片段的深度值。如果一个片段着色器对它的深度值进行了写入,提前深度测试是不可能的。</strong>OpenGL不能提前知道深度值。</p></blockquote><p> <strong>深度缓冲就像颜色缓冲(Color Buffer)(储存所有的片段颜色:视觉输出)一样,在每个片段中储存了信息</strong>,并且(通常)和颜色缓冲有着一样的宽度和高度。<strong>深度缓冲是由窗口系统自动创建的,它会以16、24或32位float的形式储存它的深度值</strong>。在大部分的系统中,深度缓冲的精度都是24位的。</p><p> 当深度测试(Depth Testing)被启用的时候,<strong>OpenGL会将一个片段的深度值与深度缓冲的内容进行对比。OpenGL会执行一个深度测试,如果这个测试通过了的话,深度缓冲将会更新为新的深度值</strong>。如果深度测试失败了,片段将会被丢弃。</p><p> <strong>深度缓冲是在片段着色器运行之后(以及模板测试(Stencil Testing)运行之后)在屏幕空间中运行的。</strong>屏幕空间坐标与通过OpenGL的glViewport所定义的视口密切相关,并且可以<strong>直接使用GLSL内建变量gl_FragCoord从片段着色器中直接访问</strong>。gl_FragCoord的x和y分量代表了片段的屏幕空间坐标(其中(0, 0)位于左下角)。gl_FragCoord中也包含了一个z分量,它包含了片段真正的深度值。z值就是需要与深度缓冲内容所对比的那个值。</p><p>深度测试默认是禁用的,所以如果要启用深度测试的话用GL_DEPTH_TEST选项来启用它</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">glEnable</span>(GL_DEPTH_TEST);</span><br></pre></td></tr></table></figure><p>当它启用的时候,如果一个片段通过了深度测试的话,OpenGL会在深度缓冲中储存该片段的z值;如果没有通过深度缓冲,则会丢弃该片段。如果你启用了深度缓冲,你还应该在每个渲染迭代之前使用GL_DEPTH_BUFFER_BIT来清除深度缓冲,否则会仍在使用上一次渲染迭代中的写入的深度值</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">glClear</span>(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);</span><br></pre></td></tr></table></figure><p>在某些情况下你会需要对所有片段都执行深度测试并丢弃相应的片段,但<strong>不</strong>希望更新深度缓冲。基本上来说,你在使用一个只读的(Read-only)深度缓冲。<strong>OpenGL允许禁用深度缓冲的写入,只需要设置它的深度掩码</strong>(Depth Mask)设置为<code>GL_FALSE</code>就可以了:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">glEnable</span>(GL_DEPTH_TEST);</span><br><span class="line"><span class="built_in">glDepthMask</span>(GL_FALSE);</span><br></pre></td></tr></table></figure><p>注意这只在深度测试被启用的时候才有效果。</p><p>OpenGL允许修改深度测试中使用的比较运算符。这允许我们来控制OpenGL什么时候该通过或丢弃一个片段,什么时候去更新深度缓冲。可以调用glDepthFunc函数来设置比较运算符(或者说深度函数(Depth Function))</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">glDepthFunc</span>(GL_LESS);</span><br></pre></td></tr></table></figure><p>默认情况下使用的深度函数是GL_LESS,它将会丢弃深度值大于等于当前深度缓冲值的所有片段</p><div class="table-container"><table><thead><tr><th style="text-align:left">函数</th><th style="text-align:left">描述</th></tr></thead><tbody><tr><td style="text-align:left">GL_ALWAYS</td><td style="text-align:left">永远通过深度测试</td></tr><tr><td style="text-align:left">GL_NEVER</td><td style="text-align:left">永远不通过深度测试</td></tr><tr><td style="text-align:left">GL_LESS</td><td style="text-align:left">在片段深度值小于缓冲的深度值时通过测试</td></tr><tr><td style="text-align:left">GL_EQUAL</td><td style="text-align:left">在片段深度值等于缓冲区的深度值时通过测试</td></tr><tr><td style="text-align:left">GL_LEQUAL</td><td style="text-align:left">在片段深度值小于等于缓冲区的深度值时通过测试</td></tr><tr><td style="text-align:left">GL_GREATER</td><td style="text-align:left">在片段深度值大于缓冲区的深度值时通过测试</td></tr><tr><td style="text-align:left">GL_NOTEQUAL</td><td style="text-align:left">在片段深度值不等于缓冲区的深度值时通过测试</td></tr><tr><td style="text-align:left">GL_GEQUAL</td><td style="text-align:left">在片段深度值大于等于缓冲区的深度值时通过测试</td></tr></tbody></table></div><h4 id="深度值精度"><a href="#深度值精度" class="headerlink" title="深度值精度"></a>深度值精度</h4><p>深度缓冲包含了一个介于0.0和1.0之间的深度值,它将会与观察者视角所看见的场景中所有物体的z值进行比较。观察空间的z值可能是投影平截头体的<strong>近平面</strong>(Near)和<strong>远平面</strong>(Far)之间的任何值。</p><p>在透视矩阵中的znear和zfar决定了哪些坐标在视锥体中,我们将处在这个范围的顶点坐标z值转换到[0,1]之间. 一种简单的方式就是使用线性变换</p><script type="math/tex; mode=display">F_{\text {depth }}=\frac{z-n e a r}{f a r-n e a r}</script><p><img data-src="https://learnopengl-cn.github.io/img/04/01/depth_linear_graph.png" alt="img"></p><p>在实践中是几乎永远不会使用这样的线性深度缓冲(Linear Depth Buffer)的。要想有正确的投影性质,需要使用一个非线性的深度方程,它是与 1/z 成正比的。它做的就是在z值很小的时候提供非常高的精度,而在z值很远的时候提供更少的精度</p><script type="math/tex; mode=display">F_{\text {depth }}=\frac{1 / z-1 / \text { near }}{1 / \text { far }-1 / \text { near }}</script><p>深度值很大一部分是由很小的z值所决定的,这给了近处的物体很大的深度精度。这个(从观察者的视角)变换z值的方程是嵌入在投影矩阵中的,所以当我们想将一个顶点坐标从观察空间至裁剪空间的时候这个非线性方程就被应用了。</p><p><img data-src="https://learnopengl-cn.github.io/img/04/01/depth_non_linear_graph.png" alt="img"></p><p>可以将本身的非线性深度值转为线性深度值</p><figure class="highlight glsl"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#version 330 core</span></span><br><span class="line"><span class="keyword">out</span> <span class="type">vec4</span> FragColor;</span><br><span class="line"></span><br><span class="line"><span class="type">float</span> near = <span class="number">0.1</span>; </span><br><span class="line"><span class="type">float</span> far = <span class="number">100.0</span>; </span><br><span class="line"></span><br><span class="line"><span class="type">float</span> LinearizeDepth(<span class="type">float</span> depth) </span><br><span class="line">{</span><br><span class="line"> <span class="type">float</span> z = depth * <span class="number">2.0</span> - <span class="number">1.0</span>; <span class="comment">// back to NDC </span></span><br><span class="line"> <span class="keyword">return</span> (<span class="number">2.0</span> * near * far) / (far + near - z * (far - near)); </span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="type">void</span> main()</span><br><span class="line">{ </span><br><span class="line"> <span class="type">float</span> depth = LinearizeDepth(<span class="built_in">gl_FragCoord</span>.z) / far; <span class="comment">// 为了演示除以 far</span></span><br><span class="line"> FragColor = <span class="type">vec4</span>(<span class="type">vec3</span>(depth), <span class="number">1.0</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="深度冲突"><a href="#深度冲突" class="headerlink" title="深度冲突"></a>深度冲突</h4><p>一个很常见的视觉错误会在两个平面或者三角形非常紧密地平行排列在一起时会发生,<strong>深度缓冲没有足够的精度来决定两个形状哪个在前面</strong>。结果就是这两个形状不断地在切换前后顺序,这会导致很奇怪的花纹。这个现象叫做深度冲突(Z-fighting),因为它看起来像是这两个形状在争夺(Fight)谁该处于顶端。</p><p> 箱子被放置在地板的同一高度上,这也就意味着箱子的底面和地板是共面的(Coplanar)。这两个面的深度值都是一样的,所以深度测试没有办法决定应该显示哪一个。</p><p> 深度冲突是深度缓冲的一个常见问题,<strong>当物体在远处时效果会更明显(因为深度缓冲在z值比较大的时候有着更小的精度)。深度冲突不能够被完全避免,但一般会有一些技巧有助于在你的场景中减轻或者完全避免深度冲突</strong></p><p><strong>防止深度冲突</strong></p><p>第一个也是最重要的技巧是<strong>永远不要把多个物体摆得太靠近,以至于它们的一些三角形会重叠</strong>。通过在两个物体之间设置一个用户无法注意到的偏移值,你可以完全避免这两个物体之间的深度冲突。在箱子和地板的例子中,<strong>可以将箱子沿着正y轴稍微移动一点。箱子位置的这点微小改变将不太可能被注意到,但它能够完全减少深度冲突的发生</strong>。然而,这需要对每个物体都手动调整,并且需要进行彻底的测试来保证场景中没有物体会产生深度冲突。</p><p>第二个技巧是<strong>尽可能将近平面设置远一些</strong>。精度在靠近<strong>近</strong>平面时是非常高的,<strong>所以如果我们将近平面远离观察者,我们将会对整个平截头体有着更大的精度</strong>。然而,将近平面设置太远将会导致近处的物体被裁剪掉,所以这通常需要实验和微调来决定最适合你的场景的<strong>近</strong>平面距离。</p><p>另外一个很好的技巧是牺牲一些性能,<strong>使用更高精度的深度缓冲</strong>。大部分深度缓冲的精度都是24位的,但现在大部分的显卡都支持32位的深度缓冲,这将会极大地提高精度。所以,牺牲掉一些性能,你就能获得更高精度的深度测试,减少深度冲突。</p><h2 id="模板测试"><a href="#模板测试" class="headerlink" title="模板测试"></a>模板测试</h2><p> 当片段着色器处理完一个片段之后,模板测试(Stencil Test)会开始执行,和深度测试一样,它也可能会丢弃片段。接下来,被保留的片段会进入深度测试,它可能会丢弃更多的片段。<strong>模板测试是根据又一个缓冲来进行的,它叫做模板缓冲(Stencil Buffer),可以在渲染的时候更新它来获得一些很有意思的效果。</strong></p><p> <strong>一个模板缓冲中,(通常)每个模板值(Stencil Value)是8位的。所以每个像素/片段一共能有256种不同的模板值</strong>。我们可以将这些模板值设置为我们想要的值,然后当某一个片段有某一个模板值的时候,我们就可以选择丢弃或是保留这个片段了</p><blockquote><p>每个窗口库都需要为你配置一个模板缓冲。GLFW自动做了这件事,所以我们不需要告诉GLFW来创建一个,但其它的窗口库可能不会默认给你创建一个模板库,所以记得要查看库的文档。</p></blockquote><p>模板缓冲操作允许在渲染片段时将模板缓冲设定为一个特定的值。通过在渲染时修改模板缓冲的内容,我们<strong>写入</strong>了模板缓冲。在同一个(或者接下来的)渲染迭代中,可以<strong>读取</strong>这些值,来决定丢弃还是保留某个片段。使用模板缓冲的时候你可以尽情发挥,但大体的步骤如下:</p><ul><li>启用模板缓冲的写入。</li><li>渲染物体,更新模板缓冲的内容。</li><li>禁用模板缓冲的写入。</li><li>渲染(其它)物体,这次根据模板缓冲的内容丢弃特定的片段。</li></ul><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">glEnable</span>(GL_STENCIL_TEST);</span><br><span class="line"><span class="built_in">glClear</span>(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);</span><br></pre></td></tr></table></figure><p>和深度测试的glDepthMask函数一样,模板缓冲也有一个类似的函数。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">glStencilMask</span>(<span class="number">0xFF</span>); <span class="comment">// 每一位写入模板缓冲时都保持原样</span></span><br><span class="line"><span class="built_in">glStencilMask</span>(<span class="number">0x00</span>); <span class="comment">// 每一位在写入模板缓冲时都会变成0(禁用写入)</span></span><br></pre></td></tr></table></figure><p>glStencilMask允许我们设置一个位掩码(Bitmask),它会与将要写入缓冲的模板值进行与(AND)运算。默认情况下设置的位掩码所有位都为1,不影响输出,但如果我们将它设置为<code>0x00</code>,写入缓冲的所有模板值最后都会变成0.这与深度测试中的glDepthMask(GL_FALSE)是等价的</p><h3 id="模板函数"><a href="#模板函数" class="headerlink" title="模板函数"></a>模板函数</h3><p>一共有两个函数能够用来配置模板测试:glStencilFunc和glStencilOp</p><p>glStencilFunc(GLenum func, GLint ref, GLuint mask)一共包含三个参数:</p><ul><li><code>func</code>:设置模板测试函数(Stencil Test Function)。这个测试函数将会应用到已储存的模板值上和glStencilFunc函数的<code>ref</code>值上。可用的选项有:GL_NEVER、GL_LESS、GL_LEQUAL、GL_GREATER、GL_GEQUAL、GL_EQUAL、GL_NOTEQUAL和GL_ALWAYS。它们的语义和深度缓冲的函数类似。</li><li><code>ref</code>:设置了模板测试的参考值(Reference Value)。模板缓冲的内容将会与这个值进行比较。</li><li><code>mask</code>:设置一个掩码,它将会与参考值和储存的模板值在测试比较它们之前进行与(AND)运算。初始情况下所有位都为1</li></ul><p>如何更新缓冲就需要glStencilOp函数</p><p>glStencilOp(GLenum sfail, GLenum dpfail, GLenum dppass)一共包含三个选项,能够设定每个选项应该采取的行为:</p><ul><li><code>sfail</code>:模板测试失败时采取的行为。</li><li><code>dpfail</code>:模板测试通过,但深度测试失败时采取的行为。</li><li><code>dppass</code>:模板测试和深度测试都通过时采取的行为。</li></ul><div class="table-container"><table><thead><tr><th style="text-align:left">行为</th><th style="text-align:left">描述</th></tr></thead><tbody><tr><td style="text-align:left">GL_KEEP</td><td style="text-align:left">保持当前储存的模板值</td></tr><tr><td style="text-align:left">GL_ZERO</td><td style="text-align:left">将模板值设置为0</td></tr><tr><td style="text-align:left">GL_REPLACE</td><td style="text-align:left">将模板值设置为glStencilFunc函数设置的<code>ref</code>值</td></tr><tr><td style="text-align:left">GL_INCR</td><td style="text-align:left">如果模板值小于最大值则将模板值加1</td></tr><tr><td style="text-align:left">GL_INCR_WRAP</td><td style="text-align:left">与GL_INCR一样,但如果模板值超过了最大值则归零</td></tr><tr><td style="text-align:left">GL_DECR</td><td style="text-align:left">如果模板值大于最小值则将模板值减1</td></tr><tr><td style="text-align:left">GL_DECR_WRAP</td><td style="text-align:left">与GL_DECR一样,但如果模板值小于0则将其设置为最大值</td></tr><tr><td style="text-align:left">GL_INVERT</td><td style="text-align:left">按位翻转当前的模板缓冲值</td></tr></tbody></table></div><p>默认情况下glStencilOp是设置为<code>(GL_KEEP, GL_KEEP, GL_KEEP)</code>的,所以不论任何测试的结果是如何,模板缓冲都会保留它的值。默认的行为不会更新模板缓冲,所以如果你想写入模板缓冲的话,你需要至少对其中一个选项设置不同的值。</p><p>通过使用glStencilFunc和glStencilOp,我们可以精确地指定更新模板缓冲的时机与行为了,我们也可以指定什么时候该让模板缓冲通过,即什么时候片段需要被丢弃</p><h3 id="物体轮廓"><a href="#物体轮廓" class="headerlink" title="物体轮廓"></a>物体轮廓</h3><p>模板测试的一个重要应用就是人为选择需要绘制的区域. 如果要绘制一个物体的轮廓,画这个物体时更新模板缓冲为1,然后画一个更大的物体,设置模板缓冲函数,让模板测试是不等于1的位置. 则只会显示轮廓区域.</p><p>重要的几点:</p><ol><li>先进行模板测试后进行深度测试. 在这两个测试都开启时且不使用glDepthMask则在后面的物体是无法通过模板函数显示在前面的</li><li>模板测试的glStencilFunc设置的是模板测试成功条件,也就是怎样才能显示图形以及显示哪一部分图形. 比如设置当模板缓冲的值大于1时则模板测试通过. glStencilOp设置的是如何更新模板缓冲.</li></ol><p>注意:</p><ol><li>模板掩码(Stencil Mask)对 glClear 的影响:</li></ol><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 设置模板掩码</span></span><br><span class="line"><span class="built_in">glStencilMask</span>(<span class="number">0xFF</span>); <span class="comment">// 允许写入所有位</span></span><br><span class="line"><span class="built_in">glClear</span>(GL_STENCIL_BUFFER_BIT); <span class="comment">// 正常清除</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">glStencilMask</span>(<span class="number">0x00</span>); <span class="comment">// 禁止写入</span></span><br><span class="line"><span class="built_in">glClear</span>(GL_STENCIL_BUFFER_BIT); <span class="comment">// 不会清除模板缓冲</span></span><br></pre></td></tr></table></figure><ol><li>清除模板缓冲区的值设置:</li></ol><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 设置清除值</span></span><br><span class="line"><span class="built_in">glClearStencil</span>(<span class="number">1</span>); <span class="comment">// 设置清除值为1</span></span><br><span class="line"><span class="built_in">glClear</span>(GL_STENCIL_BUFFER_BIT); <span class="comment">// 用1填充模板缓冲区</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 多缓冲区同时清除</span></span><br><span class="line"><span class="built_in">glClear</span>(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);</span><br></pre></td></tr></table></figure><ol><li><p>```c++<br>// 错误示范<br>glStencilMask(0x00);<br>glClear(GL_STENCIL_BUFFER_BIT); // 无效的清除</p><p>// 正确做法<br>glStencilMask(0xFF); // 确保清除前设置正确的掩码<br>glClear(GL_STENCIL_BUFFER_BIT);<br>glStencilMask(0x00); // 之后再改回所需的掩码值</p><figure class="highlight glsl"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="meta">## 混合</span></span><br><span class="line"></span><br><span class="line">OpenGL中,混合(Blending)通常是实现物体透明度(Transparency)的一种技术。透明就是说一个物体(或者其中的一部分)不是纯色(Solid Color)的,它的颜色是物体本身的颜色和它背后其它物体的颜色的不同强度结合。</span><br><span class="line"></span><br><span class="line">**一个有色玻璃窗是一个透明的物体,玻璃有它自己的颜色,但它最终的颜色还包含了玻璃之后所有物体的颜色**。这也是混合这一名字的出处,混合(Blend)(不同物体的)多种颜色为一种颜色。所以透明度能让我们看穿物体。 Alpha通道表示不透明度,值越大越不透明.</span><br><span class="line"></span><br><span class="line"><span class="meta">### 丢弃片段</span></span><br><span class="line"></span><br><span class="line">如果你有一个纹理,其中某些部分是完全透明的,你可以根据纹理的颜色 alpha 分量来决定是否丢弃片段。例如,对于 alpha 值小于某个阈值的片段,你可以选择将其丢弃,从而实现不规则形状的物体渲染。</span><br><span class="line"></span><br><span class="line">此外,有时你可能想要根据一些条件裁剪掉不需要的部分。比如,在绘制树叶或草地的时候,可能会根据距离相机的距离或者其他条件来决定是否绘制特定的片段。</span><br><span class="line"></span><br><span class="line">加载纹理之后,如果纹理带有alpha通道,可以通过通道值选择是否渲染这个片段. </span><br><span class="line"></span><br><span class="line">```glsl</span><br><span class="line"><span class="meta">#version 330 core</span></span><br><span class="line"><span class="keyword">out</span> <span class="type">vec4</span> FragColor;</span><br><span class="line"></span><br><span class="line"><span class="keyword">in</span> <span class="type">vec2</span> TexCoords;</span><br><span class="line"></span><br><span class="line"><span class="keyword">uniform</span> <span class="type">sampler2D</span> texture1;</span><br><span class="line"></span><br><span class="line"><span class="type">void</span> main()</span><br><span class="line">{ </span><br><span class="line"> <span class="type">vec4</span> texColor = <span class="built_in">texture</span>(texture1, TexCoords);</span><br><span class="line"> <span class="keyword">if</span>(texColor.a < <span class="number">0.1</span>)</span><br><span class="line"> <span class="keyword">discard</span>;</span><br><span class="line"> FragColor = texColor;</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li></ol><p>设置距离丢弃条件,太远就丢弃</p><figure class="highlight glsl"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#version 400 core</span></span><br><span class="line"><span class="keyword">in</span> <span class="type">vec2</span> TextCoord;</span><br><span class="line"><span class="keyword">in</span> <span class="type">vec3</span> Normal;</span><br><span class="line"><span class="keyword">in</span> <span class="type">vec3</span> FragPos;</span><br><span class="line"><span class="keyword">uniform</span> <span class="type">vec3</span> viewPos;</span><br><span class="line"><span class="keyword">out</span> <span class="type">vec4</span> FragColor;</span><br><span class="line"><span class="keyword">uniform</span> <span class="type">sampler2D</span> texture1;</span><br><span class="line"><span class="type">void</span> main(){</span><br><span class="line"> <span class="type">vec4</span> textColor = <span class="built_in">texture</span>(texture1,TextCoord);</span><br><span class="line"> <span class="keyword">if</span> (textColor.a < <span class="number">0.1</span>) {</span><br><span class="line"> <span class="keyword">discard</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="type">float</span> dis = <span class="built_in">distance</span>(FragPos,viewPos);</span><br><span class="line"> <span class="keyword">if</span>(dis> <span class="number">15.0</span>){</span><br><span class="line"> <span class="keyword">discard</span>;</span><br><span class="line"> }</span><br><span class="line"> FragColor = textColor;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>注意,当采样纹理的边缘的时候,OpenGL<strong>会对边缘的值和纹理下一个重复的值进行插值</strong>(因为我们将它的环绕方式设置为了GL_REPEAT。这通常是没问题的,但是由于我们使用了透明值,纹理图像的顶部将会与底部边缘的纯色值进行插值。这样的结果是一个半透明的有色边框,你可能会看见它环绕着你的纹理四边形。要想避免这个,每当你alpha纹理的时候,请将纹理的环绕方式设置为GL_CLAMP_TO_EDGE:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">glTexParameteri</span>( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);</span><br><span class="line"><span class="built_in">glTexParameteri</span>( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);</span><br></pre></td></tr></table></figure><h3 id="混合"><a href="#混合" class="headerlink" title="混合"></a>混合</h3><p>虽然直接丢弃片段很好,但它不能让我们渲染半透明的图像。我们要么渲染一个片段,要么完全丢弃它。要想渲染有多个透明度级别的图像,我们需要启用混合(Blending)。和OpenGL大多数的功能一样,可以启用GL_BLEND来启用混合</p><p>OpenGL中的混合是通过下面这个方程来实现的:</p><script type="math/tex; mode=display">\bar{C}_{\text {result }}=\bar{C}_{\text {source }} * F_{\text {source }}+\bar{C}_{\text {destination }} * F_{\text {destination }}</script><p><img data-src="https://learnopengl-cn.github.io/img/04/03/blending_equation_mixed.png" alt="img"></p><ul><li>C_source:源颜色向量。这是<strong>源自纹理的颜色向量</strong>。</li><li>C_destination:目标颜色向量。这是<strong>当前储存在颜色缓冲中的颜色向量</strong>。</li><li>F_source:源因子值。指定了alpha值对源颜色的影响。</li><li>F_destination:目标因子值。指定了alpha值对目标颜色的影响</li></ul><p>使用<code>glBlendFunc</code>设置因子值,glBlendFunc(GLenum sfactor, GLenum dfactor)函数接受两个参数,来设置源和目标因子。OpenGL为我们定义了很多个选项,我们将在下面列出大部分最常用的选项。注意常数颜色向量C¯constantC¯constant可以通过glBlendColor函数来另外设置。</p><div class="table-container"><table><thead><tr><th style="text-align:left">选项</th><th style="text-align:left">值</th></tr></thead><tbody><tr><td style="text-align:left"><code>GL_ZERO</code></td><td style="text-align:left">因子等于00</td></tr><tr><td style="text-align:left"><code>GL_ONE</code></td><td style="text-align:left">因子等于11</td></tr><tr><td style="text-align:left"><code>GL_SRC_COLOR</code></td><td style="text-align:left">因子等于源颜色向量C¯sourceC¯source</td></tr><tr><td style="text-align:left"><code>GL_ONE_MINUS_SRC_COLOR</code></td><td style="text-align:left">因子等于1−C¯source1−C¯source</td></tr><tr><td style="text-align:left"><code>GL_DST_COLOR</code></td><td style="text-align:left">因子等于目标颜色向量C¯destinationC¯destination</td></tr><tr><td style="text-align:left"><code>GL_ONE_MINUS_DST_COLOR</code></td><td style="text-align:left">因子等于1−C¯destination1−C¯destination</td></tr><tr><td style="text-align:left"><code>GL_SRC_ALPHA</code></td><td style="text-align:left">因子等于C¯sourceC¯source的alphaalpha分量</td></tr><tr><td style="text-align:left"><code>GL_ONE_MINUS_SRC_ALPHA</code></td><td style="text-align:left">因子等于1−1− C¯sourceC¯source的alphaalpha分量</td></tr><tr><td style="text-align:left"><code>GL_DST_ALPHA</code></td><td style="text-align:left">因子等于C¯destinationC¯destination的alphaalpha分量</td></tr><tr><td style="text-align:left"><code>GL_ONE_MINUS_DST_ALPHA</code></td><td style="text-align:left">因子等于1−1− C¯destinationC¯destination的alphaalpha分量</td></tr><tr><td style="text-align:left"><code>GL_CONSTANT_COLOR</code></td><td style="text-align:left">因子等于常数颜色向量C¯constantC¯constant</td></tr><tr><td style="text-align:left"><code>GL_ONE_MINUS_CONSTANT_COLOR</code></td><td style="text-align:left">因子等于1−C¯constant1−C¯constant</td></tr><tr><td style="text-align:left"><code>GL_CONSTANT_ALPHA</code></td><td style="text-align:left">因子等于C¯constantC¯constant的alphaalpha分量</td></tr><tr><td style="text-align:left"><code>GL_ONE_MINUS_CONSTANT_ALPHA</code></td><td style="text-align:left">因子等于1−1− C¯constantC¯constant的alphaalpha分量</td></tr></tbody></table></div><p>注意常数颜色向量C_constant可以通过glBlendColor函数来另外设置。</p><p>为了获得之前两个方形的混合结果,我们需要使用源颜色向量的alpha作为源因子,使用1−alpha作为目标因子。这将会产生以下的glBlendFunc:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">glBlendFunc</span>(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);</span><br></pre></td></tr></table></figure><p>也可以使用glBlendFuncSeparate为RGB和alpha通道分别设置不同的选项:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">glBlendFuncSeparate</span>(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ZERO);</span><br></pre></td></tr></table></figure><p>允许我们改变方程中源和目标部分的运算符。当前源和目标是相加的,但如果愿意的话,我们也可以让它们相减。glBlendEquation(GLenum mode)允许我们设置运算符,它提供了三个选项:</p><ul><li>GL_FUNC_ADD:默认选项,将两个分量相加:</li><li>GL_FUNC_SUBTRACT:将两个分量相减: </li><li>GL_FUNC_REVERSE_SUBTRACT:将两个分量相减,但顺序相反:</li></ul><p>通常都可以省略调用glBlendEquation</p><h3 id="渲染半透明纹理"><a href="#渲染半透明纹理" class="headerlink" title="渲染半透明纹理"></a>渲染半透明纹理</h3><p>初始化时我们启用混合,并设定相应的混合函数:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">glEnable</span>(GL_BLEND);</span><br><span class="line"><span class="built_in">glBlendFunc</span>(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);</span><br></pre></td></tr></table></figure><p>由于启用了混合,我们就不需要丢弃片段了,所以我们把片段着色器还原:</p><figure class="highlight glsl"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#version 330 core</span></span><br><span class="line"><span class="keyword">out</span> <span class="type">vec4</span> FragColor;</span><br><span class="line"></span><br><span class="line"><span class="keyword">in</span> <span class="type">vec2</span> TexCoords;</span><br><span class="line"></span><br><span class="line"><span class="keyword">uniform</span> <span class="type">sampler2D</span> texture1;</span><br><span class="line"></span><br><span class="line"><span class="type">void</span> main()</span><br><span class="line">{ </span><br><span class="line"> FragColor = <span class="built_in">texture</span>(texture1, TexCoords);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>但是可能跟绘图顺序有关.深度测试和混合一起使用的话会产生一些麻烦。当<strong>写入深度缓冲时,深度缓冲不会检查片段是否是透明的,所以透明的部分会和其它值一样写入到深度缓冲中</strong>。结果就是窗户的整个四边形不论透明度都会进行深度测试。即使透明的部分应该显示背后的窗户,深度测试仍然丢弃了它们。 也就是说</p><p>要想保证窗户中能够显示它们背后的物体,我们需要首先绘制背后的这部分物体。这也就是说在绘制的时候,必须先手动将窗户按照最远到最近来排序,再按照顺序渲染.</p><blockquote><p>注意,对于草这种全透明的物体,可以选择丢弃透明的片段而不是混合它们,这样就解决了这些头疼的问题(没有深度问题)</p></blockquote><h3 id="不要打扰顺序"><a href="#不要打扰顺序" class="headerlink" title="不要打扰顺序"></a>不要打扰顺序</h3><p> <strong>要想让混合在多个物体上工作,需要最先绘制最远的物体,最后绘制最近的物体</strong>。</p><p> 普通<strong>不需要混合的物体仍然可以使用深度缓冲正常绘制,所以它们不需要排序</strong>。但<strong>仍要保证它们在绘制(排序的)透明物体之前已经绘制完毕了</strong>。当绘制一个有不透明和透明物体的场景的时候,大体的原则如下:</p><ol><li><p>先绘制所有不透明的物体。</p></li><li><p>对所有透明的物体排序。</p></li><li><p>按顺序绘制所有透明的物体</p></li></ol><p> 排序透明物体的一种方法是,<strong>从观察者视角获取物体的距离。这可以通过计算摄像机位置向量和物体的位置向量之间的距离所获得</strong>。</p><p> 接下来把距离和它对应的位置向量存储到一个STL库的map数据结构中。map会自动根据键值(Key)对它的值排序,所以只要添加了所有的位置,并以它的距离作为键,它们就会自动根据距离值排序了。</p><p> 在渲染的时候以逆序(从远到近)从map中获取值,之后以正确的顺序绘制对应的窗户.</p><p> 虽然按照距离排序物体这种方法对这个场景能够正常工作,<strong>但它并没有考虑旋转、缩放或者其它的变换,奇怪形状的物体需要一个不同的计量,而不是仅仅一个位置向量。</strong></p><p> 在场景中排序物体是一个很困难的技术,很大程度上由场景的类型所决定,更别说它额外需要消耗的处理能力了。<strong>完整渲染一个包含不透明和透明物体的场景并不是那么容易。更高级的技术还有次序无关透明度</strong>(Order Independent Transparency, OIT)</p><h2 id="面剔除"><a href="#面剔除" class="headerlink" title="面剔除"></a>面剔除</h2><p>想象任何一个闭合形状,它的每一个面都有两侧,每一侧要么<strong>面向</strong>用户,要么背对用户。如果我们能够只绘制<strong>面向</strong>观察者的面呢?</p><p>这正是面剔除(Face Culling)所做的。OpenGL能够检查所有面向(Front Facing)观察者的面,并渲染它们,而丢弃那些背向(Back Facing)的面,节省我们很多的片段着色器调用(它们的开销很大!)。但仍要告诉OpenGL哪些面是正向面(Front Face),哪些面是背向面(Back Face)。OpenGL使用了一个很聪明的技巧,<strong>分析顶点数据的环绕顺序(Winding Order)。</strong></p><h4 id="环绕顺序"><a href="#环绕顺序" class="headerlink" title="环绕顺序"></a>环绕顺序</h4><p>当我们定义一组三角形顶点时,我们会以特定的环绕顺序来定义它们,可能是顺时针(Clockwise)的,也可能是逆时针(Counter-clockwise)的。每个三角形由3个顶点所组成,会从三角形中间来看,为这3个顶点设定一个环绕顺序。</p><p><strong>每组组成三角形图元的三个顶点就包含了一个环绕顺序</strong>。OpenGL在渲染图元的时候将使用这个信息来决定一个三角形是一个正向三角形还是背向三角形。</p><p><strong>默认情况下,逆时针顶点所定义的三角形将会被处理为正向三角形。</strong>当你定义顶点顺序的时候,你应该想象对应的三角形是面向你的,所以你定义的三角形从正面看去应该是逆时针的。这样定义顶点很棒的一点是,实际的环绕顺序是在光栅化阶段进行的,也就是顶点着色器运行之后。这些顶点就是从<strong>观察者视角</strong>所见的了。</p><h4 id="面剔除-1"><a href="#面剔除-1" class="headerlink" title="面剔除"></a>面剔除</h4><p>OpenGL能够丢弃那些渲染为背向三角形的三角形图元。既然已经知道如何设置顶点的环绕顺序了,我们就可以使用OpenGL的面剔除选项了,它默认是禁用状态的。</p><p>在之前教程中使用的立方体顶点数据并不是按照逆时针环绕顺序定义的,所以顶点数据反映了环绕顺序从而反应是正向面和背向面.</p><p>要想启用面剔除,我们只需要启用OpenGL的GL_CULL_FACE选项:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">glEnable</span>(GL_CULL_FACE);</span><br></pre></td></tr></table></figure><p>从这一句代码之后,所有背向面都将被丢弃(尝试飞进立方体内部,看看所有的内面是不是都被丢弃了)。目前我们在渲染片段的时候能够节省50%以上的性能,但注意这只对像立方体这样的封闭形状有效。</p><p>OpenGL允许我们改变需要剔除的面的类型。我们可以调用glCullFace来定义这一行为:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">glCullFace</span>(GL_FRONT);</span><br></pre></td></tr></table></figure><p>glCullFace函数有三个可用的选项:</p><ul><li><code>GL_BACK</code>:只剔除背向面。</li><li><code>GL_FRONT</code>:只剔除正向面。</li><li><code>GL_FRONT_AND_BACK</code>:剔除正向面和背向面。</li></ul><p>glCullFace的初始值是GL_BACK。除了需要剔除的面之外,也可以通过调用glFrontFace,告诉OpenGL我们希望将顺时针的面(而不是逆时针的面)定义为正向面:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">glFrontFace</span>(GL_CCW);</span><br></pre></td></tr></table></figure><p>默认值是GL_CCW,它代表的是逆时针的环绕顺序,另一个选项是GL_CW,它(显然)代表的是顺时针顺序。</p><p>我们可以来做一个实验,告诉OpenGL现在顺时针顺序代表的是正向面:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">glEnable</span>(GL_CULL_FACE);</span><br><span class="line"><span class="built_in">glCullFace</span>(GL_BACK);</span><br><span class="line"><span class="built_in">glFrontFace</span>(GL_CW);</span><br></pre></td></tr></table></figure><h2 id="帧缓冲"><a href="#帧缓冲" class="headerlink" title="帧缓冲"></a>帧缓冲</h2><p> 现在已经使用了很多屏幕缓冲了:用于<strong>写入颜色值的颜色缓冲</strong>、用于<strong>写入深度信息的深度缓冲</strong>和允许我们<strong>根据一些条件丢弃特定片段的模板缓冲</strong>。这些缓冲<strong>结合起来叫做帧缓冲(</strong>Framebuffer)</p><p> 它被<strong>储存在GPU内存</strong>中的某处。<strong>OpenGL允许我们定义我们自己的帧缓冲,也就是说能够定义我们自己的颜色缓冲,甚至是深度缓冲和模板缓冲</strong>。</p><p> 目前所做的<strong>所有操作都是在默认帧缓冲的渲染缓冲上进行的</strong>。默认的帧缓冲是在你创建窗口的时候生成和配置的(GLFW帮我们做了这些)。通过创建自己的帧缓冲,我们可以获得额外的渲染目标(target)。</p><p>渲染你的场景到不同的帧缓冲能够让我们在场景中加入类似镜子的东西,或者做出很酷的后期处理效果</p><h4 id="创建帧缓冲"><a href="#创建帧缓冲" class="headerlink" title="创建帧缓冲"></a>创建帧缓冲</h4><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">Gluint fbo;</span><br><span class="line"><span class="built_in">glGenFramebuffers</span>(<span class="number">1</span>,&fbo);</span><br><span class="line"><span class="built_in">glBindFrameBuffer</span>(GL_FRAMEBUFFER,fbo);</span><br></pre></td></tr></table></figure><p>在绑定到GL_FRAMEBUFFER目标之后,所有的<strong>读取</strong>和<strong>写入</strong>帧缓冲的操作将会影响当前绑定的帧缓冲。<strong>也可以使用GL_READ_FRAMEBUFFER或GL_DRAW_FRAMEBUFFER,将一个帧缓冲分别绑定到读取目标或写入目标</strong>。绑定到GL_READ_FRAMEBUFFER的帧缓冲将会使用在所有像是glReadPixels的读取操作中,而绑定到GL_DRAW_FRAMEBUFFER的帧缓冲将会被用作渲染、清除等写入操作的目标。大部分情况你都不需要区分它们,通常都会使用GL_FRAMEBUFFER,绑定到两个上</p><p>一个完整的帧缓冲需要满足以下的条件:</p><ul><li>附加至少一个缓冲(颜色、深度或模板缓冲)。</li><li>至少有一个颜色附件(Attachment)。</li><li>所有的附件都必须是完整的(保留了内存)。</li><li>每个缓冲都应该有相同的样本数(sample)</li></ul><p> 需要为帧缓冲创建一些附件,并将附件附加到帧缓冲上。在完成所有的条件之后,我们可以以GL_FRAMEBUFFER为参数调用glCheckFramebufferStatus,检查帧缓冲是否完整。它将会检测当前绑定的帧缓冲,并返回规范中这些值的其中之一。如果它返回的是GL_FRAMEBUFFER_COMPLETE,帧缓冲就是完整的了</p><p> <strong>之后所有的渲染操作将会渲染到当前绑定帧缓冲的附件中</strong>。由于帧缓冲不是默认帧缓冲,<strong>渲染指令将不会对窗口的视觉输出有任何影响</strong>。出于这个原因,<strong>渲染到一个不同的帧缓冲被叫做离屏渲染(Off-screen Rendering)。要保证所有的渲染操作在主窗口中有视觉效果,我们需要再次激活默认帧缓冲,将它绑定到<code>0</code>。</strong></p><p> 在完整性检查执行之前,<strong>需要给帧缓冲附加一个附件。附件是一个内存位置,它能够作为帧缓冲的一个缓冲</strong>,可以将它想象为一个图像。当创建一个附件的时候我们有两个选项:纹理或渲染缓冲对象(Renderbuffer Object)。</p><h4 id="纹理附件"><a href="#纹理附件" class="headerlink" title="纹理附件"></a>纹理附件</h4><p>想象帧缓冲(FBO)是一个画框,纹理附件就是可以放进这个画框的”画布”。这些”画布”可以用来:</p><ul><li>存储颜色(类似照片)</li><li>存储深度(物体的远近信息)</li><li>存储模板值(用于特效)</li></ul><p>当把一个纹理附加到帧缓冲的时候,所有的渲染指令将会写入到这个纹理中,就像它是一个普通的颜色/深度或模板缓冲一样。使用纹理的优点是,<strong>所有渲染操作的结果将会被储存在一个纹理图像中,之后可以在着色器中很方便地使用它。</strong></p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">unsigned</span> <span class="keyword">int</span> texture;</span><br><span class="line"><span class="built_in">glGenTextures</span>(<span class="number">1</span>,&texture);</span><br><span class="line"><span class="built_in">glBindTexture</span>(GL_TEXTURE_2D,texture);</span><br><span class="line"><span class="built_in">glTexImage2D</span>(GL_TEXTURE_2D,<span class="number">0</span>,GL_RGB,<span class="number">800</span>,<span class="number">600</span>,<span class="number">0</span>,GL_RGB,GL_UNSIGNED_BYTE,<span class="literal">NULL</span>);</span><br><span class="line"><span class="built_in">glTexParameteri</span>(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);</span><br><span class="line"><span class="built_in">glTexParameteri</span>(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);</span><br><span class="line"><span class="comment">// 添加颜色纹理附件</span></span><br><span class="line"><span class="built_in">glFramebufferTexture2D</span>(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, <span class="number">0</span>);</span><br></pre></td></tr></table></figure><p>对于深度和模板缓冲格式不同</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">glTexImage2D</span>(GL_TEXTURE_2D,<span class="number">0</span>,GL_DEPTH_COMPONENT,<span class="number">800</span>,<span class="number">600</span>,<span class="number">0</span>,GL_DEPTH_COMPONENT,GL_UNSIGNED_BYTE,<span class="literal">NULL</span>);</span><br><span class="line"><span class="comment">// 添加深度纹理附件</span></span><br><span class="line"><span class="built_in">glFramebufferTexture2D</span>(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, texture, <span class="number">0</span>);</span><br></pre></td></tr></table></figure><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">glTexImage2D</span>(GL_TEXTURE_2D,<span class="number">0</span>,GL_STENCIL_INDEX,<span class="number">800</span>,<span class="number">600</span>,<span class="number">0</span>,GL_STENCIL_INDEX,GL_UNSIGNED_BYTE,<span class="literal">NULL</span>);</span><br><span class="line"><span class="comment">// 添加模板纹理附件</span></span><br><span class="line"><span class="built_in">glFramebufferTexture2D</span>(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, texture, <span class="number">0</span>);</span><br></pre></td></tr></table></figure><h4 id="渲染缓冲对象附件"><a href="#渲染缓冲对象附件" class="headerlink" title="渲染缓冲对象附件"></a>渲染缓冲对象附件</h4><p> 和纹理图像一样,渲染缓冲对象是一个真正的缓冲,即一系列的字节、整数、像素等。渲染缓冲对象附加的好处是,<strong>它会将数据储存为OpenGL原生的渲染格式,它是为离屏渲染到帧缓冲优化过的。</strong></p><p> <strong>渲染缓冲对象直接将所有的渲染数据储存到它的缓冲中,不会做任何针对纹理格式的转换,让它变为一个更快的可写储存介质。</strong>然而,渲染缓冲对象通常都是只写的,所以你不能读取它们(比如使用纹理访问)。当然你仍然还是能够使用glReadPixels来读取它,这会从当前绑定的帧缓冲,而不是附件本身,中返回特定区域的像素。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">unsigned</span> <span class="keyword">int</span> rbo;</span><br><span class="line"><span class="built_in">glGenRenderbuffers</span>(<span class="number">1</span>,&rbo);</span><br><span class="line"><span class="built_in">glBindRenderbuffer</span>(GL_RENDERBUFFER,rbo);</span><br><span class="line"><span class="built_in">glRenderbufferStorage</span>(GL_RENDERBUFFER,GL_DEPTH24_STENCIL8,<span class="number">800</span>,<span class="number">600</span>);</span><br><span class="line"><span class="built_in">glFramebufferRenderbuffer</span>(GL_FRAMEBUFFER,GL_DEPTH_STENCIL_ATTACHMENT,GL_RENDERBUFFER,rbo);</span><br></pre></td></tr></table></figure><p>由于<strong>渲染缓冲对象通常都是只写的,它们会经常用于深度和模板附件,因为大部分时间都不需要从深度和模板缓冲中读取值</strong>,只关心深度和模板测试。我们<strong>需要</strong>深度和模板值用于测试,但不需要对它们进行<strong>采样</strong>,所以渲染缓冲对象非常适合它们。当我们不需要从这些缓冲中采样的时候,通常都会选择渲染缓冲对象,因为它会更优化一点。</p><h4 id="渲染到纹理"><a href="#渲染到纹理" class="headerlink" title="渲染到纹理"></a>渲染到纹理</h4><p>要想绘制场景到一个纹理上,我需要采取以下的步骤:</p><ol><li>将新的帧缓冲绑定为激活的帧缓冲,和往常一样渲染场景</li><li>绑定默认的帧缓冲</li><li>绘制一个横跨整个屏幕的四边形,将帧缓冲的颜色缓冲作为它的纹理。</li></ol><p>将帧缓冲的颜色渲染到默认缓冲的纹理上</p><h4 id="反相"><a href="#反相" class="headerlink" title="反相"></a>反相</h4><p>将纹理颜色反相</p><figure class="highlight glsl"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">void</span> main()</span><br><span class="line">{</span><br><span class="line"> FragColor = <span class="type">vec4</span>(<span class="type">vec3</span>(<span class="number">1.0</span> - <span class="built_in">texture</span>(screenTexture, TexCoords)), <span class="number">1.0</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="灰度"><a href="#灰度" class="headerlink" title="灰度"></a>灰度</h4><figure class="highlight glsl"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">void</span> main()</span><br><span class="line">{</span><br><span class="line"> FragColor = <span class="built_in">texture</span>(screenTexture, TexCoords);</span><br><span class="line"> <span class="type">float</span> average = <span class="number">0.2126</span> * FragColor.r + <span class="number">0.7152</span> * FragColor.g + <span class="number">0.0722</span> * FragColor.b;</span><br><span class="line"> FragColor = <span class="type">vec4</span>(average, average, average, <span class="number">1.0</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="核效果"><a href="#核效果" class="headerlink" title="核效果"></a>核效果</h4><figure class="highlight glsl"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="type">float</span> <span class="keyword">offset</span> = <span class="number">1.0</span> / <span class="number">300.0</span>; </span><br><span class="line"></span><br><span class="line"><span class="type">void</span> main()</span><br><span class="line">{</span><br><span class="line"> <span class="type">vec2</span> offsets[<span class="number">9</span>] = <span class="type">vec2</span>[](</span><br><span class="line"> <span class="type">vec2</span>(-<span class="keyword">offset</span>, <span class="keyword">offset</span>), <span class="comment">// 左上</span></span><br><span class="line"> <span class="type">vec2</span>( <span class="number">0.0</span>f, <span class="keyword">offset</span>), <span class="comment">// 正上</span></span><br><span class="line"> <span class="type">vec2</span>( <span class="keyword">offset</span>, <span class="keyword">offset</span>), <span class="comment">// 右上</span></span><br><span class="line"> <span class="type">vec2</span>(-<span class="keyword">offset</span>, <span class="number">0.0</span>f), <span class="comment">// 左</span></span><br><span class="line"> <span class="type">vec2</span>( <span class="number">0.0</span>f, <span class="number">0.0</span>f), <span class="comment">// 中</span></span><br><span class="line"> <span class="type">vec2</span>( <span class="keyword">offset</span>, <span class="number">0.0</span>f), <span class="comment">// 右</span></span><br><span class="line"> <span class="type">vec2</span>(-<span class="keyword">offset</span>, -<span class="keyword">offset</span>), <span class="comment">// 左下</span></span><br><span class="line"> <span class="type">vec2</span>( <span class="number">0.0</span>f, -<span class="keyword">offset</span>), <span class="comment">// 正下</span></span><br><span class="line"> <span class="type">vec2</span>( <span class="keyword">offset</span>, -<span class="keyword">offset</span>) <span class="comment">// 右下</span></span><br><span class="line"> );</span><br><span class="line"></span><br><span class="line"> <span class="type">float</span> kernel[<span class="number">9</span>] = <span class="type">float</span>[](</span><br><span class="line"> <span class="number">-1</span>, <span class="number">-1</span>, <span class="number">-1</span>,</span><br><span class="line"> <span class="number">-1</span>, <span class="number">9</span>, <span class="number">-1</span>,</span><br><span class="line"> <span class="number">-1</span>, <span class="number">-1</span>, <span class="number">-1</span></span><br><span class="line"> );</span><br><span class="line"></span><br><span class="line"> <span class="type">vec3</span> sampleTex[<span class="number">9</span>];</span><br><span class="line"> <span class="keyword">for</span>(<span class="type">int</span> i = <span class="number">0</span>; i < <span class="number">9</span>; i++)</span><br><span class="line"> {</span><br><span class="line"> sampleTex[i] = <span class="type">vec3</span>(<span class="built_in">texture</span>(screenTexture, TexCoords.st + offsets[i]));</span><br><span class="line"> }</span><br><span class="line"> <span class="type">vec3</span> col = <span class="type">vec3</span>(<span class="number">0.0</span>);</span><br><span class="line"> <span class="keyword">for</span>(<span class="type">int</span> i = <span class="number">0</span>; i < <span class="number">9</span>; i++)</span><br><span class="line"> col += sampleTex[i] * kernel[i];</span><br><span class="line"></span><br><span class="line"> FragColor = <span class="type">vec4</span>(col, <span class="number">1.0</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>此外还有模糊、边缘检测等效果,都是利用卷积核.</p><h2 id="立方体贴图"><a href="#立方体贴图" class="headerlink" title="立方体贴图"></a>立方体贴图</h2><p>立方体贴图(Cube Map)是将多个纹理组合起来映射到一张纹理上的一种纹理类型. </p><p>立方体贴图的坐标至关重要,通过一个方向向量进行索引. 方向向量原点位于立方体中心</p><p><img data-src="https://learnopengl-cn.github.io/img/04/06/cubemaps_sampling.png" alt="img"></p><p>假设将这样的立方体贴图应用到一个立方体上,采样立方体贴图所使用的方向向量将和立方体(插值的)顶点位置非常相像。这样子,只要立方体的中心位于原点,我们就能使用立方体的实际位置向量来对立方体贴图进行采样了。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">unsigned</span> <span class="keyword">int</span> textureID;</span><br><span class="line"><span class="built_in">glGenTextures</span>(<span class="number">1</span>, &textureID);</span><br><span class="line"><span class="built_in">glBindTexture</span>(GL_TEXTURE_CUBE_MAP, textureID);</span><br><span class="line"></span><br><span class="line"><span class="keyword">int</span> width, height, nrChannels;</span><br><span class="line"><span class="keyword">unsigned</span> <span class="keyword">char</span> *data; </span><br><span class="line"><span class="keyword">for</span>(<span class="keyword">unsigned</span> <span class="keyword">int</span> i = <span class="number">0</span>; i < textures_faces.<span class="built_in">size</span>(); i++)</span><br><span class="line">{</span><br><span class="line"> data = <span class="built_in">stbi_load</span>(textures_faces[i].<span class="built_in">c_str</span>(), &width, &height, &nrChannels, <span class="number">0</span>);</span><br><span class="line"> <span class="built_in">glTexImage2D</span>(</span><br><span class="line"> GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, </span><br><span class="line"> <span class="number">0</span>, GL_RGB, width, height, <span class="number">0</span>, GL_RGB, GL_UNSIGNED_BYTE, data</span><br><span class="line"> );</span><br><span class="line">}</span><br><span class="line"><span class="built_in">glTexParameteri</span>(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);</span><br><span class="line"><span class="built_in">glTexParameteri</span>(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);</span><br><span class="line"><span class="built_in">glTexParameteri</span>(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);</span><br><span class="line"><span class="built_in">glTexParameteri</span>(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);</span><br><span class="line"><span class="built_in">glTexParameteri</span>(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);</span><br></pre></td></tr></table></figure><p>立方体纹理最常用的就是天空盒,而天空盒的核心就是将贴图用在一个立方体上并通过深度测试将贴图作为背景. 一个简单的方法就是利用<code>glDepthMask</code>,首先渲染天空盒,在绘制其他物体之前再启动Mask,更新深度缓冲.</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">glDepthMask</span>(GL_FALSE);</span><br><span class="line">skyboxShader.<span class="built_in">use</span>();</span><br><span class="line"><span class="comment">// ... 设置观察和投影矩阵</span></span><br><span class="line"><span class="built_in">glBindVertexArray</span>(skyboxVAO);</span><br><span class="line"><span class="built_in">glBindTexture</span>(GL_TEXTURE_CUBE_MAP, cubemapTexture);</span><br><span class="line"><span class="built_in">glDrawArrays</span>(GL_TRIANGLES, <span class="number">0</span>, <span class="number">36</span>);</span><br><span class="line"><span class="built_in">glDepthMask</span>(GL_TRUE);</span><br><span class="line"><span class="comment">// ... 绘制剩下的场景</span></span><br></pre></td></tr></table></figure><p>注意到天空盒的大部分可能会被其他物体覆盖,通过提前深度测试,将已知的深度较大的物体在之后绘制,这样在片段着色器运行之前就直到被其他物体””盖住”了. 因为天空盒总是在最后,设置其z在透视变换后为1即可,在片段着色器中设置其z为w.</p><figure class="highlight glsl"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">vec4</span> pos = projection * view * <span class="type">vec4</span>(position, <span class="number">1.0</span>); <span class="comment">// view矩阵取消了移动,projection矩阵只对z进行了缩放,而之后通过透视除法将z设置为了1</span></span><br><span class="line"> <span class="built_in">gl_Position</span> = pos.xyww;</span><br></pre></td></tr></table></figure><h3 id="环境映射"><a href="#环境映射" class="headerlink" title="环境映射"></a>环境映射</h3><p>我们现在<strong>将整个环境映射到了一个纹理对象</strong>上了,能利用这个信息的不仅仅只有天空盒。通过使用环境的立方体贴图,<strong>可以给物体反射和折射的属性。这样使用环境立方体贴图的技术叫做环境映射</strong>(Environment Mapping),其中最流行的两个是反射(Reflection)和折射(Refraction)。</p><p><img data-src="https://learnopengl-cn.github.io/img/04/06/cubemaps_reflection_theory.png" alt="img"></p><p>反射通过眼睛与物体法线向量得到的反射向量对立方体纹理采样</p><figure class="highlight glsl"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">uniform</span> <span class="type">vec3</span> viewPos;</span><br><span class="line"><span class="keyword">uniform</span> <span class="type">samplerCube</span> skybox;</span><br><span class="line"><span class="keyword">in</span> <span class="type">vec3</span> Normal;</span><br><span class="line"><span class="keyword">in</span> <span class="type">vec3</span> FragPos;</span><br><span class="line"><span class="type">vec3</span> CalcEnvReflectLight(<span class="type">vec3</span> viewDir,<span class="type">vec3</span> norm) {</span><br><span class="line"> <span class="keyword">return</span> <span class="type">vec3</span>(<span class="built_in">texture</span>(skybox,<span class="built_in">reflect</span>(-viewDir,norm)));</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>当反射应用到一整个物体上时,这个物体看起来就像是钢或者铬这样的高反射性材质. 在现实中大部分的模型都不具有完全反射性。<strong>可以引入反射贴图(Reflection Map),来给模型更多的细节。与漫反射和镜面光贴图一样,反射贴图也是可以采样的纹理图像,它决定这片段的反射性</strong>。通过使用反射贴图,可以知道模型的哪些部分该以什么强度显示反射。</p><p><img data-src="https://s2.loli.net/2025/01/05/kCJ4toqla1Vy8FL.png" alt="image-20250105144159047"></p><p>环境映射的另一种形式是折射,它和反射很相似。折射是光线由于传播介质的改变而产生的方向变化。在常见的类水表面上所产生的现象就是折射,光线不是直直地传播,而是弯曲了一点。将你的半只胳膊伸进水里,观察出来的就是这种效果。</p><p><img data-src="https://learnopengl-cn.github.io/img/04/06/cubemaps_refraction_theory.png" alt="img"></p><p>折射率是光在真空中的传播速度与光在该介质中的传播速度之比,光从介质1射入介质2发生折射时,入射角与折射角的正弦之比叫做介质2相对介质1的折射率. 折射率越大,光线扭曲程度越大. 有一个观察向量I¯,一个法向量N¯,而这次是折射向量R¯。</p><p>观察向量的方向轻微弯曲了。弯折后的向量R¯R¯将会用来从立方体贴图中采样。折射可以使用GLSL的内建refract函数来轻松实现,它需要一个法向量、一个观察方向和两个材质之间的折射率(Refractive Index)。</p><p>假设看向水面,折射率就是在空气中的折射率/在水中的折射率.</p><p><img data-src="https://s2.loli.net/2025/01/05/qVRmaAPHS14loCg.png" alt="image-20250105144121316"></p><h4 id="动态环境贴图"><a href="#动态环境贴图" class="headerlink" title="动态环境贴图"></a>动态环境贴图</h4><p> 现在使用的都是静态图像的组合来作为天空盒,看起来很不错,但它<strong>没有在场景中包括可移动的物体</strong>。我们一直都没有注意到这一点,因为我们只使用了一个物体。如果我们有一个镜子一样的物体,周围还有多个物体,镜子中可见的只有天空盒,看起来就像它是场景中唯一一个物体一样。</p><p> <strong>通过使用帧缓冲能够为物体的6个不同角度创建出场景的纹理,并在每个渲染迭代中将它们储存到一个立方体贴图中。之后我们就可以使用这个(动态生成的)立方体贴图来创建出更真实的,包含其它物体的,反射和折射表面了。这就叫做动态环境映射(Dynamic Environment Mapping),因为动态创建了物体周围的立方体贴图,并将其用作环境贴图。</strong></p><p> 但它有一个很大的缺点:<strong>需要为使用环境贴图的物体渲染场景6次,这是对程序是非常大的性能开销。现代的程序通常会尽可能使用天空盒,并在可能的时候使用预编译的立方体贴图</strong>,只要它们能产生一点动态环境贴图的效果。虽然动态环境贴图是一个很棒的技术,但是要想在不降低性能的情况下让它工作还是需要非常多的技巧的。</p><h2 id="高级数据"><a href="#高级数据" class="headerlink" title="高级数据"></a>高级数据</h2><p>缓冲有不同的类型,比如GL_ARRAY_BUFFER,GL_ELEMENT_BUFFER. 不同缓冲有不同用处. 当<code>glGenBuffers()</code>创建缓冲对象后,需要绑定相应类型才有意义. </p><h3 id="填充缓冲数据"><a href="#填充缓冲数据" class="headerlink" title="填充缓冲数据"></a>填充缓冲数据</h3><p>glBufferData函数来填充缓冲对象所管理的内存,这个函数会分配一块内存,并将数据添加到这块内存中。如果我们将它的<code>data</code>参数设置为<code>NULL</code>,那么这个函数将只会分配内存,但不进行填充。这在我们需要<strong>预留</strong>(Reserve)特定大小的内存,之后回到这个缓冲一点一点填充的时候会很有用。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">glBufferData</span>(GL_ARRAY_BUFFER,,<span class="built_in"><span class="keyword">sizeof</span></span>(vertices),<span class="literal">NULL</span>,GL_STATIC_DRAW);</span><br><span class="line"><span class="built_in">glBufferSubData</span>(GL_ARRAY_BUFFER,<span class="number">24</span>,<span class="built_in"><span class="keyword">sizeof</span></span>(data),&data);</span><br></pre></td></tr></table></figure><p>除了使用一次函数调用填充整个缓冲之外,也可以使用glBufferSubData,填充缓冲的特定区域。这个函数需要一个缓冲目标、一个偏移量、数据的大小和数据本身作为它的参数。这个函数不同的地方在于,我们可以提供一个偏移量,指定从<strong>何处</strong>开始填充这个缓冲。这能够让我们插入或者更新缓冲内存的某一部分。要注意的是,缓冲需要有足够的已分配内存,所以对一个缓冲调用glBufferSubData之前必须要先调用glBufferData。</p><p>此外可以获得指向内存的指针,通过一些内存函数进行操作</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">float</span> data[] = {</span><br><span class="line"> <span class="number">0.5f</span>, <span class="number">1.0f</span>, <span class="number">-0.35f</span></span><br><span class="line"> ...</span><br><span class="line">};</span><br><span class="line"><span class="built_in">glBindBuffer</span>(GL_ARRAY_BUFFER, buffer);</span><br><span class="line"><span class="comment">// 获取指针</span></span><br><span class="line"><span class="keyword">void</span> *ptr = <span class="built_in">glMapBuffer</span>(GL_ARRAY_BUFFER, GL_WRITE_ONLY);</span><br><span class="line"><span class="comment">// 复制数据到内存</span></span><br><span class="line"><span class="built_in">memcpy</span>(ptr, data, <span class="built_in"><span class="keyword">sizeof</span></span>(data));</span><br><span class="line"><span class="comment">// 记得告诉OpenGL我们不再需要这个指针了</span></span><br><span class="line"><span class="built_in">glUnmapBuffer</span>(GL_ARRAY_BUFFER);</span><br><span class="line"></span><br><span class="line"><span class="comment">// in modern c++ </span></span><br><span class="line"> <span class="keyword">auto</span> ptr = <span class="built_in">glMapBuffer</span>(GL_ARRAY_BUFFER, GL_READ_WRITE);</span><br><span class="line"> std::<span class="built_in">copy</span>(std::<span class="built_in">begin</span>(skyboxVertices), std::<span class="built_in">end</span>(skyboxVertices),</span><br><span class="line"> <span class="keyword">static_cast</span><<span class="keyword">float</span> *>(ptr));</span><br><span class="line"> <span class="built_in">glUnmapBuffer</span>(GL_ARRAY_BUFFER);</span><br></pre></td></tr></table></figure><p>使用glUnmapBuffer函数,告诉OpenGL我们已经完成指针操作之后,OpenGL就会知道你已经完成了。<strong>在解除映射(Unmapping)之后,指针将会不再可用,并且如果OpenGL能够成功将您的数据映射到缓冲中,这个函数将会返回GL_TRUE</strong></p><h3 id="分批顶点属性"><a href="#分批顶点属性" class="headerlink" title="分批顶点属性"></a>分批顶点属性</h3><p>假设数据如下,表示一个点的三种属性,通过glBufferData加载</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">float</span> vertices[] = {</span><br><span class="line"> <span class="comment">// positions // normals // texture coords</span></span><br><span class="line"> <span class="number">-0.5f</span>, <span class="number">-0.5f</span>, <span class="number">-0.5f</span>, <span class="number">0.0f</span>, <span class="number">0.0f</span>, <span class="number">-1.0f</span>, <span class="number">0.0f</span>, <span class="number">0.0f</span>, <span class="number">0.5f</span>, <span class="number">-0.5f</span>,</span><br><span class="line"> <span class="number">-0.5f</span>, <span class="number">0.0f</span>, <span class="number">0.0f</span>, <span class="number">-1.0f</span>, <span class="number">1.0f</span>, <span class="number">0.0f</span>, <span class="number">0.5f</span>, <span class="number">0.5f</span>, <span class="number">-0.5f</span>, <span class="number">0.0f</span>,</span><br><span class="line"> <span class="number">0.0f</span>, <span class="number">-1.0f</span>, <span class="number">1.0f</span>, <span class="number">1.0f</span>, <span class="number">0.5f</span>, <span class="number">0.5f</span>, <span class="number">-0.5f</span>, <span class="number">0.0f</span>, <span class="number">0.0f</span>, <span class="number">-1.0f</span>,</span><br><span class="line"> <span class="number">1.0f</span>, <span class="number">1.0f</span>, <span class="number">-0.5f</span>, <span class="number">0.5f</span>, <span class="number">-0.5f</span>, <span class="number">0.0f</span>, <span class="number">0.0f</span>, <span class="number">-1.0f</span>, <span class="number">0.0f</span>, <span class="number">1.0f</span>,</span><br><span class="line"> <span class="number">-0.5f</span>, <span class="number">-0.5f</span>, <span class="number">-0.5f</span>, <span class="number">0.0f</span>, <span class="number">0.0f</span>, <span class="number">-1.0f</span>, <span class="number">0.0f</span>, <span class="number">0.0f</span>,</span><br><span class="line">}</span><br><span class="line"> <span class="built_in">glBufferData</span>(GL_ARRAY_BUFFER, <span class="built_in"><span class="keyword">sizeof</span></span>(vertices), vertices, GL_STREAM_DRAW);</span><br><span class="line"> <span class="built_in">glEnableVertexAttribArray</span>(<span class="number">0</span>);</span><br><span class="line"> <span class="built_in">glVertexAttribPointer</span>(<span class="number">0</span>, <span class="number">3</span>, GL_FLOAT, GL_FALSE, <span class="number">8</span> * <span class="built_in"><span class="keyword">sizeof</span></span>(<span class="keyword">float</span>), (<span class="keyword">void</span> *)<span class="number">0</span>);</span><br><span class="line"> <span class="built_in">glEnableVertexAttribArray</span>(<span class="number">1</span>);</span><br><span class="line"> <span class="built_in">glVertexAttribPointer</span>(<span class="number">1</span>, <span class="number">3</span>, GL_FLOAT, GL_FALSE, <span class="number">8</span> * <span class="built_in"><span class="keyword">sizeof</span></span>(<span class="keyword">float</span>), (<span class="keyword">void</span> *)(<span class="number">3</span> * <span class="built_in"><span class="keyword">sizeof</span></span>(<span class="keyword">float</span>)));</span><br><span class="line"> <span class="built_in">glEnableVertexAttribArray</span>(<span class="number">2</span>);</span><br><span class="line"> <span class="built_in">glVertexAttribPointer</span>(<span class="number">2</span>, <span class="number">2</span>, GL_FLOAT, GL_FALSE, <span class="number">8</span> * <span class="built_in"><span class="keyword">sizeof</span></span>(<span class="keyword">float</span>),(<span class="keyword">void</span> *)(<span class="number">6</span> * <span class="built_in"><span class="keyword">sizeof</span></span>(<span class="keyword">float</span>)));</span><br></pre></td></tr></table></figure><p>glVertexAttribPointer设置了解析这些数据的方式,其中3表示这个属性有几个值,8*sizeof(float)表示下一个点的相同属性的距离,0是偏移量.</p><p>假设读入数据布局不同,比如</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">float</span> vertices[] = {</span><br><span class="line"> <span class="comment">// positions </span></span><br><span class="line"> <span class="number">-0.5f</span>, <span class="number">-0.5f</span>, <span class="number">-0.5f</span>,</span><br><span class="line"> <span class="number">-0.5f</span>, <span class="number">0.0f</span>, <span class="number">0.0f</span>,</span><br><span class="line"> <span class="number">0.5f</span>, <span class="number">0.5f</span>, <span class="number">-0.5f</span>,</span><br><span class="line"> <span class="comment">// normals </span></span><br><span class="line"> <span class="number">0.0f</span>, <span class="number">0.0f</span>, <span class="number">-1.0f</span>, </span><br><span class="line"> <span class="number">0.0f</span>, <span class="number">0.0f</span>, <span class="number">0.5f</span>, </span><br><span class="line"> <span class="number">-0.5f</span>, <span class="number">-1.0f</span>, <span class="number">1.0f</span>, </span><br><span class="line"> <span class="comment">// texture coords</span></span><br><span class="line"> <span class="number">-1.0f</span>, <span class="number">1.0f</span>, </span><br><span class="line"> <span class="number">0.5f</span>, <span class="number">0.5f</span>, </span><br><span class="line"> <span class="number">0.0f</span>, <span class="number">0.0f</span>, ,}</span><br></pre></td></tr></table></figure><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">glVertexAttribPointer</span>(<span class="number">0</span>, <span class="number">3</span>, GL_FLOAT, GL_FALSE, <span class="number">3</span> * <span class="built_in"><span class="keyword">sizeof</span></span>(<span class="keyword">float</span>),(<span class="keyword">void</span> *)<span class="number">0</span>);</span><br><span class="line"><span class="built_in">glVertexAttribPointer</span>(<span class="number">1</span>, <span class="number">3</span>, GL_FLOAT, GL_FALSE, <span class="number">3</span> * <span class="built_in"><span class="keyword">sizeof</span></span>(<span class="keyword">float</span>),(<span class="keyword">void</span> *)(<span class="number">9</span>*<span class="built_in"><span class="keyword">sizeof</span></span>(<span class="keyword">float</span>)));</span><br><span class="line"><span class="built_in">glVertexAttribPointer</span>(<span class="number">2</span>, <span class="number">2</span>, GL_FLOAT, GL_FALSE, <span class="number">2</span> * <span class="built_in"><span class="keyword">sizeof</span></span>(<span class="keyword">float</span>),(<span class="keyword">void</span> *)(<span class="number">18</span>*<span class="built_in"><span class="keyword">sizeof</span></span>(<span class="keyword">float</span>)));</span><br></pre></td></tr></table></figure><p>此外可以通过<code>glBufferSubData</code>将数据从分别的数据加载到缓冲中</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">float</span> positions[] = { ... };</span><br><span class="line"><span class="keyword">float</span> normals[] = { ... };</span><br><span class="line"><span class="keyword">float</span> tex[] = { ... };</span><br><span class="line"><span class="comment">// 填充缓冲</span></span><br><span class="line"><span class="built_in">glBufferSubData</span>(GL_ARRAY_BUFFER, <span class="number">0</span>, <span class="built_in"><span class="keyword">sizeof</span></span>(positions), &positions);</span><br><span class="line"><span class="built_in">glBufferSubData</span>(GL_ARRAY_BUFFER, <span class="built_in"><span class="keyword">sizeof</span></span>(positions), <span class="built_in"><span class="keyword">sizeof</span></span>(normals), &normals);</span><br><span class="line"><span class="built_in">glBufferSubData</span>(GL_ARRAY_BUFFER, <span class="built_in"><span class="keyword">sizeof</span></span>(positions) + <span class="built_in"><span class="keyword">sizeof</span></span>(normals), <span class="built_in"><span class="keyword">sizeof</span></span>(tex), &tex);</span><br></pre></td></tr></table></figure><h4 id="复制缓冲"><a href="#复制缓冲" class="headerlink" title="复制缓冲"></a>复制缓冲</h4><p>当你的缓冲已经填充好数据之后,你可能会想与其它的缓冲共享其中的数据,或者想要将缓冲的内容复制到另一个缓冲当中。 这可以通过刚才的glMapBuffer实现. </p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">GLuint vbo1, vbo2;</span><br><span class="line"><span class="built_in">glGenBuffers</span>(<span class="number">1</span>, &vbo1);</span><br><span class="line"><span class="built_in">glGenBuffers</span>(<span class="number">1</span>, &vbo2);</span><br><span class="line"><span class="built_in">glBindBuffer</span>(GL_ARRAY_BUFFER, vbo1);</span><br><span class="line"><span class="keyword">float</span> data[] ={<span class="number">1.f</span>, <span class="number">0.f</span>, <span class="number">0.f</span>, <span class="number">1.f</span>, <span class="number">0.f</span>, <span class="number">0.f</span>, <span class="number">1.f</span>, <span class="number">0.f</span>, <span class="number">0.f</span>, <span class="number">1.f</span>, <span class="number">0.f</span>, <span class="number">0.f</span>};</span><br><span class="line"><span class="keyword">auto</span> ptr = <span class="built_in">glMapBuffer</span>(GL_ARRAY_BUFFER, GL_WRITE_ONLY);</span><br><span class="line">std::<span class="built_in">copy</span>(std::<span class="built_in">begin</span>(data), std::<span class="built_in">end</span>(data), <span class="keyword">static_cast</span><<span class="keyword">float</span> *>(ptr));</span><br><span class="line"><span class="built_in">glUnmapBuffer</span>(GL_ARRAY_BUFFER);</span><br><span class="line"><span class="built_in">glBindBuffer</span>(GL_ARRAY_BUFFER, vbo2);</span><br><span class="line">ptr = <span class="built_in">glMapBuffer</span>(GL_ARRAY_BUFFER, GL_READ_ONLY);</span><br><span class="line"><span class="keyword">float</span> data1[<span class="number">12</span>];</span><br><span class="line">std::<span class="built_in">copy</span>(data1, data1 + <span class="number">12</span>, <span class="keyword">static_cast</span><<span class="keyword">float</span> *>(ptr));</span><br><span class="line"><span class="built_in">glUnmapBuffer</span>(GL_ARRAY_BUFFER);</span><br><span class="line">ptr = <span class="built_in">glMapBuffer</span>(GL_ARRAY_BUFFER, GL_WRITE_ONLY);</span><br><span class="line">std::<span class="built_in">copy</span>(std::<span class="built_in">begin</span>(data), std::<span class="built_in">end</span>(data), <span class="keyword">static_cast</span><<span class="keyword">float</span> *>(ptr));</span><br><span class="line"><span class="built_in">glUnmapBuffer</span>(GL_ARRAY_BUFFER);</span><br></pre></td></tr></table></figure><p>但通过<code>glCopyBufferSubData</code>更方便.</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">glCopyBufferSubData</span><span class="params">(GLenum readtarget, GLenum writetarget, GLintptr readoffset,</span></span></span><br><span class="line"><span class="params"><span class="function"> GLintptr writeoffset, GLsizeiptr size)</span></span>;</span><br></pre></td></tr></table></figure><p><code>readtarget</code>和<code>writetarget</code>参数需要填入复制源和复制目标的缓冲目标。比如可以将VERTEX_ARRAY_BUFFER缓冲复制到VERTEX_ELEMENT_ARRAY_BUFFER缓冲,分别将这些缓冲目标设置为读和写的目标。当前绑定到这些缓冲目标的缓冲将会被影响到。</p><p>但如果我们想读写数据的两个不同缓冲都为顶点数组缓冲该怎么办呢?我们不能同时将两个缓冲绑定到同一个缓冲目标上。<strong>OpenGL提供另外两个缓冲目标,叫做GL_COPY_READ_BUFFER和GL_COPY_WRITE_BUFFER。</strong>接下来就可以将需要的缓冲绑定到这两个缓冲目标上,并将这两个目标作为<code>readtarget</code>和<code>writetarget</code>参数</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">float</span> vertexData[] = { ... };</span><br><span class="line"><span class="built_in">glBindBuffer</span>(GL_COPY_READ_BUFFER, vbo1);</span><br><span class="line"><span class="built_in">glBindBuffer</span>(GL_COPY_WRITE_BUFFER, vbo2);</span><br><span class="line"><span class="built_in">glCopyBufferSubData</span>(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, <span class="number">0</span>, <span class="number">0</span>, <span class="built_in"><span class="keyword">sizeof</span></span>(vertexData));</span><br></pre></td></tr></table></figure><p>也可以只将<code>writetarget</code>缓冲绑定为新的缓冲目标类型之一:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">float</span> vertexData[] = { ... };</span><br><span class="line"><span class="built_in">glBindBuffer</span>(GL_ARRAY_BUFFER, vbo1);</span><br><span class="line"><span class="built_in">glBindBuffer</span>(GL_COPY_WRITE_BUFFER, vbo2);</span><br><span class="line"><span class="built_in">glCopyBufferSubData</span>(GL_ARRAY_BUFFER, GL_COPY_WRITE_BUFFER, <span class="number">0</span>, <span class="number">0</span>, <span class="built_in"><span class="keyword">sizeof</span></span>(vertexData));</span><br></pre></td></tr></table></figure><h3 id="几何着色器"><a href="#几何着色器" class="headerlink" title="几何着色器"></a>几何着色器</h3><p> <strong>在顶点和片段着色器之间有一个可选的几何着色器(Geometry Shader),几何着色器的输入是一个图元(如点或三角形)的一组顶点</strong>。几何着色器可以在顶点发送到下一着色器阶段之前对它们随意变换。然而,几何着色器最有趣的地方在于,<strong>它能够将(这一组)顶点变换为完全不同的图元,并且还能生成比原来更多的顶点。</strong></p><p>首先定义在c++中绘画命令指定的图元类型,比如<code>GLDrawArrays(GL_POINTS,0,4)</code>绘画点图元,而几何着色器能够作为顶点和片段着色器之间的桥梁,能够增删/修改点的属性. out定义输出的图元以及最多的定点数.</p><figure class="highlight glsl"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#version 330 core</span></span><br><span class="line"><span class="keyword">layout</span> (<span class="keyword">points</span>) <span class="keyword">in</span>;</span><br><span class="line"><span class="keyword">layout</span> (<span class="keyword">line_strip</span>, <span class="keyword">max_vertices</span> = <span class="number">2</span>) <span class="keyword">out</span>;</span><br><span class="line"></span><br><span class="line"><span class="type">void</span> main() { </span><br><span class="line"> <span class="built_in">gl_Position</span> = <span class="built_in">gl_in</span>[<span class="number">0</span>].<span class="built_in">gl_Position</span> + <span class="type">vec4</span>(<span class="number">-0.1</span>, <span class="number">0.0</span>, <span class="number">0.0</span>, <span class="number">0.0</span>); </span><br><span class="line"> <span class="built_in">EmitVertex</span>();</span><br><span class="line"></span><br><span class="line"> <span class="built_in">gl_Position</span> = <span class="built_in">gl_in</span>[<span class="number">0</span>].<span class="built_in">gl_Position</span> + <span class="type">vec4</span>( <span class="number">0.1</span>, <span class="number">0.0</span>, <span class="number">0.0</span>, <span class="number">0.0</span>);</span><br><span class="line"> <span class="built_in">EmitVertex</span>();</span><br><span class="line"></span><br><span class="line"> <span class="built_in">EndPrimitive</span>();</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>几何着色器需要声明输入和输出的图元类型,以处理从顶点着色器接收的数据并生成新的几何图形。对于输入图元类型,在<code>in</code>关键字前使用布局修饰符(Layout Qualifier),可接收如下图元:</p><ul><li><code>points</code>:用于单个点。图元所包含的最小顶点数 1</li><li><code>lines</code>:用于线段或线带。</li><li><code>lines_adjacency</code>:用于带有相邻信息的线段或线带。</li><li><code>triangles</code>:用于三角形、三角形带或三角形扇。</li><li><code>triangles_adjacency</code>:用于带有相邻信息的三角形或三角形带。</li></ul><p>括号中的数字表示构成该图元所需的最小顶点数。</p><p>对于输出图元类型,在<code>out</code>关键字前同样使用布局修饰符,可设置为:</p><ul><li><code>points</code></li><li><code>line_strip</code></li><li><code>triangle_strip</code></li></ul><p> 这些输出图元允许几何着色器创建各种形状。例如,为了生成一个三角形,可以将输出定义为<code>triangle_strip</code>并提供3个顶点。</p><p>此外,几何着色器还要求指定其能输出的最大顶点数量,防止超出限制导致OpenGL忽略额外的顶点。此最大值也在<code>out</code>布局修饰符中设定。例如,若要输出一条线段,则应将最大顶点数设为2。</p><p>线条(Line Strip)是由一系列点组成的连续线段,至少需要两个点来形成。每增加一个点,就会与前一个点之间形成一条新的线段。例如,如果有5个顶点,它们会依次相连形成4条线段。</p><p> 当使用几何着色器时,如果最大输出顶点数设为2,则只能输出一条线段。为了生成更复杂的形状,几何着色器可以通过GLSL提供的内置变量<code>gl_in[]</code>访问来自上一阶段(如顶点着色器)的顶点数据。<code>gl_in[]</code>是一个接口块,它包含了每个输入顶点的位置和其他信息,如<code>gl_Position</code>等。</p><p> 几何着色器通过调用<code>EmitVertex()</code>函数来发射一个新的顶点,并最终通过<code>EndPrimitive()</code>函数来完成一个图元的定义。代码修改了原始顶点的位置,创建了两个新位置,然后发射了这两个顶点,最后通过调用<code>EndPrimitive()</code>将它们合成为一个线条图元。这样就创建了一条从原始顶点位置向左和向右各平移0.1单位的新线段。</p><p> <strong>线条是由连续的点组成,几何着色器可以访问先前阶段的顶点数据并通过发射顶点和结束图元的方法来创建新的几何图形</strong></p><h3 id="爆破物体"><a href="#爆破物体" class="headerlink" title="爆破物体"></a>爆破物体</h3><p>当我们说<strong>爆破</strong>一个物体时,我们并不是指要将宝贵的顶点集给炸掉,我们是要将每个三角形沿着法向量的方向移动一小段时间。效果就是,整个物体看起来像是沿着每个三角形的法线向量<strong>爆炸</strong>一样。</p><p>做法就是在几何着色器中计算一个三角形面的法向量(通过面上的三个点得到两个向量并计算叉乘),在几何着色器中改变顶点位置.</p><h3 id="法向量可视化"><a href="#法向量可视化" class="headerlink" title="法向量可视化"></a>法向量可视化</h3><p> 可以作为一种Debug工具,当编写光照着色器时,你可能会最终会得到一些奇怪的视觉输出,但又很难确定导致问题的原因。<strong>光照错误很常见的原因就是法向量错误,这可能是由于不正确加载顶点数据、错误地将它们定义为顶点属性或在着色器中不正确地管理所导致的</strong>。想要的是使用某种方式来检测提供的法向量是正确的。检测法向量是否正确的一个很好的方式就是对它们进行可视化,几何着色器正是实现这一目的非常有用的工具。</p><p> 首先不使用几何着色器正常绘制场景。然后再次绘制场景,但这次只显示通过几何着色器生成法向量。几何着色器接收一个三角形图元,并沿着法向量生成三条线——每个顶点一个法向量。</p><h3 id="实例化"><a href="#实例化" class="headerlink" title="实例化"></a>实例化</h3><p>如果我们需要渲染大量物体时,代码看起来会像这样:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span>(<span class="keyword">unsigned</span> <span class="keyword">int</span> i = <span class="number">0</span>; i < amount_of_models_to_draw; i++)</span><br><span class="line">{</span><br><span class="line"> <span class="built_in">DoSomePreparations</span>(); <span class="comment">// 绑定VAO,绑定纹理,设置uniform等</span></span><br><span class="line"> <span class="built_in">glDrawArrays</span>(GL_TRIANGLES, <span class="number">0</span>, amount_of_vertices);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>如果像这样绘制模型的大量实例(Instance),你很快就会因为绘制调用过多而达到性能瓶颈。与绘制顶点本身相比,使用glDrawArrays或glDrawElements函数告诉GPU去绘制你的顶点数据会消耗更多的性能,因为<strong>OpenGL在绘制顶点数据之前需要做很多准备工作(比如告诉GPU该从哪个缓冲读取数据,从哪寻找顶点属性,而且这些都是在相对缓慢的CPU到GPU总线(CPU to GPU Bus)上进行的)</strong>。所以,即便渲染顶点非常快,命令GPU去渲染却未必。</p><p>如果<strong>能够将数据一次性发送给GPU,然后使用一个绘制函数让OpenGL利用这些数据绘制多个物体,就会更方便了。这就是实例化(Instancing)</strong>。</p><link rel="stylesheet" href="/css/spoiler.css" type="text/css"><script src="/js/spoiler.js" type="text/javascript" async></script>]]></content>
<summary type="html"><p>从手动设置顶点坐标到加载模型以及OpenGL高级技巧<br></summary>
<category term="OpenGL" scheme="https://www.sekyoro.top/tags/OpenGL/"/>
<category term="computer graphics" scheme="https://www.sekyoro.top/tags/computer-graphics/"/>
</entry>
<entry>
<title>Agentic RAG and LLMs-based assistant</title>
<link href="https://www.sekyoro.top/2024/12/26/Agentic-RAG-and-LLMs-based-assistant/"/>
<id>https://www.sekyoro.top/2024/12/26/Agentic-RAG-and-LLMs-based-assistant/</id>
<published>2024-12-26T12:25:13.000Z</published>
<updated>2024-12-31T10:38:35.705Z</updated>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="\assets\js\Meting.min.js"></script><p>既text2img和大语言模型之后的热点,AI Agent和RAG,简单来说在既有的多模态大模型基础上打造应用,比如利用多个智能体以及外部数据进行搜索. 目前来看还是有一些发展潜力,这方面的资料多见于Hugging Face,LangChain,llama index等等<br><span id="more"></span></p><p> 过去一段时间涌现了一大堆大模型,参数量多达几十B,并且这些模型在许多benchmark上都似乎达到了很高的水平(甚至最近OpenAI的o3达到超越普通人的水平,AGI达没达到不知道,Hype是足够了). 普通人想免费下载的大模型就是贴心的llama了,可以在llama上进行微调等操作. 此外文生图等模型既Stable Diffusion v3.5之后,出走后的人员打造FLUX. </p><p> 种种迹象表明,Foundation Model的发展已经超出普通人能玩的范围了,因为背后耗费这成千上万张显卡以及大量人员精心修剪后的数据。而如何实现长期盈利才是关键,OpenAI早已打出了名声,作为行业标杆,不少人会去订阅ChatGPT Plus,即使再贵. 但其他公式就会遭殃了,毕竟一个人使用一到两种类似的AI服务已经足够,于是形成了类似冠军争夺,专业做AI的,只有前几家才能生成. 大厂可以背靠大量数据集和人力做模型以及服务,中小公司相对更困难. 而普通人貌似要么花钱使用Open(并不Open)AI更高质量服务,要么用开源的模型,事实上,在这个开源领域正在或者应该,在开发者之间流行,作为辅助工具抑或创造性的图片生成,即使无法盈利,但也逐渐变成计算器一样的工具成为普遍现象. 其中的一些有趣技术就包括T2I文生图以及相关的LoRA,ControlNet. 而多模态大语言模型就是RAG和AI Agent了.</p><p>目前国内大中厂以及个人开发者都在这些方向不断努力</p><p><img data-src="https://s2.loli.net/2024/12/26/4HhWUIlqwgAYEKJ.png" alt="image-20241226205431156"></p><h2 id="For-any-API-provider"><a href="#For-any-API-provider" class="headerlink" title="For any API provider"></a>For any API provider</h2><p>因为目前有许多大模型供应商,比如grok,gemini,openai,它们提供不同的大模型和接口. 如果为多个不同供应商分别写然后能够统一进行调用就更好了. HuggingFace提供了相关方法</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> os</span><br><span class="line"><span class="keyword">from</span> openai <span class="keyword">import</span> OpenAI</span><br><span class="line"></span><br><span class="line">openai_role_conversions = {</span><br><span class="line"> MessageRole.TOOL_RESPONSE: MessageRole.USER,</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">OpenAIEngine</span>:</span></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">__init__</span>(<span class="params">self, model_name=<span class="string">"gpt-4o"</span></span>):</span></span><br><span class="line"> self.model_name = model_name</span><br><span class="line"> self.client = OpenAI(</span><br><span class="line"> api_key=os.getenv(<span class="string">"OPENAI_API_KEY"</span>),</span><br><span class="line"> )</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">__call__</span>(<span class="params">self, messages, stop_sequences=[]</span>):</span></span><br><span class="line"> messages = get_clean_message_list(messages, role_conversions=openai_role_conversions)</span><br><span class="line"></span><br><span class="line"> response = self.client.chat.completions.create(</span><br><span class="line"> model=self.model_name,</span><br><span class="line"> messages=messages,</span><br><span class="line"> stop=stop_sequences,</span><br><span class="line"> temperature=<span class="number">0.5</span>,</span><br><span class="line"> )</span><br><span class="line"> <span class="keyword">return</span> response.choices[<span class="number">0</span>].message.content</span><br></pre></td></tr></table></figure><p>核心是构建一个llm_engine然后通过ReactCodeAgent或者其他Agent调用</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> anthropic <span class="keyword">import</span> Anthropic, AnthropicBedrock</span><br><span class="line"><span class="comment"># Cf this page for using Anthropic from Bedrock: https://docs.anthropic.com/en/api/claude-on-amazon-bedrock</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">AnthropicEngine</span>:</span></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">__init__</span>(<span class="params">self, model_name=<span class="string">"claude-3-5-sonnet-20240620"</span>, use_bedrock=<span class="literal">False</span></span>):</span></span><br><span class="line"> self.model_name = model_name</span><br><span class="line"> <span class="keyword">if</span> use_bedrock:</span><br><span class="line"> self.model_name = <span class="string">"anthropic.claude-3-5-sonnet-20240620-v1:0"</span></span><br><span class="line"> self.client = AnthropicBedrock(</span><br><span class="line"> aws_access_key=os.getenv(<span class="string">"AWS_BEDROCK_ID"</span>),</span><br><span class="line"> aws_secret_key=os.getenv(<span class="string">"AWS_BEDROCK_KEY"</span>),</span><br><span class="line"> aws_region=<span class="string">"us-east-1"</span>,</span><br><span class="line"> )</span><br><span class="line"> <span class="keyword">else</span>:</span><br><span class="line"> self.client = Anthropic(</span><br><span class="line"> api_key=os.getenv(<span class="string">"ANTHROPIC_API_KEY"</span>),</span><br><span class="line"> )</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">__call__</span>(<span class="params">self, messages, stop_sequences=[]</span>):</span></span><br><span class="line"> messages = get_clean_message_list(messages, role_conversions=openai_role_conversions)</span><br><span class="line"> index_system_message, system_prompt = <span class="literal">None</span>, <span class="literal">None</span></span><br><span class="line"> <span class="keyword">for</span> index, message <span class="keyword">in</span> <span class="built_in">enumerate</span>(messages):</span><br><span class="line"> <span class="keyword">if</span> message[<span class="string">"role"</span>] == MessageRole.SYSTEM:</span><br><span class="line"> index_system_message = index</span><br><span class="line"> system_prompt = message[<span class="string">"content"</span>]</span><br><span class="line"> <span class="keyword">if</span> system_prompt <span class="keyword">is</span> <span class="literal">None</span>:</span><br><span class="line"> <span class="keyword">raise</span> Exception(<span class="string">"No system prompt found!"</span>)</span><br><span class="line"></span><br><span class="line"> filtered_messages = [message <span class="keyword">for</span> i, message <span class="keyword">in</span> <span class="built_in">enumerate</span>(messages) <span class="keyword">if</span> i != index_system_message]</span><br><span class="line"> <span class="keyword">if</span> <span class="built_in">len</span>(filtered_messages) == <span class="number">0</span>:</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">"Error, no user message:"</span>, messages)</span><br><span class="line"> <span class="keyword">assert</span> <span class="literal">False</span></span><br><span class="line"></span><br><span class="line"> response = self.client.messages.create(</span><br><span class="line"> model=self.model_name,</span><br><span class="line"> system=system_prompt,</span><br><span class="line"> messages=filtered_messages,</span><br><span class="line"> stop_sequences=stop_sequences,</span><br><span class="line"> temperature=<span class="number">0.5</span>,</span><br><span class="line"> max_tokens=<span class="number">2000</span>,</span><br><span class="line"> )</span><br><span class="line"> full_response_text = <span class="string">""</span></span><br><span class="line"> <span class="keyword">for</span> content_block <span class="keyword">in</span> response.content:</span><br><span class="line"> <span class="keyword">if</span> content_block.<span class="built_in">type</span> == <span class="string">"text"</span>:</span><br><span class="line"> full_response_text += content_block.text</span><br><span class="line"> <span class="keyword">return</span> full_response_text</span><br></pre></td></tr></table></figure><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">agent = ReactCodeAgent(tools=[], llm_engine=llm_engine)</span><br></pre></td></tr></table></figure><h2 id="Intro-to-RAG"><a href="#Intro-to-RAG" class="headerlink" title="Intro to RAG"></a>Intro to RAG</h2><p>RAG是一种流行的方法,用于解决LLM由于所述内容不在其训练数据中而无法意识到特定内容的问题,或者即使以前看到过该内容也会产生幻觉。这些特定内容可能是专有的、敏感的,或者是最近的和经常更新的。</p><p>如果数据是静态的,并且不定期更改,则可以考虑对大型模型进行微调。然而,在许多情况下,微调可能是昂贵的,并且,当重复进行时(例如,为了解决数据漂移),会导致“模型转移”。这是指模型的行为以一种没有改变的方式发生变化</p><p>RAG(检索增强生成)不需要模型微调。相反,RAG通过向LLM提供从相关数据中检索的附加上下文来工作,以便它可以生成更明智的响应。</p><p><img data-src="https://huggingface.co/datasets/huggingface/cookbook-images/resolve/main/rag-diagram.png" alt="RAG diagram"></p><p>这类应用可以非常方便写文档、报告的人,因为时常会有比较新的消息而大模型训练资料中没有,这个时候可以让大模型主动调用外部工具(比如搜索引擎),或者主动给它额外的工具从而补充大模型的能力.</p><h3 id="简易的RAG工作流"><a href="#简易的RAG工作流" class="headerlink" title="简易的RAG工作流"></a>简易的RAG工作流</h3><h4 id="加载数据"><a href="#加载数据" class="headerlink" title="加载数据"></a>加载数据</h4><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> getpass <span class="keyword">import</span> getpass</span><br><span class="line"><span class="keyword">from</span> langchain.document_loaders <span class="keyword">import</span> GitHubIssuesLoader</span><br><span class="line">ACCESS_TOKEN = getpass(<span class="string">"YOUR_GITHUB_PERSONAL_TOKEN"</span>)</span><br><span class="line">loader = GitHubIssuesLoader(repo=<span class="string">"huggingface/peft"</span>, access_token=ACCESS_TOKEN, include_prs=<span class="literal">False</span>, state=<span class="string">"all"</span>)</span><br><span class="line">docs = loader.load()</span><br></pre></td></tr></table></figure><p>使用github加载数据,将数据分块加载,最常见和直接的分块方法是定义一个固定大小的块,以及它们之间是否应该有任何重叠。在块之间保持一些重叠可以让我们在块之间保留一些语义上下文。一般文本的推荐拆分器是RecursiveCharacterTextSplitter</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> langchain.text_splitter <span class="keyword">import</span> RecursiveCharacterTextSplitter</span><br><span class="line">splitter = RecursiveCharacterTextSplitter(chunk_size=<span class="number">512</span>, chunk_overlap=<span class="number">30</span>)</span><br><span class="line">chunked_docs = splitter.split_documents(docs)</span><br></pre></td></tr></table></figure><h4 id="创建embedding和retriever"><a href="#创建embedding和retriever" class="headerlink" title="创建embedding和retriever"></a>创建embedding和retriever</h4><p>需要将文件转为特征,为了创建文档块嵌入,这里使用HuggingFaceEmbeddings和BAAI/ big -base-en-v1.5嵌入模型。Hub上还有许多其他的嵌入模型<a href="https://huggingface.co/spaces/mteb/leaderboard">MTEB Leaderboard - a Hugging Face Space by mteb</a>,使用FAISS作为嵌入特征搜索库,相当于创建一个数据库,其中数据就是嵌入后的特征</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> langchain.vectorstores <span class="keyword">import</span> FAISS</span><br><span class="line"><span class="keyword">from</span> langchain.embeddings <span class="keyword">import</span> HuggingFaceEmbeddings</span><br><span class="line"></span><br><span class="line">db = FAISS.from_documents(chunked_docs, HuggingFaceEmbeddings(model_name=<span class="string">"BAAI/bge-base-en-v1.5"</span>))</span><br></pre></td></tr></table></figure><p>设置检索方式,近邻搜索,返回最高的4个结果.</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">retriever = db.as_retriever(search_type=<span class="string">"similarity"</span>, search_kwargs={<span class="string">"k"</span>: <span class="number">4</span>})</span><br></pre></td></tr></table></figure><h4 id="LLM"><a href="#LLM" class="headerlink" title="LLM"></a>LLM</h4><p>刚才使用了嵌入模型,现在使用对输入prompt以及回复的llm,相关榜单<a href="https://huggingface.co/spaces/open-llm-leaderboard/open_llm_leaderboard">Open LLM Leaderboard - a Hugging Face Space by open-llm-leaderboard</a>.</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> torch</span><br><span class="line"><span class="keyword">from</span> transformers <span class="keyword">import</span> AutoTokenizer, AutoModelForCausalLM, BitsAndBytesConfig</span><br><span class="line"></span><br><span class="line">model_name = <span class="string">"HuggingFaceH4/zephyr-7b-beta"</span></span><br><span class="line"></span><br><span class="line">bnb_config = BitsAndBytesConfig(</span><br><span class="line"> load_in_4bit=<span class="literal">True</span>, bnb_4bit_use_double_quant=<span class="literal">True</span>, bnb_4bit_quant_type=<span class="string">"nf4"</span>, bnb_4bit_compute_dtype=torch.bfloat16</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">model = AutoModelForCausalLM.from_pretrained(model_name, quantization_config=bnb_config)</span><br><span class="line">tokenizer = AutoTokenizer.from_pretrained(model_name)</span><br></pre></td></tr></table></figure><p>此外对模型做了量化减小体积</p><h4 id="搭建LLM链"><a href="#搭建LLM链" class="headerlink" title="搭建LLM链"></a>搭建LLM链</h4><p>设置pipeline,</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> langchain.llms <span class="keyword">import</span> HuggingFacePipeline</span><br><span class="line"><span class="keyword">from</span> langchain.prompts <span class="keyword">import</span> PromptTemplate</span><br><span class="line"><span class="keyword">from</span> transformers <span class="keyword">import</span> pipeline</span><br><span class="line"><span class="keyword">from</span> langchain_core.output_parsers <span class="keyword">import</span> StrOutputParser</span><br><span class="line"></span><br><span class="line">text_generation_pipeline = pipeline(</span><br><span class="line"> model=model,</span><br><span class="line"> tokenizer=tokenizer,</span><br><span class="line"> task=<span class="string">"text-generation"</span>,</span><br><span class="line"> temperature=<span class="number">0.2</span>,</span><br><span class="line"> do_sample=<span class="literal">True</span>,</span><br><span class="line"> repetition_penalty=<span class="number">1.1</span>,</span><br><span class="line"> return_full_text=<span class="literal">True</span>,</span><br><span class="line"> max_new_tokens=<span class="number">400</span>,</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">llm = HuggingFacePipeline(pipeline=text_generation_pipeline)</span><br><span class="line"></span><br><span class="line">prompt_template = <span class="string">"""</span></span><br><span class="line"><span class="string"><|system|></span></span><br><span class="line"><span class="string">Answer the question based on your knowledge. Use the following context to help:</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">{context}</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string"></s></span></span><br><span class="line"><span class="string"><|user|></span></span><br><span class="line"><span class="string">{question}</span></span><br><span class="line"><span class="string"></s></span></span><br><span class="line"><span class="string"><|assistant|></span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string"> """</span></span><br><span class="line"></span><br><span class="line">prompt = PromptTemplate(</span><br><span class="line"> input_variables=[<span class="string">"context"</span>, <span class="string">"question"</span>],</span><br><span class="line"> template=prompt_template,</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">llm_chain = prompt | llm | StrOutputParser()</span><br></pre></td></tr></table></figure><p>结合llm_chain搭配retriver</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> langchain_core.runnables <span class="keyword">import</span> RunnablePassthrough</span><br><span class="line"></span><br><span class="line">retriever = db.as_retriever()</span><br><span class="line"></span><br><span class="line">rag_chain = {<span class="string">"context"</span>: retriever, <span class="string">"question"</span>: RunnablePassthrough()} | llm_chain</span><br></pre></td></tr></table></figure><h4 id="比较结果"><a href="#比较结果" class="headerlink" title="比较结果"></a>比较结果</h4><p>在没有rag下</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">question = <span class="string">"How do you combine multiple adapters?"</span></span><br><span class="line">llm_chain.invoke({<span class="string">"context"</span>: <span class="string">""</span>, <span class="string">"question"</span>: question})</span><br></pre></td></tr></table></figure><p>在有rag下</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">rag_chain.invoke(question)</span><br></pre></td></tr></table></figure><h2 id="Agent-with-tool-calling"><a href="#Agent-with-tool-calling" class="headerlink" title="Agent with tool-calling"></a>Agent with tool-calling</h2><p>大模型搭配tool calling(比如调用计算器,编程语言解析器,浏览器搜索等)</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> transformers <span class="keyword">import</span> load_tool, ReactCodeAgent, HfApiEngine</span><br><span class="line"></span><br><span class="line"><span class="comment"># Import tool from Hub</span></span><br><span class="line">image_generation_tool = load_tool(<span class="string">"m-ric/text-to-image"</span>, cache=<span class="literal">False</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># Import tool from LangChain</span></span><br><span class="line"><span class="keyword">from</span> transformers.agents.search <span class="keyword">import</span> DuckDuckGoSearchTool</span><br><span class="line"></span><br><span class="line">search_tool = DuckDuckGoSearchTool()</span><br><span class="line"></span><br><span class="line">llm_engine = HfApiEngine(<span class="string">"Qwen/Qwen2.5-72B-Instruct"</span>)</span><br><span class="line"><span class="comment"># Initialize the agent with both tools</span></span><br><span class="line">agent = ReactCodeAgent(tools=[image_generation_tool, search_tool], llm_engine=llm_engine)</span><br><span class="line"></span><br><span class="line"><span class="comment"># Run it!</span></span><br><span class="line">result = agent.run(</span><br><span class="line"> <span class="string">"Generate me a photo of the car that James bond drove in the latest movie."</span>,</span><br><span class="line">)</span><br><span class="line">result</span><br></pre></td></tr></table></figure><p>使用Qwen-2.5大模型,搭配文生图和搜索工具,相当于结合了多个模型和外部工具,让原本单一的模型具备多种功能.</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> datasets</span><br><span class="line"><span class="keyword">from</span> langchain.docstore.document <span class="keyword">import</span> Document</span><br><span class="line"><span class="keyword">from</span> langchain.text_splitter <span class="keyword">import</span> RecursiveCharacterTextSplitter</span><br><span class="line"><span class="keyword">from</span> langchain.vectorstores <span class="keyword">import</span> FAISS</span><br><span class="line"><span class="keyword">from</span> langchain_community.embeddings <span class="keyword">import</span> HuggingFaceEmbeddings</span><br><span class="line">knowledge_base = datasets.load_dataset(<span class="string">"m-ric/huggingface_doc"</span>, split=<span class="string">"train"</span>)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">source_docs = [</span><br><span class="line"> Document(page_content=doc[<span class="string">"text"</span>], metadata={<span class="string">"source"</span>: doc[<span class="string">"source"</span>].split(<span class="string">"/"</span>)[<span class="number">1</span>]}) <span class="keyword">for</span> doc <span class="keyword">in</span> knowledge_base</span><br><span class="line">]</span><br><span class="line"></span><br><span class="line">docs_processed = RecursiveCharacterTextSplitter(chunk_size=<span class="number">500</span>).split_documents(source_docs)[:<span class="number">1000</span>]</span><br><span class="line"></span><br><span class="line">embedding_model = HuggingFaceEmbeddings(model_name=<span class="string">"thenlper/gte-small"</span>)</span><br><span class="line">vectordb = FAISS.from_documents(documents=docs_processed, embedding=embedding_model)</span><br><span class="line"><span class="keyword">import</span> json</span><br><span class="line"><span class="keyword">from</span> transformers.agents <span class="keyword">import</span> Tool</span><br><span class="line"><span class="keyword">from</span> langchain_core.vectorstores <span class="keyword">import</span> VectorStore</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">RetrieverTool</span>(<span class="params">Tool</span>):</span></span><br><span class="line"> name = <span class="string">"retriever"</span></span><br><span class="line"> description = (</span><br><span class="line"> <span class="string">"Retrieves some documents from the knowledge base that have the closest embeddings to the input query."</span></span><br><span class="line"> )</span><br><span class="line"> inputs = {</span><br><span class="line"> <span class="string">"query"</span>: {</span><br><span class="line"> <span class="string">"type"</span>: <span class="string">"string"</span>,</span><br><span class="line"> <span class="string">"description"</span>: <span class="string">"The query to perform. This should be semantically close to your target documents. Use the affirmative form rather than a question."</span>,</span><br><span class="line"> },</span><br><span class="line"> <span class="string">"source"</span>: {<span class="string">"type"</span>: <span class="string">"string"</span>, <span class="string">"description"</span>: <span class="string">""</span>},</span><br><span class="line"> <span class="string">"number_of_documents"</span>: {</span><br><span class="line"> <span class="string">"type"</span>: <span class="string">"string"</span>,</span><br><span class="line"> <span class="string">"description"</span>: <span class="string">"the number of documents to retrieve. Stay under 10 to avoid drowning in docs"</span>,</span><br><span class="line"> },</span><br><span class="line"> }</span><br><span class="line"> output_type = <span class="string">"string"</span></span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">__init__</span>(<span class="params">self, vectordb: VectorStore, all_sources: <span class="built_in">str</span>, **kwargs</span>):</span></span><br><span class="line"> <span class="built_in">super</span>().__init__(**kwargs)</span><br><span class="line"> self.vectordb = vectordb</span><br><span class="line"> self.inputs[<span class="string">"source"</span>][<span class="string">"description"</span>] = (</span><br><span class="line"> <span class="string">f"The source of the documents to search, as a str representation of a list. Possible values in the list are: <span class="subst">{all_sources}</span>. If this argument is not provided, all sources will be searched."</span>.replace(</span><br><span class="line"> <span class="string">"'"</span>, <span class="string">"`"</span></span><br><span class="line"> )</span><br><span class="line"> )</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">forward</span>(<span class="params">self, query: <span class="built_in">str</span>, source: <span class="built_in">str</span> = <span class="literal">None</span>, number_of_documents=<span class="number">7</span></span>) -> <span class="built_in">str</span>:</span></span><br><span class="line"> <span class="keyword">assert</span> <span class="built_in">isinstance</span>(query, <span class="built_in">str</span>), <span class="string">"Your search query must be a string"</span></span><br><span class="line"> number_of_documents = <span class="built_in">int</span>(number_of_documents)</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> source:</span><br><span class="line"> <span class="keyword">if</span> <span class="built_in">isinstance</span>(source, <span class="built_in">str</span>) <span class="keyword">and</span> <span class="string">"["</span> <span class="keyword">not</span> <span class="keyword">in</span> <span class="built_in">str</span>(source): <span class="comment"># if the source is not representing a list</span></span><br><span class="line"> source = [source]</span><br><span class="line"> source = json.loads(<span class="built_in">str</span>(source).replace(<span class="string">"'"</span>, <span class="string">'"'</span>))</span><br><span class="line"></span><br><span class="line"> docs = self.vectordb.similarity_search(</span><br><span class="line"> query,</span><br><span class="line"> <span class="built_in">filter</span>=({<span class="string">"source"</span>: source} <span class="keyword">if</span> source <span class="keyword">else</span> <span class="literal">None</span>),</span><br><span class="line"> k=number_of_documents,</span><br><span class="line"> )</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> <span class="built_in">len</span>(docs) == <span class="number">0</span>:</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"No documents found with this filtering. Try removing the source filter."</span></span><br><span class="line"> <span class="keyword">return</span> <span class="string">"Retrieved documents:\n\n"</span> + <span class="string">"\n===Document===\n"</span>.join([doc.page_content <span class="keyword">for</span> doc <span class="keyword">in</span> docs])</span><br><span class="line"></span><br><span class="line"><span class="keyword">from</span> transformers.agents <span class="keyword">import</span> HfApiEngine, ReactJsonAgent</span><br><span class="line"></span><br><span class="line">llm_engine = HfApiEngine(<span class="string">"Qwen/Qwen2.5-72B-Instruct"</span>)</span><br><span class="line"></span><br><span class="line">retriever_tool = RetrieverTool(vectordb=vectordb, all_sources=all_sources)</span><br><span class="line">agent = ReactJsonAgent(tools=[retriever_tool], llm_engine=llm_engine, verbose=<span class="number">0</span>)</span><br><span class="line"></span><br><span class="line">agent_output = agent.run(<span class="string">"Please show me a LORA finetuning script"</span>)</span><br><span class="line"></span><br><span class="line"><span class="built_in">print</span>(<span class="string">"Final output:"</span>)</span><br><span class="line"><span class="built_in">print</span>(agent_output)</span><br></pre></td></tr></table></figure><p>通过RAG以及迭代的询问提升回答质量,首先还是加载数据,分块,编码为向量加载到一个向量数据库. </p><p><img data-src="https://s2.loli.net/2024/12/26/WSB5vpIPZzTwC31.png" alt="image-20241226215424509"></p><p>huggingface的transformers库自带调用解释器</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> transformers <span class="keyword">import</span> ReactCodeAgent</span><br><span class="line"></span><br><span class="line">agent = ReactCodeAgent(tools=[], llm_engine=HfApiEngine(<span class="string">"Qwen/Qwen2.5-72B-Instruct"</span>))</span><br><span class="line"></span><br><span class="line">code = <span class="string">"""</span></span><br><span class="line"><span class="string">list=[0, 1, 2]</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">for i in range(4):</span></span><br><span class="line"><span class="string"> print(list(i))</span></span><br><span class="line"><span class="string">"""</span></span><br><span class="line"></span><br><span class="line">final_answer = agent.run(</span><br><span class="line"> <span class="string">"I have some code that creates a bug: please debug it, then run it to make sure it works and return the final code"</span>,</span><br><span class="line"> code=code,</span><br><span class="line">)</span><br></pre></td></tr></table></figure><p>结合OpenAI的接口制作agent.</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> os</span><br><span class="line"><span class="keyword">from</span> openai <span class="keyword">import</span> OpenAI</span><br><span class="line"><span class="keyword">from</span> transformers.agents.llm_engine <span class="keyword">import</span> MessageRole, get_clean_message_list</span><br><span class="line"></span><br><span class="line">openai_role_conversions = {</span><br><span class="line"> MessageRole.TOOL_RESPONSE: <span class="string">"user"</span>,</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">OpenAIEngine</span>:</span></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">__init__</span>(<span class="params">self, model_name=<span class="string">"gpt-4o-2024-05-13"</span></span>):</span></span><br><span class="line"> self.model_name = model_name</span><br><span class="line"> self.client = OpenAI(</span><br><span class="line"> api_key=os.getenv(<span class="string">"OPENAI_API_KEY"</span>),</span><br><span class="line"> )</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">__call__</span>(<span class="params">self, messages, stop_sequences=[]</span>):</span></span><br><span class="line"> <span class="comment"># Get clean message list</span></span><br><span class="line"> messages = get_clean_message_list(messages, role_conversions=openai_role_conversions)</span><br><span class="line"></span><br><span class="line"> <span class="comment"># Get LLM output</span></span><br><span class="line"> response = self.client.chat.completions.create(</span><br><span class="line"> model=self.model_name,</span><br><span class="line"> messages=messages,</span><br><span class="line"> stop=stop_sequences,</span><br><span class="line"> )</span><br><span class="line"> <span class="keyword">return</span> response.choices[<span class="number">0</span>].message.content</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">openai_engine = OpenAIEngine()</span><br><span class="line">agent = ReactCodeAgent(llm_engine=openai_engine, tools=[])</span><br><span class="line"></span><br><span class="line">code = <span class="string">"""</span></span><br><span class="line"><span class="string">list=[0, 1, 2]</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">for i in range(4):</span></span><br><span class="line"><span class="string"> print(list(i))</span></span><br><span class="line"><span class="string">"""</span></span><br><span class="line"></span><br><span class="line">final_answer = agent.run(</span><br><span class="line"> <span class="string">"I have some code that creates a bug: please debug it and return the final code"</span>,</span><br><span class="line"> code=code,</span><br><span class="line">)</span><br></pre></td></tr></table></figure><h2 id="Agentic-RAG"><a href="#Agentic-RAG" class="headerlink" title="Agentic RAG"></a>Agentic RAG</h2><p>单纯的RAG也有局限性,最重要的是以下两点:</p><ul><li>它只执行一个检索步骤:如果结果不好,那么生成的结果也会不好。</li><li>语义相似度是以用户查询作为参考来计算的,这可能是次优的:例如,用户查询通常是一个问题,而包含真实答案的文档就是确定句式,因此与疑问形式的其他源文档相比,其相似度得分将被降低,从而导致丢失相关信息的风险。</li></ul><p>但是可以通过创建一个RAG代理来缓解这些问题:</p><ol><li>不直接使用用户的问题作为查询,agent将结合用户输入</li><li>agent可以生成片段以便重新检索</li></ol><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> datasets</span><br><span class="line"><span class="keyword">from</span> tqdm <span class="keyword">import</span> tqdm</span><br><span class="line"><span class="keyword">from</span> transformers <span class="keyword">import</span> AutoTokenizer</span><br><span class="line"><span class="keyword">from</span> langchain.docstore.document <span class="keyword">import</span> Document</span><br><span class="line"><span class="keyword">from</span> langchain.text_splitter <span class="keyword">import</span> RecursiveCharacterTextSplitter</span><br><span class="line"><span class="keyword">from</span> langchain.vectorstores <span class="keyword">import</span> FAISS</span><br><span class="line"><span class="keyword">from</span> langchain_community.embeddings <span class="keyword">import</span> HuggingFaceEmbeddings</span><br><span class="line"><span class="keyword">from</span> langchain_community.vectorstores.utils <span class="keyword">import</span> DistanceStrategy</span><br><span class="line"></span><br><span class="line">knowledge_base = datasets.load_dataset(<span class="string">"m-ric/huggingface_doc"</span>, split=<span class="string">"train"</span>)</span><br><span class="line">source_docs = [</span><br><span class="line"> Document(page_content=doc[<span class="string">"text"</span>], metadata={<span class="string">"source"</span>: doc[<span class="string">"source"</span>].split(<span class="string">"/"</span>)[<span class="number">1</span>]}) <span class="keyword">for</span> doc <span class="keyword">in</span> knowledge_base</span><br><span class="line">]</span><br><span class="line"></span><br><span class="line">text_splitter = RecursiveCharacterTextSplitter.from_huggingface_tokenizer(</span><br><span class="line"> AutoTokenizer.from_pretrained(<span class="string">"thenlper/gte-small"</span>),</span><br><span class="line"> chunk_size=<span class="number">200</span>,</span><br><span class="line"> chunk_overlap=<span class="number">20</span>,</span><br><span class="line"> add_start_index=<span class="literal">True</span>,</span><br><span class="line"> strip_whitespace=<span class="literal">True</span>,</span><br><span class="line"> separators=[<span class="string">"\n\n"</span>, <span class="string">"\n"</span>, <span class="string">"."</span>, <span class="string">" "</span>, <span class="string">""</span>],</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="comment"># Split docs and keep only unique ones</span></span><br><span class="line"><span class="built_in">print</span>(<span class="string">"Splitting documents..."</span>)</span><br><span class="line">docs_processed = []</span><br><span class="line">unique_texts = {}</span><br><span class="line"><span class="keyword">for</span> doc <span class="keyword">in</span> tqdm(source_docs):</span><br><span class="line"> new_docs = text_splitter.split_documents([doc])</span><br><span class="line"> <span class="keyword">for</span> new_doc <span class="keyword">in</span> new_docs:</span><br><span class="line"> <span class="keyword">if</span> new_doc.page_content <span class="keyword">not</span> <span class="keyword">in</span> unique_texts:</span><br><span class="line"> unique_texts[new_doc.page_content] = <span class="literal">True</span></span><br><span class="line"> docs_processed.append(new_doc)</span><br><span class="line"></span><br><span class="line"><span class="built_in">print</span>(<span class="string">"Embedding documents... This should take a few minutes (5 minutes on MacBook with M1 Pro)"</span>)</span><br><span class="line">embedding_model = HuggingFaceEmbeddings(model_name=<span class="string">"thenlper/gte-small"</span>)</span><br><span class="line">vectordb = FAISS.from_documents(</span><br><span class="line"> documents=docs_processed,</span><br><span class="line"> embedding=embedding_model,</span><br><span class="line"> distance_strategy=DistanceStrategy.COSINE,</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"></span><br></pre></td></tr></table></figure><p><img data-src="https://proanimer-img.oss-cn-shanghai.aliyuncs.com/alimg/image-20241228204813028.png" alt="image-20241228204813028"></p><h2 id="Use-local-data"><a href="#Use-local-data" class="headerlink" title="Use local data"></a>Use local data</h2><p><a href="https://huggingface.co/learn/cookbook/rag_with_unstructured_data">Building RAG with Custom Unstructured Data</a></p><p>许多重要的知识都以各种格式存储,如pdf、电子邮件、Markdown文件、PowerPoint演示文稿、HTML页面、Word文档等。</p><p>如何预处理所有这些数据,因为这些文件的格式各不相同,要么搭配langchain等工具分别进行解析,要么直接使用现有工具库<a href="https://python.langchain.com/docs/introduction/">Introduction | 🦜️🔗 LangChain</a>,<a href="https://docs.unstructured.io/welcome">Unstructured - Unstructured</a></p><h3 id="本地推理应用与库"><a href="#本地推理应用与库" class="headerlink" title="本地推理应用与库"></a>本地推理应用与库</h3><p>目前有许多大模型本地推理应用,比如Ollama,LMStudio,GPT4All等等,而它们背后的推理库也有很多,比如<a href="https://github.com/ggerganov/llama.cpp">llama.cpp</a>,<a href="https://huggingface.co/blog/introduction-to-ggml">ggml</a>. 通过利用Ollama等工具搭配LangChain可以更高效及其定制化属于自己的LLM应用.</p><p>Ollama本身提供了API服务,通过这个服务与用户本地文件和prompt,可以搭建一个小应用</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">curl http://localhost:11434/api/chat -d '{</span><br><span class="line"> "model": "llama3.2",</span><br><span class="line"> "messages": [</span><br><span class="line"> { "role": "user", "content": "why is the sky blue?" }</span><br><span class="line"> ]</span><br><span class="line">}'</span><br></pre></td></tr></table></figure><p>注意这个返回值会是一个JSON流.具体来说, 通过<a href="https://python.langchain.com/docs/integrations/chat/ollama/">ChatOllama | 🦜️🔗 LangChain</a>获得更好的处理,再利用FastAPI或Sanic搭建web用户界面,结合上传用户文件等搭建一个应用.</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> langchain_ollama <span class="keyword">import</span> OllamaLLM</span><br><span class="line"></span><br><span class="line">model = OllamaLLM(model=<span class="string">"llama3"</span>)</span><br><span class="line">model.invoke(<span class="string">"Come up with 10 names for a song about parrots"</span>)</span><br></pre></td></tr></table></figure><p><img data-src="https://proanimer-img.oss-cn-shanghai.aliyuncs.com/alimg/image-20241231183826980.png" alt="image-20241231183826980"></p><link rel="stylesheet" href="/css/spoiler.css" type="text/css"><script src="/js/spoiler.js" type="text/javascript" async></script>]]></content>
<summary type="html"><p>既text2img和大语言模型之后的热点,AI Agent和RAG,简单来说在既有的多模态大模型基础上打造应用,比如利用多个智能体以及外部数据进行搜索. 目前来看还是有一些发展潜力,这方面的资料多见于Hugging Face,LangChain,llama index等等<br></summary>
<category term="AI Agent" scheme="https://www.sekyoro.top/tags/AI-Agent/"/>
<category term="RAG" scheme="https://www.sekyoro.top/tags/RAG/"/>
</entry>
<entry>
<title>OpenGL中不可忽视的部分:glsl、glm、assimp以及更多</title>
<link href="https://www.sekyoro.top/2024/12/25/glsl%E5%AD%A6%E4%B9%A0/"/>
<id>https://www.sekyoro.top/2024/12/25/glsl%E5%AD%A6%E4%B9%A0/</id>
<published>2024-12-25T14:15:40.000Z</published>
<updated>2025-01-06T14:17:57.605Z</updated>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="\assets\js\Meting.min.js"></script><p>在OpenGL中着色器的编程语言叫做GLSL,类似C语言但是内置了许多有用的函数. 这里简单学习其基础语法和包含特殊函数.<br><span id="more"></span></p><h2 id="GLSL"><a href="#GLSL" class="headerlink" title="GLSL"></a>GLSL</h2><p>着色器编程语言,在GPU下编程</p><h3 id="开发环境搭建"><a href="#开发环境搭建" class="headerlink" title="开发环境搭建"></a>开发环境搭建</h3><p>这里使用vscode进行开发,由于着色器语言不像C++,Java这种通用语言,它的语法检查和Lint没有这么丰富. 在vscode主要下载两个插件,一个是语法高亮<a href="https://marketplace.visualstudio.com/items?itemName=slevesque.shader">Shader languages support for VS Code - Visual Studio Marketplace</a>,另一个是Lint<a href="https://github.com/hsimpson/vscode-glsllint?tab=readme-ov-file">hsimpson/vscode-glsllint: VSCode extension to lint GLSL shading language files</a>.</p><p><img data-src="https://s2.loli.net/2024/12/26/RenYMiV8qSGBm4y.png" alt="image-20241226175503864"></p><p>此外Lint的插件本身不提供Linter,需要自己下载<a href="https://github.com/KhronosGroup/glslang">KhronosGroup/glslang: Khronos-reference front end for GLSL/ESSL, partial front end for HLSL, and a SPIR-V generator.</a></p><p>语法提示规则与文件名后缀相关</p><p>The applied stage-specific rules are based on the file extension:</p><ul><li><code>.vert</code> for a vertex shader</li><li><code>.tesc</code> for a tessellation control shader</li><li><code>.tese</code> for a tessellation evaluation shader</li><li><code>.geom</code> for a geometry shader</li><li><code>.frag</code> for a fragment shader</li><li><code>.comp</code> for a compute shader</li></ul><p>For ray tracing pipeline shaders:</p><ul><li><code>.rgen</code> for a ray generation shader</li><li><code>.rint</code> for a ray intersection shader</li><li><code>.rahit</code> for a ray any-hit shader</li><li><code>.rchit</code> for a ray closest-hit shader</li><li><code>.rmiss</code> for a ray miss shader</li><li><code>.rcall</code> for a callable shader</li></ul><p>此外还可以配置代码片段<a href="https://gist.github.com/lewislepton/8b17f56baa7f1790a70284e7520f9623">GLSL snippets for visual studio code/kode studio</a>,输入缩写即可提示.</p><h3 id="数据类型"><a href="#数据类型" class="headerlink" title="数据类型"></a>数据类型</h3><p>float,int,bool,vec,mat, struct,[]</p><h3 id="重要辅助函数"><a href="#重要辅助函数" class="headerlink" title="重要辅助函数"></a>重要辅助函数</h3><h4 id="向量和矩阵运算"><a href="#向量和矩阵运算" class="headerlink" title="向量和矩阵运算"></a>向量和矩阵运算</h4><p>dot,cross,normalize,transpose,inverse</p><h4 id="纹理采样"><a href="#纹理采样" class="headerlink" title="纹理采样"></a>纹理采样</h4><p>texture,mix</p><h4 id="光照计算"><a href="#光照计算" class="headerlink" title="光照计算"></a>光照计算</h4><p>clamp,mix,reflect</p><h3 id="高级GLSL"><a href="#高级GLSL" class="headerlink" title="高级GLSL"></a>高级GLSL</h3><p>glsl定义了几个以<code>gl_</code>为前缀的变量,它们能提供给我们更多的方式来读取/写入数据。比如顶点着色器的输出向量gl_Position和片段着色器的gl_FragCoord。</p><h4 id="顶点着色器"><a href="#顶点着色器" class="headerlink" title="顶点着色器"></a>顶点着色器</h4><p><strong>gl_PointSize</strong></p><p>通过gl_PointSize设置GL_POINT图元的大小,在顶点着色器中修改点大小的功能默认是禁用的,如果你需要启用它的话,需要启用OpenGL的GL_PROGRAM_POINT_SIZE:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">glEnable</span>(GL_PROGRAM_POINT_SIZE);</span><br></pre></td></tr></table></figure><p><strong>gl_VertexID</strong></p><p>gl_Position和gl_PointSize都是<strong>输出变量</strong>,因为它们的值是作为顶点着色器的输出被读取的。可以对它们进行写入,来改变结果。</p><p>顶点着色器还提供了一个有趣的<strong>输入变量</strong>,只能对它进行读取,它叫做gl_VertexID。</p><p>整型变量gl_VertexID储存了正在绘制顶点的当前ID。<strong>当使用glDrawElements进行索引渲染的时候,这个变量会存储正在绘制顶点的当前索引</strong>。当使用glDrawArrays不使用索引进行绘制的时候,这个变量会储存从渲染调用开始的已处理顶点数量。</p><h4 id="片段着色器"><a href="#片段着色器" class="headerlink" title="片段着色器"></a>片段着色器</h4><p>GLSL提供给两个输入变量:gl_FragCoord和gl_FrontFacing。</p><p><strong>gl_FragCoord</strong></p><p>gl_FragCoord的x和y分量是片段的窗口空间(Window-space)坐标,其原点为窗口的左下角。 可以利用它的z值(也就是深度)和xy空间坐标(屏幕空间,归一到[0,1])</p><figure class="highlight glsl"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">void</span> main()</span><br><span class="line">{ </span><br><span class="line"> <span class="keyword">if</span>(<span class="built_in">gl_FragCoord</span>.x < <span class="number">400</span>)</span><br><span class="line"> FragColor = <span class="type">vec4</span>(<span class="number">1.0</span>, <span class="number">0.0</span>, <span class="number">0.0</span>, <span class="number">1.0</span>);</span><br><span class="line"> <span class="keyword">else</span></span><br><span class="line"> FragColor = <span class="type">vec4</span>(<span class="number">0.0</span>, <span class="number">1.0</span>, <span class="number">0.0</span>, <span class="number">1.0</span>); </span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><strong>gl_FrontFacing</strong></p><p>提到OpenGL根据顶点的环绕顺序来决定一个面是正向还是背向面。如果我们不(启用GL_FACE_CULL来)使用面剔除,那么<strong>gl_FrontFacing将会告诉我们当前片段是属于正向面的一部分还是背向面的一部分</strong>。</p><p>gl_FrontFacing变量是一个bool,如果当前片段是正向面的一部分那么就是<code>true</code>,否则就是<code>false</code></p><p>如果开启了面剔除,就看不到箱子内部的面了,再使用gl_FrontFacing就没有意义了。</p><p><strong>gl_FragDepth</strong></p><p>输入变量gl_FragCoord的深度值是一个只读(Read-only)变量,不能修改片段的窗口空间坐标,但实际上<strong>修改片段的深度值还是可能的</strong>。<strong>GLSL提供一个叫做gl_FragDepth的输出变量,可以使用它来在着色器内设置片段的深度值。</strong></p><p>要想设置深度值,直接写入一个0.0到1.0之间的float值到输出变量就可以了:</p><figure class="highlight glsl"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">gl_FragDepth</span> = <span class="number">0.0</span>; <span class="comment">// 这个片段现在的深度值为 0.0</span></span><br></pre></td></tr></table></figure><p>如果着色器没有写入值到gl_FragDepth,它会自动取用<code>gl_FragCoord.z</code>的值。但是只要我们在片段着色器中对gl_FragDepth进行写入,OpenGL就会<strong>禁用所有的提前深度测试</strong>(Early Depth Testing)。它被禁用的原因是,OpenGL无法在片段着色器运行<strong>之前</strong>得知片段将拥有的深度值,因为片段着色器可能会完全修改这个深度值。</p><p>从4.2起,在片段着色器的顶部使用深度条件(Depth Condition)重新声明gl_FragDepth变量:</p><figure class="highlight glsl"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">layout</span> (depth_<condition>) <span class="keyword">out</span> <span class="type">float</span> <span class="built_in">gl_FragDepth</span>;</span><br></pre></td></tr></table></figure><p><code>condition</code>可以为下面的值:</p><div class="table-container"><table><thead><tr><th style="text-align:left">条件</th><th style="text-align:left">描述</th></tr></thead><tbody><tr><td style="text-align:left"><code>any</code></td><td style="text-align:left">默认值。提前深度测试是禁用的,你会损失很多性能</td></tr><tr><td style="text-align:left"><code>greater</code></td><td style="text-align:left">你只能让深度值比<code>gl_FragCoord.z</code>更大</td></tr><tr><td style="text-align:left"><code>less</code></td><td style="text-align:left">你只能让深度值比<code>gl_FragCoord.z</code>更小</td></tr><tr><td style="text-align:left"><code>unchanged</code></td><td style="text-align:left">如果你要写入<code>gl_FragDepth</code>,你将只能写入<code>gl_FragCoord.z</code>的值</td></tr></tbody></table></div><figure class="highlight glsl"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#version 420 core</span></span><br><span class="line"><span class="keyword">layout</span>(<span class="keyword">depth_less</span>) <span class="keyword">out</span> <span class="type">float</span> <span class="built_in">gl_FragDepth</span>;</span><br><span class="line">voif main{</span><br><span class="line"> <span class="built_in">gl_FragDepth</span> = <span class="built_in">gl_FragCoord</span>.z+<span class="number">1</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="接口块"><a href="#接口块" class="headerlink" title="接口块"></a>接口块</h4><p>为了方便在着色器之间传递数据,可以定义in out块,类似结构体</p><figure class="highlight glsl"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#version 330 core</span></span><br><span class="line"><span class="keyword">layout</span> (<span class="keyword">location</span> = <span class="number">0</span>) <span class="keyword">in</span> <span class="type">vec3</span> aPos;</span><br><span class="line"><span class="keyword">layout</span> (<span class="keyword">location</span> = <span class="number">1</span>) <span class="keyword">in</span> <span class="type">vec2</span> aTexCoords;</span><br><span class="line"></span><br><span class="line"><span class="keyword">uniform</span> <span class="type">mat4</span> model;</span><br><span class="line"><span class="keyword">uniform</span> <span class="type">mat4</span> view;</span><br><span class="line"><span class="keyword">uniform</span> <span class="type">mat4</span> projection;</span><br><span class="line"></span><br><span class="line"><span class="keyword">out</span> VS_OUT</span><br><span class="line">{</span><br><span class="line"> <span class="type">vec2</span> TexCoords;</span><br><span class="line">} vs_out;</span><br><span class="line"></span><br><span class="line"><span class="type">void</span> main()</span><br><span class="line">{</span><br><span class="line"> <span class="built_in">gl_Position</span> = projection * view * model * <span class="type">vec4</span>(aPos, <span class="number">1.0</span>); </span><br><span class="line"> vs_out.TexCoords = aTexCoords;</span><br><span class="line">}</span><br><span class="line"></span><br></pre></td></tr></table></figure><figure class="highlight glsl"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#version 330 core</span></span><br><span class="line"><span class="keyword">out</span> <span class="type">vec4</span> FragColor;</span><br><span class="line"></span><br><span class="line"><span class="keyword">in</span> VS_OUT</span><br><span class="line">{</span><br><span class="line"> <span class="type">vec2</span> TexCoords;</span><br><span class="line">} fs_in;</span><br><span class="line"></span><br><span class="line"><span class="keyword">uniform</span> <span class="type">sampler2D</span> <span class="built_in">texture</span>;</span><br><span class="line"></span><br><span class="line"><span class="type">void</span> main()</span><br><span class="line">{ </span><br><span class="line"> FragColor = <span class="built_in">texture</span>(<span class="built_in">texture</span>, fs_in.TexCoords); </span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>只要两个接口块的名字一样,它们对应的输入和输出将会匹配起来.它在几何着色器这样穿插特定着色器阶段的场景下会很有用。</p><h4 id="Uniform缓冲对象"><a href="#Uniform缓冲对象" class="headerlink" title="Uniform缓冲对象"></a>Uniform缓冲对象</h4><p>假设多个着色器都包含一个uniform变量,它们的值相同,为了不重复地设置,可以使用Uniform缓冲对象.<strong>允许定义一系列在多个着色器程序中相同的全局Uniform变量</strong>。当<strong>使用Uniform缓冲对象的时候,只需要设置相关的uniform一次</strong>。当然,我们仍需要手动设置每个着色器中不同的uniform。并且创建和配置Uniform缓冲对象会有一点繁琐。</p><figure class="highlight glsl"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#version 330 core</span></span><br><span class="line"><span class="keyword">layout</span> (<span class="keyword">location</span> = <span class="number">0</span>) <span class="keyword">in</span> <span class="type">vec3</span> aPos;</span><br><span class="line"></span><br><span class="line"><span class="keyword">layout</span> (<span class="keyword">std140</span>) <span class="keyword">uniform</span> Matrices</span><br><span class="line"> <span class="comment">// 定义的Uniform块对它的内容使用一个特定的内存布局。这个语句设置了Uniform块布局(Uniform Block Layout)。</span></span><br><span class="line">{</span><br><span class="line"> <span class="type">mat4</span> projection;</span><br><span class="line"> <span class="type">mat4</span> view;</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="keyword">uniform</span> <span class="type">mat4</span> model;</span><br><span class="line"></span><br><span class="line"><span class="type">void</span> main()</span><br><span class="line">{</span><br><span class="line"> <span class="built_in">gl_Position</span> = projection * view * model * <span class="type">vec4</span>(aPos, <span class="number">1.0</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这个Uniform块储存了两个4x4矩阵。<strong>Uniform块中的变量可以直接访问,不需要加块名作为前缀。</strong> 每个声明了这个Uniform块的着色器都能够访问这些矩阵。</p><h4 id="Uniform块布局"><a href="#Uniform块布局" class="headerlink" title="Uniform块布局"></a>Uniform块布局</h4><p>Uniform块的内容是储存在一个缓冲对象中的,它实际上只是一块预留内存。因为这块内存并不会保存它具体保存的是什么类型的数据,我们还需要告诉OpenGL内存的哪一部分对应着着色器中的哪一个uniform变量。</p><p>假设着色器中有以下的这个Uniform块:</p><figure class="highlight glsl"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">layout</span> (<span class="keyword">std140</span>) <span class="keyword">uniform</span> ExampleBlock</span><br><span class="line">{</span><br><span class="line"> <span class="type">float</span> value;</span><br><span class="line"> <span class="type">vec3</span> vector;</span><br><span class="line"> <span class="type">mat4</span> matrix;</span><br><span class="line"> <span class="type">float</span> values[<span class="number">3</span>];</span><br><span class="line"> <span class="type">bool</span> boolean;</span><br><span class="line"> <span class="type">int</span> integer;</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>我们需要知道的是每个变量的大小(字节)和(从块起始位置的)偏移量,来让我们能够按顺序将它们放进缓冲中。<strong>每个元素的大小都是在OpenGL中有清楚地声明的,而且直接对应C++数据类型</strong>,其中向量和矩阵都是大的float数组。OpenGL没有声明的是这些变量间的间距(Spacing)。这允许硬件能够在它认为合适的位置放置变量。比如说,一些硬件可能会将一个vec3放置在float边上。不是所有的硬件都能这样处理,可能会在附加这个float之前,先将vec3填充(Pad)为一个4个float的数组。这个特性本身很棒,但是会造成麻烦。</p><p><strong>默认情况下,GLSL会使用一个叫做共享(Shared)布局的Uniform内存布局,共享是因为一旦硬件定义了偏移量,它们在多个程序中是共享并一致的</strong>。使用共享布局时,GLSL是<strong>可以为了优化而对uniform变量的位置进行变动的,只要变量的顺序保持不变</strong>。<strong>因为无法知道每个uniform变量的偏移量,我们也就不知道如何准确地填充Uniform缓冲</strong>了。我们能够使用像是<strong>glGetUniformIndices这样的函数来查询这个信息</strong></p><p>虽然共享布局给了我们很多节省空间的优化,但是我们需要查询每个uniform变量的偏移量,这会产生非常多的工作量。通常的做法是,不使用共享布局,而是使用std140布局。std140布局声明了每个变量的偏移量都是由一系列规则所决定的,这<strong>显式地</strong>声明了每个变量类型的内存布局。由于这是显式提及的,我们可以手动计算出每个变量的偏移量。</p><p>每个变量都有一个基准对齐量(Base Alignment),它等于一个变量在Uniform块中所占据的空间(包括填充量(Padding)),这个基准对齐量是使用std140布局的规则计算出来的。接下来,对每个变量,我们再计算它的对齐偏移量(Aligned Offset),它是一个变量从块起始位置的字节偏移量。<strong>一个变量的对齐字节偏移量必须等于基准对齐量的倍数。</strong></p><p>GLSL中的每个变量,比如说int、float和bool,都被定义为4字节量。每4个字节将会用一个<code>N</code>来表示。</p><div class="table-container"><table><thead><tr><th style="text-align:left">类型</th><th style="text-align:left">布局规则</th></tr></thead><tbody><tr><td style="text-align:left">标量,比如int和bool</td><td style="text-align:left">每个标量的基准对齐量为N。</td></tr><tr><td style="text-align:left">向量</td><td style="text-align:left">2N或者4N。这意味着vec3的基准对齐量为4N。</td></tr><tr><td style="text-align:left">标量或向量的数组</td><td style="text-align:left">每个元素的基准对齐量与vec4的相同。</td></tr><tr><td style="text-align:left">矩阵</td><td style="text-align:left">储存为列向量的数组,每个向量的基准对齐量与vec4的相同。</td></tr><tr><td style="text-align:left">结构体</td><td style="text-align:left">等于所有元素根据规则计算后的大小,但会填充到vec4大小的倍数。</td></tr></tbody></table></div><p>使用计算后的偏移量值,根据std140布局的规则,我们就能使用像是glBufferSubData的函数将变量数据按照偏移量填充进缓冲中了。<strong>虽然std140布局不是最高效的布局,但它保证了内存布局在每个声明了这个Uniform块的程序中是一致的。</strong></p><p><strong>通过在Uniform块定义之前添加<code>layout (std140)</code>语句,告诉OpenGL这个Uniform块使用的是std140布局。</strong>除此之外<strong>还可以选择两个布局,但它们都需要我们在填充缓冲之前先查询每个偏移量</strong>。</p><p> 我们已经见过<code>shared</code>布局了,剩下的一个布局是<code>packed</code>。<strong>当使用紧凑(Packed)布局时,是不能保证这个布局在每个程序中保持不变的(即非共享),因为它允许编译器去将uniform变量从Uniform块中优化掉,这在每个着色器中都可能是不同的。</strong></p><h4 id="使用Uniform缓冲"><a href="#使用Uniform缓冲" class="headerlink" title="使用Uniform缓冲"></a>使用Uniform缓冲</h4><p>我们已经讨论了如何在着色器中定义Uniform块,并设定它们的内存布局了,但我们还没有讨论该如何使用它们。</p><p>首先,我们需要调用glGenBuffers,创建一个Uniform缓冲对象。一旦我们有了一个缓冲对象,我们需要将它绑定到GL_UNIFORM_BUFFER目标,并调用glBufferData,分配足够的内存。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">unsigned</span> <span class="keyword">int</span> uboExampleBlock;</span><br><span class="line"><span class="built_in">glGenBuffers</span>(<span class="number">1</span>, &uboExampleBlock); <span class="comment">//创建uniform buffer对象</span></span><br><span class="line"><span class="built_in">glBindBuffer</span>(GL_UNIFORM_BUFFER, uboExampleBlock);</span><br><span class="line"><span class="built_in">glBufferData</span>(GL_UNIFORM_BUFFER, <span class="number">152</span>, <span class="literal">NULL</span>, GL_STATIC_DRAW); <span class="comment">// 分配152字节的内存</span></span><br><span class="line"><span class="built_in">glBindBuffer</span>(GL_UNIFORM_BUFFER, <span class="number">0</span>);</span><br></pre></td></tr></table></figure><p>每当我们需要对缓冲更新或者插入数据,我们都会绑定到uboExampleBlock,并使用glBufferSubData来更新它的内存。我们只需要更新这个Uniform缓冲一次,所有使用这个缓冲的着色器就都使用的是更新后的数据了</p><p>在OpenGL上下文中,定义了一些绑定点(Binding Point),我们可以将一个Uniform缓冲链接至它。在创建Uniform缓冲之后,我们将它绑定到其中一个绑定点上,并将着色器中的Uniform块绑定到相同的绑定点,把它们连接到一起。下面的这个图示展示了这个:</p><p><img data-src="https://learnopengl-cn.github.io/img/04/08/advanced_glsl_binding_points.png" alt="img"></p><p>你可以看到,我们可以绑定多个Uniform缓冲到不同的绑定点上。因为着色器A和着色器B都有一个链接到绑定点0的Uniform块,它们的Uniform块将会共享相同的uniform数据,uboMatrices,前提条件是两个着色器都定义了相同的Matrices Uniform块。</p><p>为了将Uniform块绑定到一个特定的绑定点中,我们需要调用glUniformBlockBinding函数,它的第一个参数是一个程序对象,之后是一个Uniform块索引和链接到的绑定点。Uniform块索引(Uniform Block Index)是着色器中已定义Uniform块的位置值索引。这可以通过调用glGetUniformBlockIndex来获取,它接受一个程序对象和Uniform块的名称</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">unsigned</span> <span class="keyword">int</span> lights_index = <span class="built_in">glGetUniformBlockIndex</span>(shaderA.ID, <span class="string">"Lights"</span>); </span><br><span class="line"><span class="built_in">glUniformBlockBinding</span>(shaderA.ID, lights_index, <span class="number">2</span>);</span><br></pre></td></tr></table></figure><p>从OpenGL 4.2版本起,也可以添加一个布局标识符,显式地将Uniform块的绑定点储存在着色器中,这样就不用再调用glGetUniformBlockIndex和glUniformBlockBinding了。下面的代码显式地设置了Lights Uniform块的绑定点。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">layout</span>(std140, binding = <span class="number">2</span>) uniform Lights { ... };</span><br></pre></td></tr></table></figure><p>接下来,我们还需要绑定Uniform缓冲对象到相同的绑定点上,这可以使用glBindBufferBase或glBindBufferRange来完成。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">glBindBufferBase</span>(GL_UNIFORM_BUFFER, <span class="number">2</span>, uboExampleBlock); </span><br><span class="line"><span class="comment">// 或</span></span><br><span class="line"><span class="built_in">glBindBufferRange</span>(GL_UNIFORM_BUFFER, <span class="number">2</span>, uboExampleBlock, <span class="number">0</span>, <span class="number">152</span>);</span><br></pre></td></tr></table></figure><p>glBindbufferBase需要一个目标,一个绑定点索引和一个Uniform缓冲对象作为它的参数。这个函数将uboExampleBlock链接到绑定点2上,自此,绑定点的两端都链接上了。你也可以使用glBindBufferRange函数,它需要一个附加的偏移量和大小参数,这样子你可以绑定Uniform缓冲的特定一部分到绑定点中。通过使用glBindBufferRange函数,你可以让多个不同的Uniform块绑定到同一个Uniform缓冲对象上。</p><p>现在,所有的东西都配置完毕了,我们可以开始向Uniform缓冲中添加数据了。只要我们需要,就可以使用glBufferSubData函数,用一个字节数组添加所有的数据,或者更新缓冲的一部分。要想更新uniform变量boolean,我们可以用以下方式更新Uniform缓冲对象:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">glBindBuffer</span>(GL_UNIFORM_BUFFER, uboExampleBlock);</span><br><span class="line"><span class="keyword">int</span> b = <span class="literal">true</span>; <span class="comment">// GLSL中的bool是4字节的,所以我们将它存为一个integer</span></span><br><span class="line"><span class="built_in">glBufferSubData</span>(GL_UNIFORM_BUFFER, <span class="number">144</span>, <span class="number">4</span>, &b); </span><br><span class="line"><span class="built_in">glBindBuffer</span>(GL_UNIFORM_BUFFER, <span class="number">0</span>);</span><br></pre></td></tr></table></figure><p>同样的步骤也能应用到Uniform块中其它的uniform变量上,但需要使用不同的范围参数。</p><p>Uniform缓冲对象比起独立的uniform有很多好处。第一,<strong>一次设置很多uniform会比一个一个设置多个uniform要快很多</strong>。第二,<strong>比起在多个着色器中修改同样的uniform,在Uniform缓冲中修改一次会更容易一些</strong>。最后一个好处可能不会立即显现,<strong>如果使用Uniform缓冲对象的话,你可以在着色器中使用更多的uniform</strong>。OpenGL限制了它能够处理的uniform数量,这可以通过GL_MAX_VERTEX_UNIFORM_COMPONENTS来查询。当<strong>使用Uniform缓冲对象时,最大的数量会更高。</strong>所以,当你达到了uniform的最大数量时(比如再做骨骼动画(Skeletal Animation)的时候),总是可以选择使用Uniform缓冲对象。</p><h2 id="GLM"><a href="#GLM" class="headerlink" title="GLM"></a>GLM</h2><p>与opengl向适应的向量计算库</p><ol><li><strong>向量类</strong>:<ul><li><code>glm::vec2</code>: 2D 向量。</li><li><code>glm::vec3</code>: 3D 向量。</li><li><code>glm::vec4</code>: 4D 向量(通常用于颜色或齐次坐标)。</li></ul></li><li><strong>矩阵类</strong>:<ul><li><code>glm::mat2</code>: 2x2 矩阵。</li><li><code>glm::mat3</code>: 3x3 矩阵。</li><li><code>glm::mat4</code>: 4x4 矩阵(常用于变换矩阵)。</li></ul></li><li><strong>四元数类</strong>:<ul><li><code>glm::quat</code>: 四元数,用于表示旋转。</li></ul></li></ol><h3 id="常用方法"><a href="#常用方法" class="headerlink" title="常用方法"></a>常用方法</h3><h4 id="向量操作"><a href="#向量操作" class="headerlink" title="向量操作"></a>向量操作</h4><ul><li><p><strong>创建向量</strong>:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">glm::vec3 <span class="title">v</span><span class="params">(<span class="number">1.0f</span>, <span class="number">2.0f</span>, <span class="number">3.0f</span>)</span></span>;</span><br></pre></td></tr></table></figure></li><li><p><strong>向量加法</strong>:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">glm::vec3 sum = v1 + v2;</span><br></pre></td></tr></table></figure></li><li><p><strong>向量减法</strong>:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">glm::vec3 difference = v1 - v2;</span><br></pre></td></tr></table></figure></li><li><p><strong>标量乘法</strong>:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">glm::vec3 scaled = v * scalar;</span><br></pre></td></tr></table></figure></li><li><p><strong>点积</strong> (<code>dot product</code>):</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">float</span> dotProduct = glm::<span class="built_in">dot</span>(v1, v2);</span><br></pre></td></tr></table></figure></li><li><p><strong>叉积</strong> (<code>cross product</code>) - 仅适用于3D向量:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">glm::vec3 crossProduct = glm::<span class="built_in">cross</span>(v1, v2);</span><br></pre></td></tr></table></figure></li><li><p><strong>长度/模</strong> (<code>length/magnitude</code>):</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">float</span> length = glm::<span class="built_in">length</span>(v);</span><br></pre></td></tr></table></figure></li><li><p><strong>标准化</strong> (<code>normalize</code>):</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">glm::vec3 normalizedV = glm::<span class="built_in">normalize</span>(v);</span><br></pre></td></tr></table></figure></li></ul><h4 id="矩阵操作"><a href="#矩阵操作" class="headerlink" title="矩阵操作"></a>矩阵操作</h4><ul><li><p><strong>创建单位矩阵</strong>:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">glm::mat4 identity = glm::<span class="built_in">mat4</span>(<span class="number">1.0f</span>);</span><br></pre></td></tr></table></figure></li><li><p><strong>平移矩阵</strong>:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">glm::mat4 translateMatrix = glm::<span class="built_in">translate</span>(glm::<span class="built_in">mat4</span>(<span class="number">1.0f</span>), glm::<span class="built_in">vec3</span>(x, y, z));</span><br></pre></td></tr></table></figure></li><li><p><strong>缩放矩阵</strong>:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">glm::mat4 scaleMatrix = glm::<span class="built_in">scale</span>(glm::<span class="built_in">mat4</span>(<span class="number">1.0f</span>), glm::<span class="built_in">vec3</span>(sx, sy, sz));</span><br></pre></td></tr></table></figure></li><li><p><strong>旋转矩阵</strong>:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">glm::mat4 rotateMatrix = glm::<span class="built_in">rotate</span>(glm::<span class="built_in">mat4</span>(<span class="number">1.0f</span>), angle, glm::<span class="built_in">vec3</span>(axisX, axisY, axisZ));</span><br></pre></td></tr></table></figure></li><li><p><strong>组合变换</strong>:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">glm::mat4 model = glm::<span class="built_in">translate</span>(glm::<span class="built_in">mat4</span>(<span class="number">1.0f</span>), translation) *</span><br><span class="line"> glm::<span class="built_in">rotate</span>(glm::<span class="built_in">mat4</span>(<span class="number">1.0f</span>), rotationAngle, rotationAxis) *</span><br><span class="line"> glm::<span class="built_in">scale</span>(glm::<span class="built_in">mat4</span>(<span class="number">1.0f</span>), scale);</span><br></pre></td></tr></table></figure></li><li><p><strong>视图矩阵</strong> (<code>lookAt</code>):</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">glm::mat4 view = glm::<span class="built_in">lookAt</span>(cameraPosition, cameraTarget, upVector);</span><br></pre></td></tr></table></figure></li><li><p><strong>投影矩阵</strong> (<code>perspective</code> 和 <code>orthographic</code>):</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">glm::mat4 projection = glm::<span class="built_in">perspective</span>(glm::<span class="built_in">radians</span>(fov), aspectRatio, nearPlane, farPlane);</span><br><span class="line"><span class="comment">// 或者正交投影</span></span><br><span class="line">glm::mat4 orthoProjection = glm::<span class="built_in">ortho</span>(left, right, bottom, top, nearPlane, farPlane);</span><br></pre></td></tr></table></figure></li></ul><h4 id="四元数操作"><a href="#四元数操作" class="headerlink" title="四元数操作"></a>四元数操作</h4><ul><li><p><strong>创建四元数</strong>:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">glm::quat q = glm::<span class="built_in">quat</span>(angle, glm::<span class="built_in">vec3</span>(axisX, axisY, axisZ));</span><br></pre></td></tr></table></figure></li><li><p><strong>从旋转矩阵转换到四元数</strong>:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">glm::quat qFromMat = glm::<span class="built_in">quat_cast</span>(rotationMatrix);</span><br></pre></td></tr></table></figure></li><li><p><strong>四元数插值</strong> (<code>slerp</code>):</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">glm::quat interpolatedQ = glm::<span class="built_in">slerp</span>(q1, q2, t);</span><br></pre></td></tr></table></figure></li><li><p><strong>四元数转欧拉角</strong>:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">glm::vec3 eulerAngles = glm::<span class="built_in">eulerAngles</span>(q);</span><br></pre></td></tr></table></figure></li><li><p><strong>四元数转旋转矩阵</strong>:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">glm::mat4 matFromQuat = glm::<span class="built_in">mat4_cast</span>(q);</span><br></pre></td></tr></table></figure></li></ul><h2 id="Assimp"><a href="#Assimp" class="headerlink" title="Assimp"></a>Assimp</h2><p>模型加载库</p><h3 id="读取文件"><a href="#读取文件" class="headerlink" title="读取文件"></a>读取文件</h3><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Assimp::Importer::<span class="built_in">ReadFile</span>()</span><br></pre></td></tr></table></figure><p> 该类将读取文件并处理其数据,将导入的数据作为指向一个对象的指针返回。现在可以从文件中提取所需的数据。</p><p> 导入器为自己管理所有资源。如果导入器被销毁,那么由它创建/读取的所有数据也将被销毁。因此,使用Importer最简单的方法是在本地创建一个实例,使用它的结果,然</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><assimp/Importer.hpp></span> <span class="comment">// C++ importer interface</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><assimp/scene.h></span> <span class="comment">// Output data structure</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><assimp/postprocess.h></span> <span class="comment">// Post processing flags</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">DoTheImportThing</span><span class="params">( <span class="keyword">const</span> std::string& pFile)</span> </span>{</span><br><span class="line"> <span class="comment">// Create an instance of the Importer class</span></span><br><span class="line"> Assimp::Importer importer;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// And have it read the given file with some example postprocessing</span></span><br><span class="line"> <span class="comment">// Usually - if speed is not the most important aspect for you - you'll</span></span><br><span class="line"> <span class="comment">// probably to request more postprocessing than we do in this example.</span></span><br><span class="line"> <span class="keyword">const</span> aiScene* scene = importer.<span class="built_in">ReadFile</span>( pFile,</span><br><span class="line"> aiProcess_CalcTangentSpace |</span><br><span class="line"> aiProcess_Triangulate |</span><br><span class="line"> aiProcess_JoinIdenticalVertices |</span><br><span class="line"> aiProcess_SortByPType);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// If the import failed, report it</span></span><br><span class="line"> <span class="keyword">if</span> (<span class="literal">nullptr</span> == scene) {</span><br><span class="line"> <span class="built_in">DoTheErrorLogging</span>( importer.<span class="built_in">GetErrorString</span>());</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// Now we can access the file's contents.</span></span><br><span class="line"> <span class="built_in">DoTheSceneProcessing</span>( scene);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// We're done. Everything will be cleaned up by the importer destructor</span></span><br><span class="line"> <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>后简单地让它离开作用域。</p><h3 id="数据结构"><a href="#数据结构" class="headerlink" title="数据结构"></a>数据结构</h3><p>以结构集合的形式返回导入的数据。aiScene形成数据的根,从这里可以访问从导入文件中<strong>读取的所有节点,网格,材质,动画或纹理</strong>。</p><p>默认情况下,所有3D数据都以右手坐标系提供,例如OpenGL使用的坐标系。在这个坐标系中,+X指向右侧,+Y指向上方,+Z指向屏幕外的观察者</p><p>输出面顺序为逆时针方向</p><h5 id="Scene"><a href="#Scene" class="headerlink" title="Scene"></a>Scene</h5><p>场景包含一个rootNode用于遍历以及mesh和material,通过node中的mesh索引获得具体mesh,通过mesh中的material索引获得具体material,mesh还包括faces,一个face就是一个primitive.</p><ol><li><strong><code>mFlags</code></strong>: 这个标志表示场景的一些特性,例如是否是不完整的 (<code>AI_SCENE_FLAGS_INCOMPLETE</code>) 或者是否有无效的数据 (<code>AI_SCENE_FLAGS_INVALID_DATA</code>)。</li><li><strong><code>mRootNode</code></strong>: 指向场景根节点的指针。每个场景都有一个根节点,所有其他节点都是它的子节点。通过遍历这个节点树,你可以获取场景的所有几何信息。</li><li><strong><code>mNumMeshes</code> 和 <code>mMeshes</code></strong>: 分别表示场景中网格的数量和指向网格数组的指针。网格包含了顶点、面和其他几何数据。</li><li><strong><code>mNumMaterials</code> 和 <code>mMaterials</code></strong>: 分别表示场景中材质的数量和指向材质数组的指针。材质定义了网格的外观属性。</li><li><strong><code>mNumTextures</code> 和 <code>mTextures</code></strong>: 分别表示场景中文本贴图的数量和指向文本贴图数组的指针。请注意,不是所有的模型格式都支持直接导出纹理,所以这个成员可能为空。</li><li><strong><code>mNumCameras</code> 和 <code>mCameras</code></strong>: 分别表示场景中相机的数量和指向相机数组的指针。并不是所有模型文件都会包含相机信息。</li><li><strong><code>mNumLights</code> 和 <code>mLights</code></strong>: 分别表示场景中光源的数量和指向光源数组的指针。同样地,并非所有模型文件都包含光源信息。</li><li><strong><code>mMetaData</code></strong>: 包含有关场景的元数据。这可以包括版本号、作者等信息</li></ol><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 假设我们已经有一个 aiScene* scene</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 访问根节点</span></span><br><span class="line">aiNode* rootNode = scene->mRootNode;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 遍历所有网格</span></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">unsigned</span> <span class="keyword">int</span> i = <span class="number">0</span>; i < scene->mNumMeshes; ++i) {</span><br><span class="line"> aiMesh* mesh = scene->mMeshes[i];</span><br><span class="line"> <span class="comment">// 处理网格...</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 遍历所有材质</span></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">unsigned</span> <span class="keyword">int</span> i = <span class="number">0</span>; i < scene->mNumMaterials; ++i) {</span><br><span class="line"> aiMaterial* material = scene->mMaterials[i];</span><br><span class="line"> <span class="comment">// 处理材质...</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 如果有纹理,遍历它们</span></span><br><span class="line"><span class="keyword">if</span> (scene->mNumTextures > <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">unsigned</span> <span class="keyword">int</span> i = <span class="number">0</span>; i < scene->mNumTextures; ++i) {</span><br><span class="line"> aiTexture* texture = scene->mTextures[i];</span><br><span class="line"> <span class="comment">// 处理纹理...</span></span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 如果有相机,遍历它们</span></span><br><span class="line"><span class="keyword">if</span> (scene->mNumCameras > <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">unsigned</span> <span class="keyword">int</span> i = <span class="number">0</span>; i < scene->mNumCameras; ++i) {</span><br><span class="line"> aiCamera* camera = scene->mCameras[i];</span><br><span class="line"> <span class="comment">// 处理相机...</span></span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 如果有光源,遍历它们</span></span><br><span class="line"><span class="keyword">if</span> (scene->mNumLights > <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">unsigned</span> <span class="keyword">int</span> i = <span class="number">0</span>; i < scene->mNumLights; ++i) {</span><br><span class="line"> aiLight* light = scene->mLights[i];</span><br><span class="line"> <span class="comment">// 处理光源...</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h5 id="Nodes"><a href="#Nodes" class="headerlink" title="Nodes"></a>Nodes</h5><p>节点是场景中名字不大的实体,相对于它们的父节点有一个位置和方向。从场景的根节点开始,所有节点可以有0到x个子节点,从而形成一个层次结构。</p><ol><li><strong><code>mTransformation</code></strong>:<ul><li>类型: <code>aiMatrix4x4</code></li><li>描述: 表示节点的本地变换矩阵,它定义了该节点相对于其父节点的位置、旋转和缩放。</li></ul></li><li><strong><code>mNumMeshes</code> 和 <code>mMeshes</code></strong>:<ul><li>类型: <code>unsigned int</code> 和 <code>unsigned int*</code></li><li>描述: <code>mNumMeshes</code> 表示此节点直接关联的网格数量;<code>mMeshes</code> 是一个索引数组,指向 <code>aiScene</code> 的 <code>mMeshes</code> 数组中的相应网格。如果 <code>mNumMeshes</code> 为 0,则该节点没有直接关联的网格。</li></ul></li><li><strong><code>mParent</code></strong>:<ul><li>类型: <code>aiNode*</code></li><li>描述: 指向该节点的父节点的指针。根节点的 <code>mParent</code> 为 <code>nullptr</code>。</li></ul></li><li><strong><code>mNumChildren</code> 和 <code>mChildren</code></strong>:<ul><li>类型: <code>unsigned int</code> 和 <code>aiNode**</code></li><li>描述: <code>mNumChildren</code> 表示该节点的子节点数量;<code>mChildren</code> 是一个指针数组,指向该节点的所有子节点。</li></ul></li><li><strong><code>mName</code></strong>:<ul><li>类型: <code>aiString</code></li><li>描述: 节点的名字。在某些情况下,这个名字可能被用来标识特定的节点或作为动画等的参考</li></ul></li></ol><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 假设我们有一个 aiNode* node</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取节点的变换矩阵</span></span><br><span class="line">aiMatrix4x4 transformation = node->mTransformation;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 遍历所有与该节点关联的网格</span></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">unsigned</span> <span class="keyword">int</span> i = <span class="number">0</span>; i < node->mNumMeshes; ++i) {</span><br><span class="line"> <span class="keyword">unsigned</span> <span class="keyword">int</span> meshIndex = node->mMeshes[i];</span><br><span class="line"> aiMesh* mesh = scene->mMeshes[meshIndex];</span><br><span class="line"> <span class="comment">// 处理网格...</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取父节点</span></span><br><span class="line"><span class="keyword">if</span> (node->mParent != <span class="literal">nullptr</span>) {</span><br><span class="line"> aiNode* parentNode = node->mParent;</span><br><span class="line"> <span class="comment">// 处理父节点...</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 遍历所有子节点</span></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">unsigned</span> <span class="keyword">int</span> i = <span class="number">0</span>; i < node->mNumChildren; ++i) {</span><br><span class="line"> aiNode* childNode = node->mChildren[i];</span><br><span class="line"> <span class="comment">// 处理子节点...</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取节点名称</span></span><br><span class="line"><span class="function">std::string <span class="title">nodeName</span><span class="params">(node->mName.C_Str())</span></span>;</span><br></pre></td></tr></table></figure><h5 id="Mesh"><a href="#Mesh" class="headerlink" title="Mesh"></a>Mesh</h5><ol><li><strong><code>mPrimitiveTypes</code></strong>:<ul><li>类型: <code>unsigned int</code></li><li>描述: 表示该网格中包含的图元类型,例如点 (<code>aiPrimitiveType_POINT</code>)、线 (<code>aiPrimitiveType_LINE</code>) 或三角形 (<code>aiPrimitiveType_TRIANGLE</code>)。</li></ul></li><li><strong><code>mNumVertices</code> 和 <code>mVertices</code></strong>:<ul><li>类型: <code>unsigned int</code> 和 <code>aiVector3D*</code></li><li>描述: <code>mNumVertices</code> 表示顶点的数量;<code>mVertices</code> 是指向 <code>aiVector3D</code> 数组的指针,每个元素代表一个顶点的位置。</li></ul></li><li><strong><code>mNormals</code></strong>:<ul><li>类型: <code>aiVector3D*</code></li><li>描述: 指向法线数组的指针。如果网格包含法线数据,则每个顶点都有一个对应的法线向量。</li></ul></li><li><strong><code>mTextureCoords</code></strong>:<ul><li>类型: <code>aiVector3D**</code></li><li>描述: 指向纹理坐标数组的二维数组指针。第一维是纹理坐标集的数量(最多8个),第二维是实际的纹理坐标。如果某个顶点有纹理坐标,则可以通过这个成员访问。</li></ul></li><li><strong><code>mColors</code></strong>:<ul><li>类型: <code>aiColor4D**</code></li><li>描述: 指向颜色数组的二维数组指针。第一维是颜色集的数量(最多8个),第二维是实际的颜色值。如果顶点有色值,则可以在这里找到。</li></ul></li><li><strong><code>mNumFaces</code> 和 <code>mFaces</code></strong>:<ul><li>类型: <code>unsigned int</code> 和 <code>aiFace*</code></li><li>描述: <code>mNumFaces</code> 表示面的数量;<code>mFaces</code> 是指向 <code>aiFace</code> 数组的指针,每个 <code>aiFace</code> 定义了一个由若干顶点组成的多边形(通常是三角形)。</li></ul></li><li><strong><code>mMaterialIndex</code></strong>:<ul><li>类型: <code>unsigned int</code></li><li>描述: 网格使用的材质在场景的 <code>mMaterials</code> 数组中的索引。通过这个索引,你可以获取与该网格关联的 <code>aiMaterial</code> 对象。</li></ul></li><li><strong><code>mNumBones</code> 和 <code>mBones</code></strong>:<ul><li>类型: <code>unsigned int</code> 和 <code>aiBone**</code></li><li>描述: 如果网格支持骨骼动画,则 <code>mBones</code> 包含了指向 <code>aiBone</code> 数组的指针,每个 <code>aiBone</code> 定义了一个影响顶点位置的骨骼。<code>mNumBones</code> 是骨骼的数量。</li></ul></li><li><strong><code>mName</code></strong>:<ul><li>类型: <code>aiString</code></li><li>描述: 网格的名字。某些情况下,这个名字可能是有意义的,比如用于标识特定的网格或作为其他资源的引用。</li></ul></li></ol><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 假设我们有一个 aiMesh* mesh 和 aiScene* scene</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取网格的材质</span></span><br><span class="line">aiMaterial* material = scene->mMaterials[mesh->mMaterialIndex];</span><br><span class="line"></span><br><span class="line"><span class="comment">// 遍历所有顶点</span></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">unsigned</span> <span class="keyword">int</span> i = <span class="number">0</span>; i < mesh->mNumVertices; ++i) {</span><br><span class="line"> aiVector3D position = mesh->mVertices[i];</span><br><span class="line"> <span class="comment">// 处理顶点位置...</span></span><br><span class="line"> </span><br><span class="line"> <span class="keyword">if</span> (mesh-><span class="built_in">HasNormals</span>()) {</span><br><span class="line"> aiVector3D normal = mesh->mNormals[i];</span><br><span class="line"> <span class="comment">// 处理法线...</span></span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (mesh-><span class="built_in">HasTextureCoords</span>(<span class="number">0</span>)) { <span class="comment">// 假设使用第0个纹理坐标集</span></span><br><span class="line"> aiVector3D texCoord = mesh->mTextureCoords[<span class="number">0</span>][i];</span><br><span class="line"> <span class="comment">// 处理纹理坐标...</span></span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 遍历所有面</span></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">unsigned</span> <span class="keyword">int</span> i = <span class="number">0</span>; i < mesh->mNumFaces; ++i) {</span><br><span class="line"> aiFace face = mesh->mFaces[i];</span><br><span class="line"> <span class="comment">// 处理面...</span></span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">unsigned</span> <span class="keyword">int</span> j = <span class="number">0</span>; j < face.mNumIndices; ++j) {</span><br><span class="line"> <span class="keyword">unsigned</span> <span class="keyword">int</span> vertexIndex = face.mIndices[j];</span><br><span class="line"> <span class="comment">// 使用顶点索引...</span></span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 如果有骨骼信息,遍历它们</span></span><br><span class="line"><span class="keyword">if</span> (mesh-><span class="built_in">HasBones</span>()) {</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">unsigned</span> <span class="keyword">int</span> i = <span class="number">0</span>; i < mesh->mNumBones; ++i) {</span><br><span class="line"> aiBone* bone = mesh->mBones[i];</span><br><span class="line"> <span class="comment">// 处理骨骼...</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><code>aiMesh</code> 还提供了一些辅助函数来检查网格是否包含特定的数据类型:</p><ul><li><strong><code>HasPositions()</code></strong>: 返回网格是否有顶点位置。</li><li><strong><code>HasNormals()</code></strong>: 返回网格是否有法线数据。</li><li><strong><code>HasTangentsAndBitangents()</code></strong>: 返回网格是否有切线和副切线数据。</li><li><strong><code>HasTextureCoords(unsigned int)</code></strong>: 接受一个参数指定纹理坐标集的索引,返回网格是否有对应的纹理坐标。</li><li><strong><code>HasVertexColors(unsigned int)</code></strong>: 接受一个参数指定颜色集的索引,返回网格是否有对应的顶点颜色。</li><li><strong><code>HasFaces()</code></strong>: 返回网格是否有面数据。</li><li><strong><code>HasBones()</code></strong>: 返回网格是否有骨骼信息。</li></ul><h5 id="Material"><a href="#Material" class="headerlink" title="Material"></a>Material</h5><h6 id="颜色属性"><a href="#颜色属性" class="headerlink" title="颜色属性"></a>颜色属性</h6><ul><li><strong><code>AI_MATKEY_COLOR_DIFFUSE</code></strong>: 扩散(漫反射)颜色。</li><li><strong><code>AI_MATKEY_COLOR_AMBIENT</code></strong>: 环境光颜色。</li><li><strong><code>AI_MATKEY_COLOR_SPECULAR</code></strong>: 高光(镜面反射)颜色。</li><li><strong><code>AI_MATKEY_COLOR_EMISSIVE</code></strong>: 发射光颜色。</li><li><strong><code>AI_MATKEY_COLOR_TRANSPARENT</code></strong>: 透明颜色。</li><li><strong><code>AI_MATKEY_COLOR_REFLECTIVE</code></strong>: 反射颜色。</li></ul><h6 id="浮点数属性"><a href="#浮点数属性" class="headerlink" title="浮点数属性"></a>浮点数属性</h6><ul><li><strong><code>AI_MATKEY_SHININESS</code></strong>: 高光强度。</li><li><strong><code>AI_MATKEY_SHININESS_STRENGTH</code></strong>: 高光强度因子。</li><li><strong><code>AI_MATKEY_REFRACTI</code></strong>: 折射率。</li></ul><h6 id="布尔属性"><a href="#布尔属性" class="headerlink" title="布尔属性"></a>布尔属性</h6><ul><li><strong><code>AI_MATKEY_ENABLE_WIREFRAME</code></strong>: 是否启用线框模式。</li></ul><h6 id="纹理属性"><a href="#纹理属性" class="headerlink" title="纹理属性"></a>纹理属性</h6><ul><li><strong><code>AI_MATKEY_TEXTURE_BASE</code></strong>: 基础纹理。</li><li><strong><code>AI_MATKEY_TEXTURE_DIFFUSE</code></strong>: 扩散(漫反射)纹理。</li><li><strong><code>AI_MATKEY_TEXTURE_SPECULAR</code></strong>: 高光(镜面反射)纹理。</li><li><strong><code>AI_MATKEY_TEXTURE_AMBIENT</code></strong>: 环境光纹理。</li><li><strong><code>AI_MATKEY_TEXTURE_EMISSIVE</code></strong>: 发射光纹理。</li><li><strong><code>AI_MATKEY_TEXTURE_HEIGHT</code></strong>: 高度图。</li><li><strong><code>AI_MATKEY_TEXTURE_NORMALS</code></strong>: 法线贴图。</li><li><strong><code>AI_MATKEY_TEXTURE_SHININESS</code></strong>: 高光贴图。</li><li><strong><code>AI_MATKEY_TEXTURE_OPACITY</code></strong>: 不透明度贴图。</li><li><strong><code>AI_MATKEY_TEXTURE_DISPLACEMENT</code></strong>: 位移贴图。</li><li><strong><code>AI_MATKEY_TEXTURE_LIGHTMAP</code></strong>: 光照贴图。</li><li><strong><code>AI_MATKEY_TEXTURE_REFLECTION</code></strong>: 反射贴图。</li></ul><h6 id="方法"><a href="#方法" class="headerlink" title="方法"></a>方法</h6><p>为了访问上述属性,<code>aiMaterial</code> 提供了一系列的 <code>Get</code> 和 <code>Set</code> 函数。最常用的 <code>Get</code> 函数包括:</p><ol><li><strong><code>Get(AI_MATKEY key, aiColor4D& out)</code></strong>:<ul><li>获取指定键的颜色值。</li></ul></li><li><strong><code>Get(AI_MATKEY key, float& out)</code></strong>:<ul><li>获取指定键的浮点数值。</li></ul></li><li><strong><code>Get(AI_MATKEY key, bool& out)</code></strong>:<ul><li>获取指定键的布尔值。</li></ul></li><li><strong><code>GetTexture(aiTextureType type, unsigned int index, aiString\* path)</code></strong>:<ul><li>获取指定类型的纹理路径。<code>index</code> 参数允许你访问同一类型下的多个纹理(例如,多层扩散纹理)。</li></ul></li><li><strong><code>HasProperty(const char\* key)</code></strong>:<ul><li>检查材质是否具有给定键的属性。</li></ul></li><li><strong><code>Get(AI_MATKEY key, unsigned int& out)</code></strong>:<ul><li>获取指定键的无符号整数值(例如,用于获取纹理的数量)</li></ul></li></ol><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 假设我们有一个 aiMaterial* material</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取扩散颜色</span></span><br><span class="line">aiColor4D diffuseColor;</span><br><span class="line"><span class="keyword">if</span> (AI_SUCCESS == material-><span class="built_in">Get</span>(AI_MATKEY_COLOR_DIFFUSE, diffuseColor)) {</span><br><span class="line"> <span class="comment">// 使用 diffuseColor...</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取高光强度</span></span><br><span class="line"><span class="keyword">float</span> shininess;</span><br><span class="line"><span class="keyword">if</span> (AI_SUCCESS == material-><span class="built_in">Get</span>(AI_MATKEY_SHININESS, shininess)) {</span><br><span class="line"> <span class="comment">// 使用 shininess...</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取第一个扩散纹理路径</span></span><br><span class="line">aiString texturePath;</span><br><span class="line"><span class="keyword">if</span> (AI_SUCCESS == material-><span class="built_in">GetTexture</span>(aiTextureType_DIFFUSE, <span class="number">0</span>, &texturePath)) {</span><br><span class="line"> <span class="comment">// 使用 texturePath.C_Str()...</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 检查是否有环境光颜色</span></span><br><span class="line"><span class="keyword">if</span> (material-><span class="built_in">HasProperty</span>(AI_MATKEY_COLOR_AMBIENT)) {</span><br><span class="line"> aiColor4D ambientColor;</span><br><span class="line"> <span class="keyword">if</span> (AI_SUCCESS == material-><span class="built_in">Get</span>(AI_MATKEY_COLOR_AMBIENT, ambientColor)) {</span><br><span class="line"> <span class="comment">// 使用 ambientColor...</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h5 id="Texture"><a href="#Texture" class="headerlink" title="Texture"></a>Texture</h5><p>通常,资源使用的纹理存储在单独的文件中,但是也有文件格式将纹理直接嵌入到模型文件中。这样的纹理被加载到aittexture结构中</p><ol><li><strong><code>mWidth</code> 和 <code>mHeight</code></strong>:<ul><li>类型: <code>unsigned int</code></li><li>描述: 分别表示纹理图像的宽度和高度(以像素为单位)。对于非图像格式(例如程序生成的纹理),这些值可能为0。</li></ul></li><li><strong><code>mData</code></strong>:<ul><li>类型: <code>unsigned char*</code></li><li>描述: 指向包含纹理数据的缓冲区。注意,并不是所有情况下都会提供实际的纹理数据;某些导入器可能会直接返回文件路径而不是加载图像数据到内存中。</li></ul></li><li><strong><code>mHeight</code></strong>:<ul><li>类型: <code>unsigned int</code></li><li>描述: 纹理的高度(以像素为单位)。</li></ul></li><li><strong><code>achFormatHint</code></strong>:<ul><li>类型: <code>char[AI_TEXTURE_FORMAT_MAX]</code></li><li>描述: 提供关于纹理格式的提示字符串,例如 <code>"jpg"</code> 或 <code>"png"</code>。这可以帮助你确定如何正确地解码纹理数据。</li></ul></li><li><strong><code>mFilename</code></strong>:<ul><li>类型: <code>aiString</code></li><li>描述: 包含纹理文件的相对或绝对路径名。这是最常用的方式来获取纹理资源的位置。</li></ul></li></ol><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 假设我们有一个 aiTexture* texture</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取纹理文件名</span></span><br><span class="line"><span class="function">std::string <span class="title">filename</span><span class="params">(texture->mFilename.C_Str())</span></span>;</span><br><span class="line">std::cout << <span class="string">"Texture filename: "</span> << filename << std::endl;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 如果有纹理数据,可以尝试读取其大小</span></span><br><span class="line"><span class="keyword">if</span> (texture->mWidth > <span class="number">0</span> && texture->mHeight > <span class="number">0</span>) {</span><br><span class="line"> std::cout << <span class="string">"Texture dimensions: "</span> << texture->mWidth << <span class="string">"x"</span> << texture->mHeight << std::endl;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 根据格式提示来决定如何处理纹理数据</span></span><br><span class="line"><span class="function">std::string <span class="title">formatHint</span><span class="params">(texture->achFormatHint)</span></span>;</span><br><span class="line"><span class="keyword">if</span> (!formatHint.<span class="built_in">empty</span>()) {</span><br><span class="line"> std::cout << <span class="string">"Texture format hint: "</span> << formatHint << std::endl;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 如果有纹理数据,可以直接使用指针访问</span></span><br><span class="line"><span class="keyword">if</span> (texture->mData != <span class="literal">nullptr</span>) {</span><br><span class="line"> <span class="comment">// 注意:这里只是示例,通常你需要根据具体的格式解码这些数据</span></span><br><span class="line"> <span class="keyword">unsigned</span> <span class="keyword">char</span>* data = texture->mData;</span><br><span class="line"> <span class="comment">// 使用纹理数据...</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="stb-image"><a href="#stb-image" class="headerlink" title="stb_image"></a>stb_image</h2><p>加载图像库,通过<code>stbi_load</code>读取图像的宽高和通道</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">GLuint <span class="title">LoadTexture</span><span class="params">(<span class="keyword">const</span> <span class="keyword">char</span> *path, <span class="keyword">bool</span> clip)</span> </span>{</span><br><span class="line"> <span class="comment">// 生成纹理</span></span><br><span class="line"> GLuint texture;</span><br><span class="line"> <span class="built_in">glGenTextures</span>(<span class="number">1</span>, &texture);</span><br><span class="line"> <span class="built_in">stbi_set_flip_vertically_on_load</span>(<span class="literal">true</span>);</span><br><span class="line"> <span class="comment">// 加载图像</span></span><br><span class="line"> <span class="keyword">int</span> img_width, img_height, nrChannels;</span><br><span class="line"> <span class="keyword">unsigned</span> <span class="keyword">char</span> *data =</span><br><span class="line"> <span class="built_in">stbi_load</span>(path, &img_width, &img_height, &nrChannels, <span class="number">0</span>);</span><br><span class="line"> <span class="keyword">if</span> (data) {</span><br><span class="line"> GLenum format;</span><br><span class="line"> <span class="keyword">if</span> (nrChannels == <span class="number">1</span>) {</span><br><span class="line"> format = GL_RED;</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (nrChannels == <span class="number">3</span>) {</span><br><span class="line"> format = GL_RGB;</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (nrChannels == <span class="number">4</span>) {</span><br><span class="line"> format = GL_RGBA;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">throw</span> std::<span class="built_in">runtime_error</span>(<span class="string">"No available format."</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="built_in">glBindTexture</span>(GL_TEXTURE_2D, texture);</span><br><span class="line"> <span class="built_in">glTexImage2D</span>(GL_TEXTURE_2D, <span class="number">0</span>, format, img_width, img_height, <span class="number">0</span>, format,</span><br><span class="line"> GL_UNSIGNED_BYTE, data);</span><br><span class="line"> <span class="built_in">glGenerateMipmap</span>(GL_TEXTURE_2D);</span><br><span class="line"> <span class="keyword">if</span> (clip) {</span><br><span class="line"> <span class="built_in">glTexParameteri</span>(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);</span><br><span class="line"> <span class="built_in">glTexParameteri</span>(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="built_in">glTexParameteri</span>(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);</span><br><span class="line"> <span class="built_in">glTexParameteri</span>(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);</span><br><span class="line"> }</span><br><span class="line"> <span class="built_in">glTexParameteri</span>(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,</span><br><span class="line"> GL_LINEAR_MIPMAP_LINEAR);</span><br><span class="line"> <span class="built_in">glTexParameteri</span>(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);</span><br><span class="line"></span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> std::cerr << <span class="string">"Failed to load texture"</span> << std::endl;</span><br><span class="line"> std::cout << <span class="string">"Error: Failed to load the image because "</span></span><br><span class="line"> << <span class="built_in">stbi_failure_reason</span>();</span><br><span class="line"> <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="built_in">stbi_image_free</span>(data);</span><br><span class="line"> <span class="keyword">return</span> texture;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><link rel="stylesheet" href="/css/spoiler.css" type="text/css"><script src="/js/spoiler.js" type="text/javascript" async></script>]]></content>
<summary type="html"><p>在OpenGL中着色器的编程语言叫做GLSL,类似C语言但是内置了许多有用的函数. 这里简单学习其基础语法和包含特殊函数.<br></summary>
<category term="GLSL编程" scheme="https://www.sekyoro.top/tags/GLSL%E7%BC%96%E7%A8%8B/"/>
</entry>
<entry>
<title>从C++模板谈起</title>
<link href="https://www.sekyoro.top/2024/12/09/%E4%BB%8EC-%E6%A8%A1%E6%9D%BF%E8%B0%88%E8%B5%B7/"/>
<id>https://www.sekyoro.top/2024/12/09/%E4%BB%8EC-%E6%A8%A1%E6%9D%BF%E8%B0%88%E8%B5%B7/</id>
<published>2024-12-09T09:06:21.000Z</published>
<updated>2024-12-13T03:10:16.634Z</updated>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="\assets\js\Meting.min.js"></script><p>C++的模板编程是学习C++不可或缺的一部分,说来讽刺,这部分在Rust中实现得很优雅.<br><span id="more"></span></p><h2 id="函数模板"><a href="#函数模板" class="headerlink" title="函数模板"></a>函数模板</h2><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//C++ 提供了不同的方法来处理这个问题:</span></span><br><span class="line"><span class="comment">// • 为返回类型引入第三个模板参数。</span></span><br><span class="line"><span class="comment">//• 让编译器找出返回类型。</span></span><br><span class="line"><span class="comment">//• 将返回类型声明为两个参数类型的“公共类型”。</span></span><br><span class="line"><span class="function"><span class="keyword">template</span><<span class="keyword">typename</span> T1,<span class="keyword">typename</span> T2,<span class="keyword">typename</span> RT></span></span><br><span class="line"><span class="function">RT <span class="title">lowMax</span><span class="params">(T1 a, T2 b)</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> a>b?a:b;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// since c++14, 使用auto不显式声明返回类型</span></span><br><span class="line"><span class="comment">// :返回类型可能是引用类型,在某些条件下T可能是引用。因为这个原因,应该返回T衰变的类型</span></span><br><span class="line"><span class="keyword">template</span><<span class="keyword">typename</span> T1,<span class="keyword">typename</span> T2></span><br><span class="line"><span class="comment">// 实现也不一定要匹配。事实上,使用true作为操作符的条件就够了</span></span><br><span class="line"><span class="function"><span class="keyword">auto</span> <span class="title">max</span><span class="params">(T1 <span class="keyword">const</span>& a,T2 <span class="keyword">const</span>& b)</span>-> std::<span class="keyword">decay_t</span><<span class="title">decltype</span><span class="params">(<span class="literal">true</span>? T1():T2())</span>> </span>{</span><br><span class="line"> <span class="keyword">return</span> a>b?a:b;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// C++11 后,标准库提供了一种方法来指定选择“公共类型”。std::common_type<>::type会产生</span></span><br><span class="line"><span class="comment">// 由两个(或多个)不同类型作为模板类型的“公共类型”</span></span><br><span class="line"><span class="function"><span class="keyword">template</span><<span class="keyword">typename</span> T1,<span class="keyword">typename</span> T2></span></span><br><span class="line"><span class="function">std::<span class="keyword">common_type_t</span><T1,T2> <span class="title">commonMax</span><span class="params">(T1 a,T2 b)</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> a>b? a:b;</span><br><span class="line">}</span><br><span class="line"><span class="comment">// 注意,自动类型转换在类型推导时有一些限制:</span></span><br><span class="line"><span class="comment">// • 当通过引用声明参数时,简单的转换也不适用于类型推导。用同一个模板参数T声明的两个</span></span><br><span class="line"><span class="comment">// 实参必须完全匹配。</span></span><br><span class="line"><span class="comment">// • 当按值声明参数时,只支持简单的转换:忽略const或volatile的限定符,引用转换为引用的类</span></span><br><span class="line"><span class="comment">// 型,原始数组或函数转换为相应的指针类型。对于使用相同模板参数T声明的两个参数,转换类型必须匹配。</span></span><br><span class="line"><span class="comment">// 默认模板参数</span></span><br><span class="line"><span class="keyword">template</span><<span class="keyword">typename</span> T1,<span class="keyword">typename</span> T2,</span><br><span class="line"><span class="keyword">typename</span> RT = std::<span class="keyword">decay_t</span><<span class="keyword">decltype</span>(<span class="literal">true</span>? <span class="built_in">T1</span>():<span class="built_in">T2</span>())>></span><br><span class="line">RT <span class="built_in">defaultMax</span>(T1 a,T2 b) {</span><br><span class="line"> <span class="keyword">return</span> a>b?a:b;</span><br><span class="line">}</span><br><span class="line"><span class="comment">// 其他相同的情况下,重载解析将优先使用非模板方式</span></span><br><span class="line"><span class="comment">// 重载函数模板</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">max</span><span class="params">(<span class="keyword">int</span> a,<span class="keyword">int</span> b)</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> a;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="模板参数推导"><a href="#模板参数推导" class="headerlink" title="模板参数推导"></a>模板参数推导</h4><p>自动类型转换在类型推导时有一些限制: </p><ul><li>当通过引用声明参数时,简单的转换也不适用于类型推导。用同一个模板参数T声明的两个实参必须完全匹配。 </li><li>当按值声明参数时,只支持简单的转换:忽略const或volatile的限定符,引用转换为引用的类型,原始数组或函数转换为相应的指针类型。对于使用相同模板参数T声明的两个参数,转换类型必须匹配。</li></ul><p>与普通函数一样,函数模板也可以重载。可以使用相同的函数名来声明不同的函数体,当使用 该函数名称时,编译器会决定调用哪一个候选函数。</p><p>重载函数模板时,应该确保只有一 个函数模板与调用匹配。</p><h2 id="类模板"><a href="#类模板" class="headerlink" title="类模板"></a>类模板</h2><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="comment">// 类模板</span></span><br><span class="line"><span class="comment">// 使用类模板时除了可以推导参数的情况下必须使用声明</span></span><br><span class="line"><span class="comment">// 在类模板中使用不带模板参数的类名表明这个内部类的模板参数类型和模板类的参数类型相同</span></span><br><span class="line"><span class="keyword">template</span><<span class="keyword">typename</span> T, std::<span class="keyword">size_t</span> Maxsize></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Stack</span>{</span> </span><br><span class="line"> <span class="keyword">private</span>:</span><br><span class="line"> std::array<T,Maxsize> elems;</span><br><span class="line"> std::<span class="keyword">size_t</span> numElems;</span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line"> <span class="built_in">Stack</span>();</span><br><span class="line"> <span class="built_in">Stack</span>(Stack <span class="keyword">const</span>&) ;</span><br><span class="line"> Stack& <span class="keyword">operator</span>=(Stack <span class="keyword">const</span>&) = <span class="keyword">default</span>;</span><br><span class="line"> <span class="function"><span class="keyword">void</span> <span class="title">push</span><span class="params">(T <span class="keyword">const</span>& elem)</span></span>;</span><br><span class="line"> <span class="function"><span class="keyword">void</span> <span class="title">pop</span><span class="params">()</span></span>;</span><br><span class="line"> <span class="function">T <span class="keyword">const</span>& <span class="title">top</span><span class="params">()</span> <span class="keyword">const</span></span>;</span><br><span class="line"> <span class="function"><span class="keyword">bool</span> <span class="title">empty</span><span class="params">()</span> <span class="keyword">const</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> numElems == <span class="number">0</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="function">std::<span class="keyword">size_t</span> <span class="title">size</span><span class="params">()</span> <span class="keyword">const</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> numElems;</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="keyword">void</span> <span class="title">printOn</span><span class="params">(std::ostream& strm)</span> <span class="keyword">const</span> </span>{</span><br><span class="line"> <span class="keyword">for</span>(<span class="keyword">const</span> T& elem: elems){</span><br><span class="line"> strm<<elem;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">template</span><<span class="keyword">typename</span> U></span><br><span class="line"> <span class="keyword">friend</span> std::ostream& <span class="keyword">operator</span><<(std::ostream& strm,Stack<U,Maxsize> <span class="keyword">const</span> & sk) {</span><br><span class="line"> sk.<span class="built_in">printOn</span>(strm);</span><br><span class="line"> <span class="keyword">return</span> strm;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="comment">// 在需要类名而不是类类型的地方,只能使用Stack。尤其是,指定构造函数的名称(而不是参数)和析构函数时。</span></span><br><span class="line"><span class="comment">// 不能在函数或块作用域内声明或定义类模板。通常,模板只能在全局/命名空间作用域或类中声明内部定义</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 声明成员函数</span></span><br><span class="line"><span class="comment">// 定义类模板的成员函数, 必须将其指定为模板. 使用类模板对类型进行限定</span></span><br><span class="line"><span class="keyword">template</span><<span class="keyword">typename</span> T,std::<span class="keyword">size_t</span> Maxsize></span><br><span class="line">Stack<T,Maxsize>::<span class="built_in">Stack</span>():numElems{<span class="number">0</span>} {</span><br><span class="line"></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">template</span><<span class="keyword">typename</span> T,std::<span class="keyword">size_t</span> Maxsize></span><br><span class="line"><span class="keyword">void</span> Stack<T,Maxsize>::<span class="built_in">push</span>(T <span class="keyword">const</span>& elem) {</span><br><span class="line"> <span class="built_in">assert</span>(numElems<Maxsize);</span><br><span class="line"> elems[numElems] = elem;</span><br><span class="line"> ++numElems;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">template</span><<span class="keyword">typename</span> T,std::<span class="keyword">size_t</span> Maxsize></span><br><span class="line"><span class="keyword">void</span> Stack<T,Maxsize>::<span class="built_in">pop</span>() {</span><br><span class="line"> <span class="built_in">assert</span>(!<span class="built_in">empty</span>());</span><br><span class="line"> numElems--;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">template</span><<span class="keyword">typename</span> T,std::<span class="keyword">size_t</span> Maxsize></span><br><span class="line">T <span class="keyword">const</span>& Stack<T,Maxsize>::<span class="built_in">top</span>() <span class="keyword">const</span> {</span><br><span class="line"> <span class="built_in">assert</span>(!<span class="built_in">empty</span>());</span><br><span class="line"> <span class="keyword">return</span> elems[numElems<span class="number">-1</span>];</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 类模板特化</span></span><br><span class="line"><span class="keyword">template</span><></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Stack</span><</span><span class="keyword">float</span>,<span class="number">12</span>> {</span><br><span class="line"> <span class="keyword">private</span>:</span><br><span class="line"> std::array<<span class="keyword">float</span>,12> elems;</span><br><span class="line"> std::<span class="keyword">size_t</span> numElems;</span><br><span class="line"></span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="comment">// 类模板的偏特化</span></span><br><span class="line"><span class="comment">// template<std::size_t Maxsize></span></span><br><span class="line"><span class="comment">// class Stack<int,Maxsize>{</span></span><br><span class="line"><span class="comment">// private:</span></span><br><span class="line"><span class="comment">// std::array<int,Maxsize> elems;</span></span><br><span class="line"><span class="comment">// std::size_t numElems;</span></span><br><span class="line"><span class="comment">// public:</span></span><br><span class="line"><span class="comment">// };</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">template</span><<span class="keyword">typename</span> T></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Stack</span><</span>T,<span class="number">10</span>>{</span><br><span class="line"> <span class="keyword">private</span>:</span><br><span class="line"> std::array<T,10> elems;</span><br><span class="line"> std::<span class="keyword">size_t</span> numElems;</span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line">};</span><br></pre></td></tr></table></figure><h3 id="模板聚合"><a href="#模板聚合" class="headerlink" title="模板聚合"></a>模板聚合</h3><p>聚合类(不由用户提供、显式或继承的构造函数的类/结构,没有private或protected的非静态数据成员,没有虚函数,也没有virtual、private或protected基类)也可以是模板</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">template</span><<span class="keyword">typename</span> T></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">ValueWithComment</span> {</span></span><br><span class="line">T Value;</span><br><span class="line">std::string comment;</span><br><span class="line">};</span><br><span class="line">ValueWithComment<<span class="keyword">int</span>> vc;</span><br><span class="line">vc.value = <span class="number">42</span>;</span><br><span class="line">vc.comment = <span class="string">"initial value"</span>;</span><br></pre></td></tr></table></figure><h2 id="非类型模板参数"><a href="#非类型模板参数" class="headerlink" title="非类型模板参数"></a>非类型模板参数</h2><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">template</span><<span class="keyword">typename</span> T, std::<span class="keyword">size_t</span> Maxsize></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Stack</span>{</span> </span><br><span class="line"> <span class="keyword">private</span>:</span><br><span class="line"> std::array<T,Maxsize> elems;</span><br><span class="line"> std::<span class="keyword">size_t</span> numElems;</span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line"> <span class="built_in">Stack</span>();</span><br><span class="line"> <span class="built_in">Stack</span>(Stack <span class="keyword">const</span>&) = <span class="keyword">default</span>;</span><br><span class="line"> Stack& <span class="keyword">operator</span>=(Stack <span class="keyword">const</span>&) = <span class="keyword">default</span>;</span><br><span class="line"> <span class="function"><span class="keyword">void</span> <span class="title">push</span><span class="params">(T <span class="keyword">const</span>& elem)</span></span>;</span><br><span class="line"> <span class="function"><span class="keyword">void</span> <span class="title">pop</span><span class="params">()</span></span>;</span><br><span class="line"> <span class="function">T <span class="keyword">const</span>& <span class="title">top</span><span class="params">()</span> <span class="keyword">const</span></span>;</span><br><span class="line"> <span class="function"><span class="keyword">bool</span> <span class="title">empty</span><span class="params">()</span> <span class="keyword">const</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> numElems == <span class="number">0</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="function">std::<span class="keyword">size_t</span> <span class="title">size</span><span class="params">()</span> <span class="keyword">const</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> numElems;</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="keyword">void</span> <span class="title">printOn</span><span class="params">(std::ostream& strm)</span> <span class="keyword">const</span> </span>{</span><br><span class="line"> <span class="keyword">for</span>(<span class="keyword">const</span> T& elem: elems){</span><br><span class="line"> strm<<elem;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">template</span><<span class="keyword">typename</span> U></span><br><span class="line"> <span class="keyword">friend</span> std::ostream& <span class="keyword">operator</span><<(std::ostream& strm,Stack<U,Maxsize> <span class="keyword">const</span> & sk) {</span><br><span class="line"> sk.<span class="built_in">printOn</span>(strm);</span><br><span class="line"> <span class="keyword">return</span> strm;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="函数模板非类型参数"><a href="#函数模板非类型参数" class="headerlink" title="函数模板非类型参数"></a>函数模板非类型参数</h3><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">template</span><<span class="keyword">int</span> Val,<span class="keyword">typename</span> T></span></span><br><span class="line"><span class="function">T <span class="title">addValue</span><span class="params">(T x)</span> </span>{</span><br><span class="line"><span class="keyword">return</span> x+Val;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">template</span><<span class="keyword">auto</span> Val,<span class="keyword">typename</span> T </span>= <span class="keyword">decltype</span>(Val)></span><br><span class="line"><span class="function">T <span class="title">foo</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">template</span><<span class="keyword">typename</span> T, T Val </span>= T{}></span><br><span class="line"><span class="function">T <span class="title">bar</span><span class="params">()</span></span>;</span><br></pre></td></tr></table></figure><h3 id="非类型模板参数限制"><a href="#非类型模板参数限制" class="headerlink" title="非类型模板参数限制"></a>非类型模板参数限制</h3><p>非类型模板参数有一些限制,只能是整型常量值(包括枚举),指向对象/函数/成员的指针,指 向对象或函数的左值引用,或者std::nullptr_t(nullptr的类型)</p><ul><li>浮点数和类型对象不允许作为非类型模板参数</li><li>当向指针或引用传递模板参数时,对象不能是字符串字面值、临时对象或数据成员和其他子对象。</li></ul><p>非类型模板参数可以是编译时表达式</p><h3 id="模板参数类型auto"><a href="#模板参数类型auto" class="headerlink" title="模板参数类型auto"></a>模板参数类型auto</h3><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="keyword">template</span><<span class="keyword">typename</span> T, <span class="keyword">auto</span> Maxsize></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">autoStack</span> {</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="keyword">using</span> size_type = <span class="keyword">decltype</span>(Maxsize);</span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line"> std::array<T,Maxsize> elems;</span><br><span class="line"> size_type numElems;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="built_in">autoStack</span>();</span><br><span class="line"> <span class="function"><span class="keyword">void</span> <span class="title">push</span><span class="params">(T <span class="keyword">const</span>& elem)</span></span>;</span><br><span class="line"> <span class="function"><span class="keyword">void</span> <span class="title">pop</span><span class="params">()</span></span>;</span><br><span class="line"> <span class="function">T <span class="keyword">const</span>& <span class="title">top</span><span class="params">()</span> <span class="keyword">const</span></span>;</span><br><span class="line"> <span class="function"><span class="keyword">bool</span> <span class="title">empty</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> numElems == <span class="number">0</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="function">size_type <span class="title">size</span><span class="params">()</span> <span class="keyword">const</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> numElems;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line">};</span><br></pre></td></tr></table></figure><ul><li>模板的模板参数可以是值,而非类型。 </li><li>不能将浮点数或类类型对象作为非类型模板的参数。对于指向字符串字面量、临时对象和子 对象的指针/引用,有一些限制。 </li><li>使用auto可使模板具有泛型值的非类型模板参数。</li></ul><h2 id="可变参数模板"><a href="#可变参数模板" class="headerlink" title="可变参数模板"></a>可变参数模板</h2><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">template</span><<span class="keyword">typename</span> T,<span class="keyword">typename</span>... Types></span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">print</span><span class="params">(T firstArg,Types... args)</span> </span>{</span><br><span class="line"> std::cout<<firstArg<<<span class="string">'\n'</span>;</span><br><span class="line"> <span class="built_in">print</span>(args...);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="重载可变和非可变模板"><a href="#重载可变和非可变模板" class="headerlink" title="重载可变和非可变模板"></a>重载可变和非可变模板</h3><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">template</span><<span class="keyword">typename</span> T></span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">print</span><span class="params">(T arg)</span> </span>{</span><br><span class="line"> std::cout<<arg<<<span class="string">'\n'</span>;</span><br><span class="line">}</span><br><span class="line"><span class="function"><span class="keyword">template</span><<span class="keyword">typename</span> T,<span class="keyword">typename</span>... Types></span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">print</span><span class="params">(T firstArg,Types... args)</span> </span>{</span><br><span class="line"> <span class="built_in">print</span>(firstArg);</span><br><span class="line"> <span class="built_in">print</span>(args...)</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>若两个函数模板的区别仅在于末尾参数包的不同,则首选没有末尾参数包的函数模板</p><p><code>sizeof...</code>操作符应用在可变参模板</p><h3 id="折叠表达式"><a href="#折叠表达式" class="headerlink" title="折叠表达式"></a>折叠表达式</h3><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">template</span><<span class="keyword">typename</span>... T></span></span><br><span class="line"><span class="function"><span class="keyword">auto</span> <span class="title">foldSum</span><span class="params">(T... s)</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> (... + s);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><img data-src="https://s2.loli.net/2024/12/09/ZT8oyw5s3ChWH1n.png" alt="image-20241209220950737"></p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">Node</span> {</span></span><br><span class="line"> <span class="keyword">int</span> value;</span><br><span class="line"> Node* left;</span><br><span class="line"> Node* right;</span><br><span class="line"> <span class="built_in">Node</span>(<span class="keyword">int</span> i=<span class="number">0</span>):<span class="built_in">value</span>(i),<span class="built_in">left</span>(<span class="literal">nullptr</span>),<span class="built_in">right</span>(<span class="literal">nullptr</span>){}</span><br><span class="line">};</span><br><span class="line"><span class="keyword">auto</span> left = &Node::left;</span><br><span class="line"><span class="keyword">auto</span> right = &Node::right;</span><br><span class="line"><span class="function"><span class="keyword">template</span><<span class="keyword">typename</span> T,<span class="keyword">typename</span>... TP></span></span><br><span class="line"><span class="function">Node* <span class="title">traverse</span><span class="params">(T np,TP... paths)</span></span>{</span><br><span class="line"> <span class="keyword">return</span> (np ->* ... ->* paths);</span><br><span class="line">}</span><br><span class="line"><span class="function"><span class="keyword">template</span><<span class="keyword">typename</span>... Types></span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">printargs</span><span class="params">(Types <span class="keyword">const</span>&... args)</span> </span>{</span><br><span class="line"> (std::cout<< ... << args)<<<span class="string">'\n'</span>;</span><br><span class="line">}</span><br><span class="line"><span class="keyword">template</span><<span class="keyword">typename</span> T></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">AddSpace</span> {</span></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line"> T <span class="keyword">const</span>& ref;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="built_in">AddSpace</span>(T <span class="keyword">const</span>& r):<span class="built_in">ref</span>(r) {</span><br><span class="line"></span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">friend</span> std::ostream& <span class="keyword">operator</span><< (std::ostream& os,AddSpace<T> s) {</span><br><span class="line"> <span class="keyword">return</span> os<< s.ref<<<span class="string">' '</span>;</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"><span class="function"><span class="keyword">template</span><<span class="keyword">typename</span>... Args></span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">print</span><span class="params">(Args... args)</span></span>{</span><br><span class="line"> (std::cout<< ... <<<span class="built_in">AddSpace</span>(args))<<<span class="string">'\n'</span>;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>可变参数模板在实现通用库(如C++标准库)时扮演着重要的角色。 典型的应用是转发可变数量的类型参数</p><h3 id="类模板和表达式"><a href="#类模板和表达式" class="headerlink" title="类模板和表达式"></a>类模板和表达式</h3><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">template</span><<span class="keyword">typename</span>... Args></span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">printDoubled</span><span class="params">(Args... args)</span></span>{</span><br><span class="line"> <span class="built_in">print</span>(args + args ...);</span><br><span class="line">}</span><br><span class="line"><span class="function"><span class="keyword">template</span><<span class="keyword">typename</span>... T></span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">addOne</span><span class="params">(T <span class="keyword">const</span>&... args)</span> </span>{</span><br><span class="line"> <span class="built_in">print</span>(args+<span class="number">1</span> ...);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="表达式"><a href="#表达式" class="headerlink" title="表达式"></a>表达式</h4><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">template</span><<span class="keyword">typename</span>... Args></span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">printDoubled</span><span class="params">(Args... args)</span></span>{</span><br><span class="line"> <span class="built_in">print</span>(args + args ...);</span><br><span class="line">}</span><br><span class="line"><span class="function"><span class="keyword">template</span><<span class="keyword">typename</span>... T></span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">addOne</span><span class="params">(T <span class="keyword">const</span>&... args)</span> </span>{</span><br><span class="line"> <span class="built_in">print</span>(args+<span class="number">1</span> ...);</span><br><span class="line">}</span><br><span class="line"><span class="function"><span class="keyword">template</span><<span class="keyword">typename</span> T1, <span class="keyword">typename</span>... TN></span></span><br><span class="line"><span class="function"><span class="keyword">constexpr</span> <span class="keyword">bool</span> <span class="title">isHomogeneous</span><span class="params">(T1,TN...)</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> (std::is_same<T1,TN>::value && ...);</span><br></pre></td></tr></table></figure><h4 id="索引"><a href="#索引" class="headerlink" title="索引"></a>索引</h4><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">template</span><<span class="keyword">typename</span> C,<span class="keyword">typename</span>... Idx></span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">printElems</span><span class="params">(C <span class="keyword">const</span>& coll,Idx... idx)</span> </span>{</span><br><span class="line"> <span class="built_in">print</span>(coll[idx]...);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">template</span><std::<span class="keyword">size_t</span>... Idx, <span class="keyword">typename</span> C></span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">printIdx</span><span class="params">(C <span class="keyword">const</span>& coll)</span> </span>{</span><br><span class="line"> <span class="built_in">print</span>(coll[Idx]...);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="基础技巧"><a href="#基础技巧" class="headerlink" title="基础技巧"></a>基础技巧</h4><p><strong>typename</strong></p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">template</span><<span class="keyword">typename</span> T></span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">printcoll</span><span class="params">(T <span class="keyword">const</span>& coll)</span> </span>{</span><br><span class="line"> <span class="keyword">typename</span> T::const_iterator pos;</span><br><span class="line"> <span class="function"><span class="keyword">typename</span> T::const_iterator <span class="title">end</span><span class="params">(coll.end())</span></span>;</span><br><span class="line"> <span class="keyword">for</span>(pos = coll.<span class="built_in">begin</span>();pos!=end;++pos) {</span><br><span class="line"> std::cout<<*pos<<<span class="string">' '</span>;</span><br><span class="line"> }</span><br><span class="line"> std::cout<<<span class="string">'\n'</span>;</span><br><span class="line">} </span><br></pre></td></tr></table></figure><p>说明模板内的标识符是类型.当模板参数是类型时,必须使用typename。</p><p><strong>零值初始化</strong></p><p>因此,可以显式调用内置类型的默认构造函数,该构造函数用0初始化内置类型(bool为false, 指针为nullptr)。因此,即使是内置类型,也可以通过零初始化来确保正确的初始化</p><p><strong>使用this-></strong></p><p>对于具有依赖于模板参数的基类类模板,即使成员x被继承,使用名称x本身并不总是等同于 this->x。</p><p><strong>原始数组和字符串字面量的模板</strong></p><h3 id="成员模板"><a href="#成员模板" class="headerlink" title="成员模板"></a>成员模板</h3><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">template</span><<span class="keyword">typename</span> T></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">tempmemberStack</span> {</span></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line"> std::deque<T> elems;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="keyword">void</span> <span class="title">push</span><span class="params">(T <span class="keyword">const</span>&)</span></span>;</span><br><span class="line"> <span class="function"><span class="keyword">void</span> <span class="title">pop</span><span class="params">()</span></span>;</span><br><span class="line"> <span class="function">T <span class="keyword">const</span>& <span class="title">top</span><span class="params">()</span> <span class="keyword">const</span></span>;</span><br><span class="line"> <span class="function"><span class="keyword">bool</span> <span class="title">empty</span><span class="params">()</span> <span class="keyword">const</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> elems.<span class="built_in">empty</span>();</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 成员模板</span></span><br><span class="line"> <span class="keyword">template</span><<span class="keyword">typename</span> T2></span><br><span class="line"> tempmemberStack& <span class="keyword">operator</span>=(tempmemberStack<T2> <span class="keyword">const</span>&); </span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="keyword">template</span><<span class="keyword">typename</span> T></span><br><span class="line"><span class="keyword">template</span><<span class="keyword">typename</span> T2></span><br><span class="line">tempmemberStack<T>& tempmemberStack<T>::<span class="keyword">operator</span>=(tempmemberStack<T2> <span class="keyword">const</span>& op2) {</span><br><span class="line"> tempmemberStack<T2> <span class="built_in">tmp</span>(op2);</span><br><span class="line"> elems.<span class="built_in">clear</span>();</span><br><span class="line"> <span class="keyword">while</span>(!tmp.<span class="built_in">empty</span>()) {</span><br><span class="line"> elems.<span class="built_in">push_front</span>(tmp.<span class="built_in">top</span>());</span><br><span class="line"> tmp.<span class="built_in">pop</span>();</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> *<span class="keyword">this</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">template</span><<span class="keyword">typename</span> T,<span class="keyword">typename</span> Cont=std::deque<T>></span><br><span class="line">class memStack {</span><br><span class="line"> <span class="keyword">private</span>:</span><br><span class="line"> Cont elems;</span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="keyword">void</span> <span class="title">push</span><span class="params">(T <span class="keyword">const</span>&)</span></span>;</span><br><span class="line"> <span class="function"><span class="keyword">void</span> <span class="title">pop</span><span class="params">()</span></span>;</span><br><span class="line"> <span class="function">T <span class="keyword">const</span>& <span class="title">top</span><span class="params">()</span> <span class="keyword">const</span></span>;</span><br><span class="line"> <span class="function"><span class="keyword">bool</span> <span class="title">empty</span><span class="params">()</span> <span class="keyword">const</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> elems.<span class="built_in">empty</span>();</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">template</span><<span class="keyword">typename</span> T2,<span class="keyword">typename</span> Cont2></span><br><span class="line"> memStack& <span class="keyword">operator</span>=(memStack<T2,Cont2> <span class="keyword">const</span>&);</span><br><span class="line"> <span class="keyword">template</span><<span class="keyword">typename</span>,<span class="keyword">typename</span>> <span class="keyword">friend</span> <span class="class"><span class="keyword">class</span> <span class="title">memStack</span>;</span></span><br><span class="line">};</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>成员函数模板也可以全特化,不能偏特化</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">BoolString</span> {</span></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line"> std::string value;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="built_in">BoolString</span> (std::string <span class="keyword">const</span>& s):<span class="built_in">value</span>(s) {</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="keyword">template</span><<span class="keyword">typename</span> T</span>= std::string></span><br><span class="line"> <span class="function">T <span class="title">get</span><span class="params">()</span> <span class="keyword">const</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> value;</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"><span class="keyword">template</span><></span><br><span class="line"><span class="keyword">inline</span> <span class="keyword">bool</span> BoolString::get<<span class="keyword">bool</span>>() <span class="keyword">const</span> {</span><br><span class="line"> <span class="keyword">return</span> value == <span class="string">"true"</span> || value ==<span class="string">"1"</span> || value == <span class="string">"on"</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>不需要也不能对特化进行声明,只需要定义。因为它是全特化的,且位于头文件中,所以若定 义包含在不同的转译单元中,必须使用内联声明,以避免错误。</p><h4 id="特殊成员函数的模板"><a href="#特殊成员函数的模板" class="headerlink" title="特殊成员函数的模板"></a>特殊成员函数的模板</h4><p><strong>只要特殊成员函数允许复制或移动对象,就可以使用模板成员函数</strong>。与上面定义的赋值操作符 类似,也可以是构造函数。但模板构造函数或模板赋值操作符,不能取代预定义构造函数或赋值操 作符,成员模板不作为复制或移动对象的特殊成员函数。本例中,对于相同类型的堆栈赋值,仍然使用默认赋值操作符</p><h4 id="泛型Lambda和成员模板"><a href="#泛型Lambda和成员模板" class="headerlink" title="泛型Lambda和成员模板"></a>泛型Lambda和成员模板</h4><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"> <span class="keyword">auto</span> sum = [](<span class="keyword">auto</span> x,<span class="keyword">auto</span> y) {</span><br><span class="line"> <span class="keyword">return</span> x+y;</span><br><span class="line"> }</span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">SomeCompilerSpecificName</span> {</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="built_in">SomeCompilerSpecificName</span>();</span><br><span class="line"> <span class="keyword">template</span><<span class="keyword">typename</span> T1,<span class="keyword">typename</span> T2></span><br><span class="line"> <span class="comment">// 仿函数、函数对象</span></span><br><span class="line"> <span class="function"><span class="keyword">auto</span> <span class="title">operator</span><span class="params">()</span><span class="params">(T1 x,T2 y)</span> <span class="keyword">const</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> x+y;</span><br><span class="line"> }</span><br><span class="line">};</span><br></pre></td></tr></table></figure><h3 id="变量模板"><a href="#变量模板" class="headerlink" title="变量模板"></a>变量模板</h3><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 变量模板</span></span><br><span class="line"><span class="keyword">template</span><<span class="keyword">typename</span> T = <span class="keyword">long</span> <span class="keyword">double</span>></span><br><span class="line"><span class="keyword">constexpr</span> T pi{<span class="number">3.141</span>};</span><br><span class="line"></span><br><span class="line"><span class="comment">// 数据成员模板</span></span><br><span class="line"><span class="keyword">template</span><<span class="keyword">typename</span> T></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">MyClass</span> {</span></span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line"> <span class="keyword">static</span> <span class="keyword">constexpr</span> <span class="keyword">int</span> max = <span class="number">100</span>;</span><br><span class="line">};</span><br><span class="line"><span class="keyword">template</span><<span class="keyword">typename</span> T></span><br><span class="line"><span class="keyword">constexpr</span> <span class="keyword">int</span> myMax = MyClass<T>::max;</span><br><span class="line"><span class="keyword">template</span><<span class="keyword">typename</span> T></span><br><span class="line"><span class="keyword">constexpr</span> <span class="keyword">bool</span> isSigned = std::numeric_limits<T>::is_signed</span><br></pre></td></tr></table></figure><h2 id="std-enable-if-lt-gt-和移动语义"><a href="#std-enable-if-lt-gt-和移动语义" class="headerlink" title="std::enable_if<>和移动语义"></a>std::enable_if<>和移动语义</h2><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">X</span>{</span></span><br><span class="line"></span><br><span class="line">};</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">g</span><span class="params">(X&)</span> </span>{</span><br><span class="line"> std::cout<<<span class="string">"g() for variable\n"</span>;</span><br><span class="line">}</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">g</span><span class="params">(X <span class="keyword">const</span> &)</span> </span>{</span><br><span class="line"> std::cout<<<span class="string">"g() for constant\n"</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">g</span><span class="params">(X&&)</span> </span>{</span><br><span class="line"> std::cout<<<span class="string">"g() for movable object\n"</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">template</span><<span class="keyword">typename</span> T></span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">f</span><span class="params">(T&& val)</span> </span>{</span><br><span class="line"> <span class="built_in">g</span>(std::forward<T>(val));</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Person</span> {</span></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line"> std::string name;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="keyword">template</span><<span class="keyword">typename</span> STR></span></span><br><span class="line"><span class="function"> <span class="keyword">explicit</span> <span class="title">Person</span><span class="params">(STR&& n)</span>:name(std::forward<STR>(n)){</span>}</span><br><span class="line"></span><br><span class="line"> <span class="built_in">Person</span>(Person <span class="keyword">const</span>& p):name{p.name}{</span><br><span class="line"></span><br><span class="line"> }</span><br><span class="line"> <span class="built_in">Person</span>(Person &&p):name{std::<span class="built_in">move</span>(p.name)}{</span><br><span class="line"></span><br><span class="line"> }</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>从C++11开始,标准库提供了辅助模板std::enable_if<>,以<strong>在特定的编译时条件下忽略函数模板</strong></p><p>std::enable_if<> 是一种类型特征,计算作为其(第一个)模板参数传递的给定编译时 表达式: </p><ul><li>若表达式结果为true,其类型成员类型将产生一个类型:</li><li>若没有传递第二个模板参数,则该类型为void。否则,该类型就是第二个模板参数类型</li></ul><p>若表达式结果为false,则没有定义成员类型。由于SFINAE的模板特性(替换失败不为 过),这将忽略使用enable_if表达式的函数模板</p><p>在声明中间使用enable_if表达式非常笨拙。由于这个原因,使用std::enable_if<>的常见方法是 使用带有默认值的函数模板参数:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">template</span><<span class="keyword">typename</span> T></span><br><span class="line"><span class="keyword">typename</span> std::<span class="keyword">enable_if_t</span><(<span class="built_in"><span class="keyword">sizeof</span></span>(T)><span class="number">1</span>),T> <span class="built_in">foo</span>() {</span><br><span class="line"> <span class="keyword">return</span> T{}*<span class="number">2</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">template</span><<span class="keyword">typename</span> T,<span class="keyword">typename</span> = std::<span class="keyword">enable_if_t</span><(<span class="built_in"><span class="keyword">sizeof</span></span>(T)><span class="number">4</span>)>></span><br><span class="line"><span class="keyword">void</span> <span class="built_in">foo</span>() {</span><br><span class="line"> </span><br><span class="line">}</span><br><span class="line"><span class="keyword">template</span><<span class="keyword">typename</span> T></span><br><span class="line"><span class="keyword">typename</span> std::<span class="keyword">enable_if_t</span><(<span class="built_in"><span class="keyword">sizeof</span></span>(T)><span class="number">1</span>),T> <span class="built_in">foo</span>() {</span><br><span class="line"> <span class="keyword">return</span> T{}*<span class="number">2</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">template</span><<span class="keyword">typename</span> T,<span class="keyword">typename</span> = std::<span class="keyword">enable_if_t</span><(<span class="built_in"><span class="keyword">sizeof</span></span>(T)><span class="number">4</span>)>></span><br><span class="line"><span class="keyword">void</span> <span class="built_in">foo</span>() {</span><br><span class="line"> </span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>禁用模板构造函数</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Person</span> {</span></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line"> std::string name;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="keyword">template</span><<span class="keyword">typename</span> STR,<span class="keyword">typename</span> = std::<span class="keyword">enable_if_t</span><std::is_convertible_v<STR,std::string> >></span><br><span class="line"> <span class="keyword">explicit</span> <span class="built_in">Person</span>(STR&& n):<span class="built_in">name</span>(std::forward<STR>(n)){}</span><br><span class="line"></span><br><span class="line"> <span class="built_in">Person</span>(Person <span class="keyword">const</span>& p):name{p.name}{</span><br><span class="line"></span><br><span class="line"> }</span><br><span class="line"> <span class="built_in">Person</span>(Person &&p):name{std::<span class="built_in">move</span>(p.name)}{</span><br><span class="line"></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="使用概念简化std-enable-if表达式"><a href="#使用概念简化std-enable-if表达式" class="headerlink" title="使用概念简化std::enable_if表达式"></a>使用概念简化std::enable_if表达式</h4><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">template</span><<span class="keyword">typename</span> T></span><br><span class="line"><span class="keyword">concept</span> ConvertibleToString = std::is_convertible_v<T,std::string>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">template</span><<span class="keyword">typename</span> T></span></span><br><span class="line"><span class="function"><span class="keyword">requires</span> ConvertibleToString<STR></span></span><br><span class="line"><span class="function"><span class="title">Person</span><span class="params">(STR&&)</span>....</span></span><br><span class="line"><span class="function"></span></span><br><span class="line"><span class="function"><span class="keyword">template</span><ConvertibleToString STR></span></span><br><span class="line"><span class="function"><span class="title">Person</span><span class="params">(STR&&)</span></span></span><br></pre></td></tr></table></figure><h3 id="按值与按引用传递"><a href="#按值与按引用传递" class="headerlink" title="按值与按引用传递"></a>按值与按引用传递</h3><h4 id="按值传递"><a href="#按值传递" class="headerlink" title="按值传递"></a>按值传递</h4><p><strong>按值传递参数</strong>时,原则上必须复制每个参数,每个参数都成为所传递实参的副本。对于类,作 为副本创建的对象通常由复制构造函数初始化。 调用复制构造函数的代价可能会很高。然而,即使在按值传递参数时,也有方法来避免复制: 编译器可能会优化复制对象的复制操作,并且通过移动语义,对复杂对象的操作也可以变得廉价</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">template</span><<span class="keyword">typename</span> T></span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">printV</span><span class="params">(T arg)</span> </span>{</span><br><span class="line"></span><br><span class="line">}</span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Student</span>{</span>};</span><br><span class="line">Student s;</span><br><span class="line"><span class="built_in">printV</span>(std::<span class="built_in">move</span>(s));</span><br></pre></td></tr></table></figure><p>按值传递:1. 会进行拷贝/移动构造,相当于进行了一次拷贝 2. 会decay衰变::当按值传递参数时,类型会衰变。从而数组将转换为指针,并删除 const 和 volatile 等限定符(就像使用值作为使用auto声明的对象的初始化式一样)</p><p>优点: 方便</p><p>缺点: 多一次构造并且参数会衰变,影响数组和指针</p><h4 id="按引用传递"><a href="#按引用传递" class="headerlink" title="按引用传递"></a>按引用传递</h4><p><strong>常量引用</strong></p><p>为了避免(不必要的)复制,传递非临时对象时,可以使用常量引用</p><p>传递引用类型不会衰变 当通过引用将参数进行传递时,就不会衰变。从而不会将数组转换为指针,并且不删除const和 volatile等限定符。因为调用参数声明为Tconst&,所以模板参数T本身并没有推导为const。</p><p>优点:避免(不必要的)复制</p><p>缺点:通过引用传递参数是通过传递参数地址实现的。地址编码紧凑时,将地址从调用 方传递给被调用方的效率很高。然而,在编译代码时,传递地址会给编译器带来不确定性</p><p><strong>传递非常量引用</strong></p><p>当通过传递的参数作为返回值时(例如,使用out或inout参数时),必须使用非常量引用(除非 通过指针传递)。在传递参数时,不会创建副本,被调用函数模板的参数只能直接访问传递的参数</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">template</span><<span class="keyword">typename</span> T></span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">OutR</span><span class="params">(T& arg)</span> </span>{</span><br><span class="line">...</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>不允许对一个临时的(prvalue)或一个通过std::move()(xvalue)传递的现有对象调用</p><p>如果传递const就会有比较尴尬的情况.。若传递const参数,可能导致arg变成一个常量引用的声明,这意 味着允许传递右值,但这里需要左值.这种情况下,修改函数模板中传递的参数是错误的。在表达式中传递常量对象是可能的,但当 函数完全实例化时(这可能发生在编译的后期),修改该值的尝试都将触发错误</p><p>需要使用静态断言或std::enable_if或概念禁用</p><p><strong>通过转发引用进行传递</strong></p><p>使用引用调用的原因是能够完美地转发参数。但当使用转发引用(定义为模板形参的右值引用)时,需要使用特殊的规则。</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">template</span><<span class="keyword">typename</span> T></span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">printV</span><span class="params">(T&& arg)</span></span>{</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>将参数声明为转发引用几乎完美。但是注意传左值时,T推到为左引用不能用来直接初始化值.</p><h4 id="std-ref-和std-cref"><a href="#std-ref-和std-cref" class="headerlink" title="std::ref()和std::cref()"></a>std::ref()和std::cref()</h4><p>可以<strong>让调用者决定函数模板参数是通过值传递,还是通过引用传递</strong>。当模板声明为 按值接受参数时,调用者可以使用在头文件中声明的std::cref()和std::ref(),通过引用 传递参数。 它们都是将对象包装为引用,本身不能直接与其他类型比较,往往需要一个转换.</p><h3 id="编译时编程"><a href="#编译时编程" class="headerlink" title="编译时编程"></a>编译时编程</h3><p>使用constexpr计算</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">template</span><<span class="keyword">unsigned</span> p,<span class="keyword">unsigned</span> d></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">DoIsPrime</span> {</span></span><br><span class="line"> <span class="keyword">static</span> <span class="keyword">bool</span> <span class="keyword">constexpr</span> value = (p%d != <span class="number">0</span>) && DoIsPrime<p,d<span class="number">-1</span>>::value;</span><br><span class="line">};</span><br><span class="line"><span class="comment">//偏特化</span></span><br><span class="line"><span class="keyword">template</span><<span class="keyword">unsigned</span> p></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">DoIsPrime</span><</span>p,<span class="number">2</span>> {</span><br><span class="line"> <span class="keyword">static</span> <span class="keyword">constexpr</span> <span class="keyword">bool</span> value = (p%<span class="number">2</span> != <span class="number">0</span>);</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="comment">//重载</span></span><br><span class="line"><span class="keyword">template</span><<span class="keyword">unsigned</span> p></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">IsPrime</span>{</span></span><br><span class="line"> <span class="keyword">static</span> <span class="keyword">constexpr</span> <span class="keyword">bool</span> value = DoIsPrime<p,p/<span class="number">2</span>>::value;</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="keyword">template</span><></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">IsPrime</span><</span><span class="number">0</span>> {</span><br><span class="line"> <span class="keyword">static</span> <span class="keyword">constexpr</span> <span class="keyword">bool</span> value = <span class="literal">false</span>;</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="keyword">template</span><></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">IsPrime</span><</span><span class="number">1</span>> {</span><br><span class="line"> <span class="keyword">static</span> <span class="keyword">constexpr</span> <span class="keyword">bool</span> value = <span class="literal">false</span>;</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">template</span><></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">IsPrime</span><</span><span class="number">2</span>> {</span><br><span class="line"> <span class="keyword">static</span> <span class="keyword">constexpr</span> <span class="keyword">bool</span> value = <span class="literal">true</span>;</span><br><span class="line">};</span><br><span class="line"><span class="keyword">template</span><></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">IsPrime</span><</span><span class="number">3</span>> {</span><br><span class="line"> <span class="keyword">static</span> <span class="keyword">constexpr</span> <span class="keyword">bool</span> value = <span class="literal">true</span>;</span><br><span class="line">};</span><br><span class="line"><span class="function"><span class="keyword">constexpr</span> <span class="keyword">bool</span> <span class="title">doIsPrime</span><span class="params">(<span class="keyword">unsigned</span> p,<span class="keyword">unsigned</span> d)</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> d!=<span class="number">2</span> ? (p%d!=<span class="number">0</span>) && <span class="built_in">doIsPrime</span>(p,d<span class="number">-1</span>):(p%<span class="number">2</span>!=<span class="number">0</span>);</span><br><span class="line">}</span><br><span class="line"><span class="function"><span class="keyword">constexpr</span> <span class="keyword">bool</span> <span class="title">longisPrime</span><span class="params">(<span class="keyword">unsigned</span> <span class="keyword">int</span> p)</span> </span>{</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">unsigned</span> <span class="keyword">int</span> d=<span class="number">2</span>;d<=p/<span class="number">2</span>;d++) {</span><br><span class="line"> <span class="keyword">if</span>(p%d==<span class="number">0</span>){</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> p><span class="number">1</span>;</span><br><span class="line">}</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>使用偏特化的执行路径选择</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">template</span><<span class="keyword">int</span> SZ,<span class="keyword">bool</span> = <span class="built_in">isPrime</span>(SZ)></span><br><span class="line">struct Helper;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">template</span><<span class="keyword">int</span> SZ></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">Helper</span><</span>SZ,<span class="literal">false</span>> {</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="keyword">template</span><<span class="keyword">int</span> SZ></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">Helper</span><</span>SZ,<span class="literal">true</span>> {</span><br><span class="line"></span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">template</span><<span class="keyword">typename</span> T,std::<span class="keyword">size_t</span> SZ></span></span><br><span class="line"><span class="function"><span class="keyword">long</span> <span class="title">foo</span><span class="params">(std::array<T,SZ> <span class="keyword">const</span>& coll)</span> </span>{</span><br><span class="line"> Helper<SZ> h;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p> 因为函数模板不支持偏特化,所以必须使用其他机制根据某些约束来更改函数实现。可供的选择包括: </p><ul><li>带有静态函数的类</li><li>std::enable_if</li><li>SFINAE特性</li><li>编译时if特性</li></ul><h4 id="SFINAE"><a href="#SFINAE" class="headerlink" title="SFINAE"></a>SFINAE</h4><p>以各种参数类型重载的函数很常见。因此,当编译器看到对重载函数的调用时,必须 考虑每个候选函数,评估调用参数,并选择最匹配的候选函数。 候选集包括函数模板的情况下,编译器首先必须确定为该候选对象使用哪些模板参数,然后在 函数参数列表及其返回类型中替换这些参数,然后评估匹配程度(就像普通函数一样)。但替换过程 可能会遇到问题:可能产生毫无意义的构造。语言规则并不认为这种无意义的替换会导致错误,而 具有这种问题的候选则会直接忽略。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">template</span><<span class="keyword">typename</span> T></span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">foot</span><span class="params">(T t)</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">if</span> <span class="title">constexpr</span> <span class="params">(std::is_integral_v<T>)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span>(t><span class="number">0</span>){ </span><br><span class="line"> <span class="built_in">foo</span> (t<span class="number">-1</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span>{</span><br><span class="line"> <span class="built_in"><span class="keyword">static_assert</span></span>(!std::is_integral_v<T>,<span class="string">"no integral"</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="实际使用模板"><a href="#实际使用模板" class="headerlink" title="实际使用模板"></a>实际使用模板</h3><h4 id="包含模型"><a href="#包含模型" class="headerlink" title="包含模型"></a>包含模型</h4><p>为了实例化模板,编译器必须知道应该实例化哪个定义,以及应该为哪个模板参数实例化它。常见的解决方案是使用与宏或内联函数相同的方法:在声明模板的头文件中包 含模板的定义。</p><p>这在实践中是一个问题,因为它增加了编译器编译重要程序所需的时间。因此,我们将研究一 些方法来解决这个问题,包括<strong>预编译头文件</strong>和<strong>使用显式模板实例化</strong></p><h3 id="通用库设计"><a href="#通用库设计" class="headerlink" title="通用库设计"></a>通用库设计</h3><p>C++ 中,有几种类型可以很好地用于回调,它们既可以作为函数调用参数传递,也可以以f(… ) 方式直接使用: </p><ul><li>函数指针类型 </li><li>具有重载operator()(函数操作符,有时称为函子)的类类型,包括Lambda</li><li>使用转换函数生成指向函数的指针或指向函数引用的类类型 这些类型统称为函数对象类型,这种类型的值就是函数对象</li></ul><h4 id="std-invoke"><a href="#std-invoke" class="headerlink" title="std::invoke"></a>std::invoke</h4><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">template</span><<span class="keyword">typename</span> Callable,<span class="keyword">typename</span>... Args></span></span><br><span class="line"><span class="function"><span class="keyword">decltype</span>(<span class="keyword">auto</span>) <span class="title">call</span><span class="params">(Callable&& op,Args&&... args)</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">if</span> <span class="title">constexpr</span><span class="params">(std::is_same_v<std::<span class="keyword">invoke_result_t</span><Callable,Args...>,<span class="keyword">void</span>>)</span></span>{</span><br><span class="line"> std::<span class="built_in">invoke</span>(std::forward<Callable>(op),std::forward<Args>(args)...);</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }<span class="keyword">else</span>{</span><br><span class="line"> <span class="keyword">decltype</span>(<span class="keyword">auto</span>) ret{std::<span class="built_in">invoke</span>(std::forward<Callable>(op),std::forward<Args>(args)...)};</span><br><span class="line"> <span class="keyword">return</span> ret;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="类型特征"><a href="#类型特征" class="headerlink" title="类型特征"></a>类型特征</h4><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">template</span><<span class="keyword">typename</span> T></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">C</span> {</span></span><br><span class="line"> <span class="built_in"><span class="keyword">static_assert</span></span>(!std::is_same_v<std::<span class="keyword">remove_cv_t</span><T>,<span class="keyword">void</span>>,<span class="string">"invalid instantiation of class C for void type"</span>);</span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="keyword">template</span><<span class="keyword">typename</span> V></span></span><br><span class="line"><span class="function"> <span class="keyword">void</span> <span class="title">f</span><span class="params">(V&& v)</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">if</span> <span class="title">constexpr</span><span class="params">(std::is_reference_v<T>)</span> </span>{</span><br><span class="line"></span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="keyword">if</span> <span class="title">constexpr</span><span class="params">(std::is_convertible_v<std::<span class="keyword">decay_t</span><V>,T>)</span> </span>{</span><br><span class="line"></span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="keyword">if</span> <span class="title">constexpr</span><span class="params">(std::has_virtual_destructor_v<V>)</span> </span>{</span><br><span class="line"></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"></span><br></pre></td></tr></table></figure><h4 id="std-addressof"><a href="#std-addressof" class="headerlink" title="std::addressof"></a>std::addressof</h4><p> std::addressof<>() 函数模板生成对象或函数的实际地址。即使对象类型有重载操作符&,也能 工作。尽管后者很少使用,但可能会发生。因此,如果需要任意类型对象的地址,建议使用addressof()</p><h4 id="std-declval"><a href="#std-declval" class="headerlink" title="std::declval"></a>std::declval</h4><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">template</span><<span class="keyword">typename</span> T1,<span class="keyword">typename</span> T2, <span class="keyword">typename</span> RT=std::<span class="keyword">decay_t</span><<span class="keyword">decltype</span>(<span class="literal">true</span>?std::declval<T1>():std::declval<T2>())>></span><br><span class="line">RT <span class="built_in">max</span>(T1 a,T2 b) {</span><br><span class="line"> <span class="keyword">return</span> b<a?a:b;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>std::declval<>() 函数模板可以用作特定类型的对象引用的占位符。该函数没有定义,因此不能 调用(也不创建对象)。因此,只能用于未求值的操作数(decltype和sizeof构造的操作数)。因此与其尝试创建一个对象,可以假设有一个相应类型的对象。</p><p>不过,这只可能在decltype未求值的上下文中实 现。 不要忘记使用std::decay<>类型来确保默认的返回类型不是一个引用,因为std::declval()本身 会产生右值引用。否则,像max(1,2)这样的调用将得到一个int&&的返回类型</p><h4 id="完美转发临时变量"><a href="#完美转发临时变量" class="headerlink" title="完美转发临时变量"></a>完美转发临时变量</h4><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">template</span><<span class="keyword">typename</span> T></span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">foo</span><span class="params">(T&& x)</span> </span>{</span><br><span class="line"><span class="built_in">g</span>(std::forward<T>(x));</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="模板参数的引用"><a href="#模板参数的引用" class="headerlink" title="模板参数的引用"></a>模板参数的引用</h4><p>使用decltype(auto)可以很容易地产生引用类型,因此在上下文中最好不要使用。</p><p>开发者应该注意 decltype 产生类型的差别,这取决于传递的参数是声明还是表达式: • decltype(e) 中,若e 是实体 (如变量、函数、枚举器或数据成员) 或类成员访问的名称,则 decltype(e) 生成该实体声明的类型或指定的类成员。因此,decltype可以用来检查变量的类型。 这用在需要精确匹配现有声明的类型时。否则,若e是其他表达式,decltype(e)会产生一个类型,表示该表达式的类型和值别</p><ul><li>若e是类型T的左值,则decltype(e)生成T&。</li><li>若e是类型T的xvalue,则decltype(e)生成T&&。</li><li>若e是类型T的prvalue,则decltype(e)生成T</li></ul><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">check_ref</span><span class="params">(std::string&& s)</span> </span>{</span><br><span class="line"> std::is_lvalue_reference<<span class="keyword">decltype</span>(s)>::value;</span><br><span class="line"> std::is_rvalue_reference<<span class="keyword">decltype</span>(s)>::value;</span><br><span class="line"> std::is_same<<span class="keyword">decltype</span>(s),std::string&>::value;</span><br><span class="line"> std::is_same<<span class="keyword">decltype</span>(s),std::string&&>::value;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"> std::is_lvalue_reference<<span class="keyword">decltype</span>((s))>::value;</span><br><span class="line"> std::is_rvalue_reference<<span class="keyword">decltype</span>((s))>::value;</span><br><span class="line"> std::is_same<<span class="keyword">decltype</span>((s)),std::string&>::value;</span><br><span class="line"> std::is_same<<span class="keyword">decltype</span>((s)),std::string&&>::value;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>auto使用模板参数推导规则来确定感兴趣的类型,实际的类型是通过直接对表达式应用decltype来确定</p><h2 id="显示实例化"><a href="#显示实例化" class="headerlink" title="显示实例化"></a>显示实例化</h2><p>可以为模板特化显式地创建实例化点。实现点的构造称为显式实例化指令。语法上,由关键字 template 和要实例化的特化声明组成</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">template</span><<span class="keyword">typename</span> T></span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">f</span><span class="params">(F)</span></span>{}</span><br><span class="line"></span><br><span class="line"><span class="keyword">template</span> <span class="keyword">void</span> f<<span class="keyword">int</span>>(<span class="keyword">int</span>);</span><br><span class="line"><span class="keyword">template</span> <span class="keyword">void</span> f<>(<span class="keyword">float</span>);</span><br><span class="line"><span class="function"><span class="keyword">template</span> <span class="keyword">void</span> <span class="title">f</span><span class="params">(<span class="keyword">long</span>)</span></span>;</span><br></pre></td></tr></table></figure><h3 id="手动实例化"><a href="#手动实例化" class="headerlink" title="手动实例化"></a>手动实例化</h3><p>自动模板实例化对构建时间有很大的负面影响。对于实现贪婪实 例化的编译器来说尤其如此,因为相同的模板特化,可能在许多不同的翻译单元中实 例化和优化</p><p>改进构建时间的技术包括,在<strong>特定位置手动实例化程序所需的那些模板特化</strong>,并<strong>在所有其他翻译单元中抑制实例化</strong>。确保这种抑制的一种可移植的方法是不提供模板定义,除非在显式实例化的 翻译单元中定义</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 翻译单元1</span></span><br><span class="line"><span class="function"><span class="keyword">template</span><<span class="keyword">typename</span> T> <span class="keyword">void</span> <span class="title">exf</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">g</span><span class="params">()</span> </span>{</span><br><span class="line"> exf<<span class="keyword">int</span>>();</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 翻译单元2</span></span><br><span class="line"><span class="function"><span class="keyword">template</span><<span class="keyword">typename</span> T> <span class="keyword">void</span> <span class="title">exf</span><span class="params">()</span> </span>{}</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">g</span><span class="params">()</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="built_in">g</span>();</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>第一个翻译单元中,编译器无法看到函数模板f的定义,因此不会(不能)生成f的实例化。 第二个翻译单元通过显式实例化定义提供f的定义;没有它,程序将无法连接。 手动实例化有一个明显的缺点:必须小心地跟踪要实例化的实体。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// f.hpp</span></span><br><span class="line"><span class="function"><span class="keyword">template</span><<span class="keyword">typename</span> T> <span class="keyword">void</span> <span class="title">f</span><span class="params">()</span></span>; <span class="comment">// no definition</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// f.tpp</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"f.hpp"</span></span></span><br><span class="line"><span class="function"><span class="keyword">template</span><<span class="keyword">typename</span> T> <span class="keyword">void</span> <span class="title">f</span><span class="params">()</span></span>{} <span class="comment">// definition</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">// f.cpp</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"f.tpp"</span></span></span><br><span class="line"><span class="keyword">template</span> <span class="keyword">void</span> f<<span class="keyword">int</span>>(); <span class="comment">// manual instantiation</span></span><br></pre></td></tr></table></figure><p>可以只包含f.hpp来获得f的声明,可以根据需要将显式实例化添加到f.cpp中。或者,若手动实例化过于繁重,还可以包含f.tpp来启用自动实例化。</p><h3 id="显示实例化声明"><a href="#显示实例化声明" class="headerlink" title="显示实例化声明"></a>显示实例化声明</h3><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// t.hpp</span></span><br><span class="line"><span class="function"><span class="keyword">template</span><<span class="keyword">typename</span> T> <span class="keyword">void</span> <span class="title">f</span><span class="params">()</span> </span>{</span><br><span class="line">}</span><br><span class="line"><span class="keyword">extern</span> <span class="keyword">template</span> <span class="keyword">void</span> f<<span class="keyword">int</span>>();</span><br><span class="line"><span class="keyword">extern</span> <span class="keyword">template</span> <span class="keyword">void</span> f<<span class="keyword">float</span>>();</span><br><span class="line"></span><br><span class="line"><span class="comment">// t.cpp</span></span><br><span class="line"><span class="keyword">template</span> <span class="keyword">void</span> f<<span class="keyword">int</span>>(){</span><br><span class="line"> </span><br><span class="line">}</span><br><span class="line"><span class="keyword">template</span> <span class="keyword">void</span> f<<span class="keyword">float</span>>(){};</span><br></pre></td></tr></table></figure><p>消除冗余自动实例化的一种更有针对性的方法是使用显式实例化声明,它以关键字extern为前缀的显式实例化指令。<strong>显式实例化声明通常会抑制已命名模板特化的自动实例化,因为声明已命名模板特化将在程序中的某个地方定义</strong></p><link rel="stylesheet" href="/css/spoiler.css" type="text/css"><script src="/js/spoiler.js" type="text/javascript" async></script>]]></content>
<summary type="html"><p>C++的模板编程是学习C++不可或缺的一部分,说来讽刺,这部分在Rust中实现得很优雅.<br></summary>
<category term="C++" scheme="https://www.sekyoro.top/tags/C/"/>
<category term="C++ templates" scheme="https://www.sekyoro.top/tags/C-templates/"/>
</entry>
<entry>
<title>vqvae及其变体代码学习</title>
<link href="https://www.sekyoro.top/2024/11/18/vqvae%E5%8F%8A%E5%85%B6%E5%8F%98%E4%BD%93%E4%BB%A3%E7%A0%81%E5%AD%A6%E4%B9%A0/"/>
<id>https://www.sekyoro.top/2024/11/18/vqvae%E5%8F%8A%E5%85%B6%E5%8F%98%E4%BD%93%E4%BB%A3%E7%A0%81%E5%AD%A6%E4%B9%A0/</id>
<published>2024-11-18T10:13:55.000Z</published>
<updated>2024-11-22T14:58:48.324Z</updated>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="\assets\js\Meting.min.js"></script><p>vqvae出自<a href="https://arxiv.org/abs/1711.00937">[1711.00937] Neural Discrete Representation Learning</a>,用于无监督学习离散表征,目前在多模态生成领域还有使用. 这里学习一下代码</p><span id="more"></span><h2 id="VQVAE"><a href="#VQVAE" class="headerlink" title="VQVAE"></a>VQVAE</h2><p>vqvae道理本身很简单,它的提出与pixelcnn、自回归模型息息相关,像vae,gan这种生成式模型,它们更像是对整个数据进行估计,而自回归模型又与序列模型相关,更像是对数据生成分布的建模</p><blockquote><p>自回归模型以序列中的先前值为条件进行预测,而不是基于潜在随机变量。因此,他们试图对数据生成分布进行显式建模,而不是对其进行近似</p></blockquote><p>poixelcnn就是一个自回归模型,而其每次就是从vqvae得到的离散结果中进行采样序列性地生成结果,为了实现这种效果利用了一种masked convolution,将卷积权重后面部分置0,使得在卷积的时候不关注后面的结果<a href="https://github.com/pilipolio/learn-pytorch/blob/master/201708_ToyPixelCNN.ipynb">ToyPixelCNN.ipynb at master · pilipolio/learn-pytorch</a></p><p><img data-src="https://camo.githubusercontent.com/2b432c6d87633c75685c3703167c0a6b5a6d6592a7ca95540bf02f6de890052c/68747470733a2f2f6c696c69616e77656e672e6769746875622e696f2f6c696c2d6c6f672f6173736574732f696d616765732f706978656c2d636e6e2e706e67" alt="img"></p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">MaskedConv</span>(<span class="params">nn.Conv2d</span>):</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">__init__</span>(<span class="params">self, mask_type, *args, **kwargs</span>):</span></span><br><span class="line"><span class="built_in">super</span>(MaskedConv, self).__init__(*args, **kwargs)</span><br><span class="line">self.mask_type = mask_type</span><br><span class="line">self.register_buffer(<span class="string">'mask'</span>, self.weight.data.clone())</span><br><span class="line"></span><br><span class="line">channels, depth, height, width = self.weight.size()</span><br><span class="line"></span><br><span class="line">self.mask.fill_(<span class="number">1</span>)</span><br><span class="line"><span class="keyword">if</span> mask_type ==<span class="string">'A'</span>:</span><br><span class="line">self.mask[:,:,height//<span class="number">2</span>,width//<span class="number">2</span>:] = <span class="number">0</span></span><br><span class="line">self.mask[:,:,height//<span class="number">2</span>+<span class="number">1</span>:,:] = <span class="number">0</span></span><br><span class="line"><span class="keyword">else</span>: </span><br><span class="line">self.mask[:,:,height//<span class="number">2</span>,width//<span class="number">2</span>+<span class="number">1</span>:] = <span class="number">0</span></span><br><span class="line">self.mask[:,:,height//<span class="number">2</span>+<span class="number">1</span>:,:] = <span class="number">0</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">forward</span>(<span class="params">self, x</span>):</span></span><br><span class="line">self.weight.data *= self.mask </span><br><span class="line"><span class="keyword">return</span> <span class="built_in">super</span>(MaskedConv, self).forward(x)</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>现在许多的模型,包括transformer都是auto-regressive的,而GAN与VAE并不是,它们的缺点就是难以建模离散数据.而vqvae就弥补了这一点.</p><p>而VQVAE中重点其实是设计好一个离散字典后,使用了一种技巧将梯度传导使得能够更新这个字典.</p><p>这种设计称作直通估计器,将decoder得到的梯度直接传到了encoder.假设codebook的shape是[codebook_size,codebook_dim],输入特征shape是[size,codebook_dim],通过一个指标得到它们的距离(可以使用<code>torch.cdist</code>)得到[size,codebook_size],这相当于得到了特征上每个位置在字典上对应的位置.</p><p><img data-src="https://notesbylex.com/_media/vq.png" alt="Vector Quantisation"></p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 写法1</span></span><br><span class="line">dist_manual = torch.sqrt(</span><br><span class="line"> torch.<span class="built_in">sum</span>(x ** <span class="number">2</span>, dim=<span class="number">1</span>, keepdim=<span class="literal">True</span>) +</span><br><span class="line"> torch.<span class="built_in">sum</span>(y ** <span class="number">2</span>, dim=<span class="number">1</span>, keepdim=<span class="literal">True</span>).t() -</span><br><span class="line"> <span class="number">2</span> * x @ y.t()</span><br><span class="line"> )</span><br><span class="line"><span class="comment"># 写法2 better readable and efficient since no gradient computation</span></span><br><span class="line"> <span class="keyword">with</span> torch.no_grad():</span><br><span class="line"> dist = torch.cdist(x, implicit_codebook)</span><br><span class="line"> indices = dist.argmin(dim = -<span class="number">1</span>)</span><br></pre></td></tr></table></figure><p>根据最近的距离得到嵌入后的特征</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 写法1 </span></span><br><span class="line">min_encoding_indices = torch.argmin(d, dim=<span class="number">1</span>).unsqueeze(<span class="number">1</span>) <span class="comment"># (encoded_feat size,1)</span></span><br><span class="line"> min_encodings = torch.zeros(</span><br><span class="line"> min_encoding_indices.shape[<span class="number">0</span>], self.n_e, device=z.device) <span class="comment"># (encoded_feat size,embedding_size)</span></span><br><span class="line"> min_encodings.scatter_(<span class="number">1</span>, min_encoding_indices, <span class="number">1</span>) <span class="comment"># one-hot like</span></span><br><span class="line"><span class="comment"># 写法2 dry and more clean</span></span><br><span class="line">min_encoding_indices = torch.argmin(d, dim=<span class="number">1</span>)</span><br><span class="line"> my_min_encodings = F.one_hot(min_encoding_indices.squeeze())</span><br></pre></td></tr></table></figure><p><code>one-hot</code>的shape是[encode_size,embed_size],下面公式中第三项是commitment loss,用于更新encoder输出,第三项用于更新字典</p><blockquote><p>为了学习嵌入空间,使用最简单的字典学习算法之一,向量量化( VQ )。VQ目标使用l2误差将嵌入向量ei移动到编码器输出ze ( x )</p></blockquote><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">z_q = torch.matmul(min_encodings, self.embedding.weight).view(z.shape)</span><br><span class="line"> loss = self.beta * torch.mean((z_q - z.detach()) ** <span class="number">2</span>))+torch.mean(((z_q.detach() - z) ** <span class="number">2</span>)</span><br><span class="line"> z_q = z + (z_q - z).detach()</span><br><span class="line"> <span class="comment"># torch.mean((z_q-z.detach())**2) 可以更简单地写为</span></span><br><span class="line"> F.mse_loss(z_q,z_e.detach())</span><br></pre></td></tr></table></figure><p><img data-src="https://s2.loli.net/2024/11/19/1EByprM63fZXdON.png" alt="image-20241119192654741"></p><p>此外可以使用EMA更新字典</p><p><img data-src="https://s2.loli.net/2024/11/19/u2JyvB6W59j3PHC.png" alt="image-20241119202801862"></p><p>这里的更新逻辑是,每次更新ema_cluster_size,针对每个嵌入的向量,得到与它最近的特征向量个数,通过ema更新,而权重就是每次嵌入的值通过ema更新</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># Update weights with EMA</span></span><br><span class="line"> <span class="keyword">if</span> self.training:</span><br><span class="line"> self._ema_cluster_size = self._ema_cluster_size * self._decay + (</span><br><span class="line"> <span class="number">1</span> - self._decay</span><br><span class="line"> ) * torch.<span class="built_in">sum</span>(encodings, <span class="number">0</span>)</span><br><span class="line"></span><br><span class="line"> <span class="comment"># Laplace smoothing</span></span><br><span class="line"> n = torch.<span class="built_in">sum</span>(self._ema_cluster_size.data)</span><br><span class="line"> self._ema_cluster_size = (</span><br><span class="line"> (self._ema_cluster_size + self._epsilon)</span><br><span class="line"> / (n + self._n_embeddings * self._epsilon)</span><br><span class="line"> * n</span><br><span class="line"> )</span><br><span class="line"></span><br><span class="line"> dw = torch.matmul(encodings.t(), flat_z_e)</span><br><span class="line"> self._ema_w = nn.Parameter(</span><br><span class="line"> self._ema_w * self._decay + (<span class="number">1</span> - self._decay) * dw</span><br><span class="line"> )</span><br><span class="line"></span><br><span class="line"> self._embedding.weight = nn.Parameter(</span><br><span class="line"> self._ema_w / self._ema_cluster_size.unsqueeze(<span class="number">1</span>)</span><br><span class="line"> )</span><br><span class="line"></span><br></pre></td></tr></table></figure><p><img data-src="https://s2.loli.net/2024/11/19/LjYv1nK2odfCQe6.png" alt="image-20241119222201269"></p><h2 id="VQVAE-2"><a href="#VQVAE-2" class="headerlink" title="VQVAE-2"></a>VQVAE-2</h2><p>简单来说就是多尺度的vqvae,设计了多个encoder-codelayer-decoder.<br>首先特征通过多个encoder降维,得到不同尺度的特征,再将不同尺度特征进行quantize,quantize后得到的特征进行上采样再decoder最终得到多尺度特征. </p><p>此外也有VQGAN论文在多尺度的基础上提出将codebook的维度从256到32,重建效果保持一致,同时将解码后的特征与codebook做l2-norm,使用cos相似度判断</p><h2 id="Residual-VQ"><a href="#Residual-VQ" class="headerlink" title="Residual VQ"></a>Residual VQ</h2><p>道理非常简单——quantize(x-quantize(x-quantize(x-…)))</p><p><img data-src="https://notesbylex.com/_media/vq-for-audio.png" alt="Vector Quantisation for Audio"></p><p><img data-src="https://notesbylex.com/_media/residual-vector-quantization-fig-2%201.png" alt="SoundStream architecture"></p><h2 id="SIMVQ"><a href="#SIMVQ" class="headerlink" title="SIMVQ"></a>SIMVQ</h2><p><img data-src="https://s2.loli.net/2024/11/19/O2APLos7bE9Fdgf.png" alt="image-20241119222219261"></p><p>据论文作者所说,在codebook上进行维度转换,提高编码表的利用率,使得在许多优化器上表现更好. 在具体代码上,我参考了<a href="https://github.com/lucidrains/vector-quantize-pytorch/tree/master">lucidrains/vector-quantize-pytorch: Vector (and Scalar) Quantization, in Pytorch</a>的实现,其使用一个linear层变换codebook的维度,在进行计算距离时也使用这个转换后的codebook,量化也使用这个codebook,这样一来特征经过encoder后的维度需要与转换后的codebook的维度一致.</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br></pre></td><td class="code"><pre><span class="line"> <span class="keyword">return</span> inverse(rotated)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">SimVQ</span>(<span class="params">nn.Module</span>):</span></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">__init__</span>(<span class="params"></span></span></span><br><span class="line"><span class="params"><span class="function"> self,</span></span></span><br><span class="line"><span class="params"><span class="function"> dim,</span></span></span><br><span class="line"><span class="params"><span class="function"> codebook_size,</span></span></span><br><span class="line"><span class="params"><span class="function"> codebook_transform: Module | <span class="literal">None</span> = <span class="literal">None</span>,</span></span></span><br><span class="line"><span class="params"><span class="function"> init_fn: <span class="type">Callable</span> = identity,</span></span></span><br><span class="line"><span class="params"><span class="function"> channel_first=<span class="literal">False</span>,</span></span></span><br><span class="line"><span class="params"><span class="function"> rotation_trick=<span class="literal">True</span>,</span></span></span><br><span class="line"><span class="params"><span class="function"> input_to_quantize_commit_loss_weight=<span class="number">.25</span>,</span></span></span><br><span class="line"><span class="params"><span class="function"> commitment_weight=<span class="number">1.</span>,</span></span></span><br><span class="line"><span class="params"><span class="function"> frozen_codebook_dim=<span class="literal">None</span>,</span></span></span><br><span class="line"><span class="params"><span class="function"> </span>):</span></span><br><span class="line"> <span class="built_in">super</span>().__init__()</span><br><span class="line"> self.codebook_size = codebook_size</span><br><span class="line"> self.channel_first = channel_first</span><br><span class="line"></span><br><span class="line"> frozen_codebook_dim = default(frozen_codebook_dim, dim)</span><br><span class="line"> codebook = torch.randn(codebook_size, frozen_codebook_dim) * (frozen_codebook_dim ** -<span class="number">.5</span>)</span><br><span class="line"></span><br><span class="line"> codebook = init_fn(codebook)</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> <span class="keyword">not</span> exists(codebook_transform):</span><br><span class="line"> codebook_transform = nn.Linear(frozen_codebook_dim, dim, bias=<span class="literal">False</span>)</span><br><span class="line"> self.code_transform = codebook_transform</span><br><span class="line"> self.register_buffer(<span class="string">"frozen_codebook"</span>, codebook)</span><br><span class="line"></span><br><span class="line"> self.rotation_trick = rotation_trick</span><br><span class="line"> self.input_to_quantize_commit_loss_weight = input_to_quantize_commit_loss_weight</span><br><span class="line"> self.commitment_weight = commitment_weight</span><br><span class="line"></span><br><span class="line"><span class="meta"> @property</span></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">codebook</span>(<span class="params">self</span>):</span></span><br><span class="line"> <span class="keyword">return</span> self.code_transform(self.frozen_codebook)</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">indices_to_codes</span>(<span class="params">self, indices</span>):</span></span><br><span class="line"> frozen_codes = self.frozen_codebook(indices)</span><br><span class="line"> quantized = self.code_transform(frozen_codes)</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> self.channel_first:</span><br><span class="line"> quantized = rearrange(quantized, <span class="string">'b ... d -> b d ...'</span>)</span><br><span class="line"> <span class="keyword">return</span> quantized</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">forward</span>(<span class="params">self, x</span>):</span></span><br><span class="line"> <span class="keyword">if</span> self.channel_first:</span><br><span class="line"> x = rearrange(x, <span class="string">'b ... d -> b d ...'</span>)</span><br><span class="line"> x, inverse_pack = pack_one(x, <span class="string">'b * d'</span>)</span><br><span class="line"> implicit_codebook = self.codebook</span><br><span class="line"> <span class="keyword">with</span> torch.no_grad():</span><br><span class="line"> dist = torch.cdist(x, implicit_codebook)</span><br><span class="line"> indices = dist.argmin(dim=-<span class="number">1</span>)</span><br><span class="line"></span><br><span class="line"> quantized = implicit_codebook[indices]</span><br><span class="line"> commit_loss = F.mse_loss(x.detach(), quantized)</span><br><span class="line"> <span class="keyword">if</span> self.rotation_trick:</span><br><span class="line"> quantized = rotate_to(x, quantized)</span><br><span class="line"> <span class="keyword">else</span>:</span><br><span class="line"> commit_loss = (commit_loss + F.mse_loss(x, quantized.detach()) * self.input_to_quantize_commit_loss_weight)</span><br><span class="line"> quantized = (quantized - x).detach() + x</span><br><span class="line"> quantized = inverse_pack(quantized)</span><br><span class="line"> indices = inverse_pack(indices, <span class="string">'b *'</span>)</span><br><span class="line"> <span class="keyword">if</span> self.channel_first:</span><br><span class="line"> quantized = rearrange(quantized, <span class="string">'b ... d-> b d...'</span>)</span><br><span class="line"> <span class="keyword">return</span> quantized, indices, commit_loss * self.commitment_weight</span><br></pre></td></tr></table></figure><p>可以看到上面代码中经常用到einops和einx以及torch的einsum操作,这些都是非常方便的库或者函数.这里介绍一下</p><h2 id="einops中常用操作"><a href="#einops中常用操作" class="headerlink" title="einops中常用操作"></a>einops中常用操作</h2><p><img data-src="https://s2.loli.net/2024/11/19/qWTtJIihjCaeVk9.png" alt="image-20241119201935141"></p><h3 id="rearrange"><a href="#rearrange" class="headerlink" title="rearrange"></a>rearrange</h3><p>最常用的就是rearrange了,可以用来转换axis的顺序,composition,decomposition等</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">x = torch.randn(<span class="number">10</span>,<span class="number">20</span>,<span class="number">10</span>,<span class="number">10</span>)</span><br><span class="line"><span class="comment"># order</span></span><br><span class="line">y = rearrange(x,<span class="string">'b c h w -> b h w c'</span>)</span><br><span class="line"><span class="built_in">print</span>(y.shape)</span><br><span class="line"><span class="comment"># composition</span></span><br><span class="line">y = rearrange(x,<span class="string">'b c h w -> b c (h w)'</span>)</span><br><span class="line"><span class="comment"># decomposition</span></span><br><span class="line">y = rearrange(y,<span class="string">'b c (h w) -> b h w c'</span>)</span><br><span class="line">y = rearrange(y,<span class="string">'(b1 b2) h w c -> b1 b2 h w c'</span>,b1=<span class="number">2</span>)</span><br></pre></td></tr></table></figure><h3 id="reduce"><a href="#reduce" class="headerlink" title="reduce"></a>reduce</h3><figure class="highlight apache"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># yet another example. Can you compute result shape?</span></span><br><span class="line"><span class="attribute">reduce</span>(ims, <span class="string">"(b1 b2) h w c -> (b2 h) (b1 w)"</span>, <span class="string">"mean"</span>, b<span class="number">1</span>=<span class="number">2</span>)</span><br></pre></td></tr></table></figure><p>可以用于求均值,maxpooling等,</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">ims = torch.randn((<span class="number">10</span>,<span class="number">20</span>,<span class="number">30</span>,<span class="number">30</span>))*<span class="number">10</span>-<span class="number">2</span></span><br><span class="line">b,c,h,w = ims.shape</span><br><span class="line">m_ims = reduce(ims,<span class="string">'b c h w -> b c'</span>,<span class="string">"min"</span>)</span><br><span class="line"><span class="built_in">print</span>(m_ims.shape)</span><br><span class="line"></span><br><span class="line">m_ims = reduce(ims,<span class="string">'b c h w -> b (h w) c'</span>,<span class="string">'min'</span>).transpose(<span class="number">1</span>,<span class="number">2</span>).reshape(b,c,h,w)</span><br><span class="line"><span class="built_in">print</span>(m_ims.shape)</span><br><span class="line"><span class="built_in">print</span>(ims == m_ims)</span><br><span class="line">min2_ims = reduce(ims,<span class="string">'b c (h h2) (w w2) -> b c h w'</span>,<span class="string">'mean'</span>,h2=<span class="number">2</span>,w2=<span class="number">2</span>)</span><br><span class="line">reduce(ims,<span class="string">'b (h h2) (w w2) c -> h (b w) c'</span>,<span class="string">"max"</span>,h2=<span class="number">2</span>,w2=<span class="number">2</span>)</span><br></pre></td></tr></table></figure><p>通过使用<code>()</code>保持dim,或者也可以使用<code>1</code></p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">data = torch.randn(<span class="number">10</span>,<span class="number">20</span>,<span class="number">30</span>,<span class="number">40</span>)</span><br><span class="line">mean_ = reduce(data,<span class="string">'b c h w -> b c () ()'</span>,<span class="string">'mean'</span>) <span class="comment"># 求均值</span></span><br><span class="line">ans = data.mean(dim=[<span class="number">2</span>,<span class="number">3</span>],keepdim=<span class="literal">True</span>)</span><br><span class="line"><span class="built_in">print</span>((((ans-mean_)<<span class="number">1e-6</span>).<span class="built_in">float</span>()).mean())</span><br><span class="line"></span><br><span class="line">max_pool = reduce(data,<span class="string">'b c (2 h) (2 w) -> b c h w'</span>,<span class="string">'max'</span>) <span class="comment">#max pooling</span></span><br><span class="line">adaptive_max_pool = reduce(data,<span class="string">'b c h w -> b c ()'</span>,<span class="string">'max'</span>)</span><br></pre></td></tr></table></figure><h3 id="stack-and-concatenation"><a href="#stack-and-concatenation" class="headerlink" title="stack and concatenation"></a>stack and concatenation</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># rearrange can also take care of lists of arrays with the same shape</span></span><br><span class="line">x = <span class="built_in">list</span>(ims)</span><br><span class="line"><span class="built_in">print</span>(<span class="built_in">type</span>(x), <span class="string">"with"</span>, <span class="built_in">len</span>(x), <span class="string">"tensors of shape"</span>, x[<span class="number">0</span>].shape)</span><br><span class="line"><span class="comment"># that's how we can stack inputs</span></span><br><span class="line"><span class="comment"># "list axis" becomes first ("b" in this case), and we left it there</span></span><br><span class="line">rearrange(x, <span class="string">"b h w c -> b h w c"</span>).shape</span><br></pre></td></tr></table></figure><p>将一个列表的tensor中的列表大小维度进行转换</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">c = <span class="built_in">list</span>()</span><br><span class="line">c.append(torch.randn(<span class="number">10</span>,<span class="number">20</span>,<span class="number">30</span>))</span><br><span class="line">c.append(torch.randn(<span class="number">10</span>,<span class="number">20</span>,<span class="number">30</span>))</span><br><span class="line"></span><br><span class="line">rearrange(c,<span class="string">'l c h w -> c l h w'</span>).shape</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>或者求一个列表中的所有tensor和、max等</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">c = <span class="built_in">list</span>()</span><br><span class="line">c.append(torch.randn(<span class="number">10</span>,<span class="number">20</span>,<span class="number">30</span>))</span><br><span class="line">c.append(torch.randn(<span class="number">10</span>,<span class="number">20</span>,<span class="number">30</span>))</span><br><span class="line"></span><br><span class="line">rearrange(c,<span class="string">'l c h w -> c l h w'</span>).shape</span><br><span class="line">reduce(c,<span class="string">'c l h w -> l h w'</span>,<span class="string">'mean'</span>).shape</span><br><span class="line">reduce(c,<span class="string">'c l h w -> l h w'</span>,<span class="string">'sum'</span>).shape</span><br><span class="line">reduce(c,<span class="string">'c l h w -> l h w'</span>,<span class="string">'max'</span>).shape</span><br></pre></td></tr></table></figure><h3 id="add-or-remove-axis"><a href="#add-or-remove-axis" class="headerlink" title="add or remove axis"></a>add or remove axis</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">x = rearrange(x,<span class="string">'b h w c -> b 1 h w 1 c'</span>)</span><br><span class="line">y = rearrange(y,<span class="string">'b h w c - b h (w c)'</span>)</span><br></pre></td></tr></table></figure><h3 id="channel-shuffle"><a href="#channel-shuffle" class="headerlink" title="channel shuffle"></a>channel shuffle</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">c = torch.randn(<span class="number">10</span>,<span class="number">30</span>,<span class="number">10</span>,<span class="number">10</span>)</span><br><span class="line">rearrange(c,<span class="string">'b (g1 g2 c) h w -> b (g2 g1 c) h w'</span>,g1=<span class="number">3</span>,g2=<span class="number">5</span>).shape</span><br></pre></td></tr></table></figure><h3 id="repeat"><a href="#repeat" class="headerlink" title="repeat"></a>repeat</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">repeat(x,<span class="string">'b h w c -> b (h 2) (w 2) c'</span>)</span><br><span class="line">repeat(x,<span class="string">'h w c -> h new_axis w c'</span>,new_axis=<span class="number">5</span>)</span><br><span class="line">repeat(x,<span class="string">'h w c -> h 5 w c'</span>)</span><br></pre></td></tr></table></figure><h3 id="split-dimension"><a href="#split-dimension" class="headerlink" title="split dimension"></a>split dimension</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">c = torch.randn(<span class="number">10</span>,<span class="number">30</span>,<span class="number">10</span>,<span class="number">10</span>)</span><br><span class="line">x,y,z = rearrange(c,<span class="string">'b (head c) h w -> head b c h w'</span>,head=<span class="number">3</span>)</span><br><span class="line"><span class="built_in">print</span>(x.shape,y.shape,z.shape)</span><br></pre></td></tr></table></figure><p>split有不同方法</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">y1, y2 = rearrange(x, <span class="string">'b (split c) h w -> split b c h w'</span>, split=<span class="number">2</span>)</span><br><span class="line">result = y2 * sigmoid(y2) <span class="comment"># or tanh</span></span><br><span class="line">y1, y2 = rearrange(x, <span class="string">'b (c split) h w -> split b c h w'</span>, split=<span class="number">2</span>)</span><br></pre></td></tr></table></figure><ul><li><code>y1 = x[:, :x.shape[1] // 2, :, :]</code></li><li><code>y1 = x[:, 0::2, :, :]</code></li></ul><h3 id="striding-anything"><a href="#striding-anything" class="headerlink" title="striding anything"></a>striding anything</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># each image is split into subgrids, each subgrid now is a separate "image"</span></span><br><span class="line">y = rearrange(x, <span class="string">"b c (h hs) (w ws) -> (hs ws b) c h w"</span>, hs=<span class="number">2</span>, ws=<span class="number">2</span>)</span><br><span class="line">y = convolve_2d(y)</span><br><span class="line"><span class="comment"># pack subgrids back to an image</span></span><br><span class="line">y = rearrange(y, <span class="string">"(hs ws b) c h w -> b c (h hs) (w ws)"</span>, hs=<span class="number">2</span>, ws=<span class="number">2</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">assert</span> y.shape == x.shape</span><br></pre></td></tr></table></figure><p>可以看到最常用的函数就是<code>rearrange</code>,<code>reduce</code>以及<code>repeat</code>,基本替代了原本的<code>sum</code>,<code>transpose</code>,<code>expand</code>,<code>reshape</code>等torch操作</p><h3 id="parse-shape"><a href="#parse-shape" class="headerlink" title="parse_shape"></a>parse_shape</h3><p>通过<code>parse_shape</code>,相当于更方便地获得了需要的维度大小</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">y = np.zeros([<span class="number">700</span>])</span><br><span class="line">rearrange(y, <span class="string">'(b c h w) -> b c h w'</span>, **parse_shape(x, <span class="string">'b _ h w'</span>)).shape</span><br></pre></td></tr></table></figure><h3 id="pack-and-unpack"><a href="#pack-and-unpack" class="headerlink" title="pack and unpack"></a>pack and unpack</h3><p>pack是将一些列数据中的的一些维度放在一起</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">h,w = <span class="number">100</span>,<span class="number">200</span></span><br><span class="line"><span class="keyword">import</span> numpy <span class="keyword">as</span> np</span><br><span class="line">img_rgb = np.random.random([h,w,<span class="number">3</span>])</span><br><span class="line">img_depth = np.random.random([h,w])</span><br><span class="line">img_rgbd,ps = pack([img_rgb,img_depth],<span class="string">'h w *'</span>)</span><br><span class="line"><span class="built_in">print</span>(img_rgbd.shape,ps)</span><br></pre></td></tr></table></figure><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">unpacked_rgb,unpacked_depth = unpack(img_rgbd,ps,<span class="string">"h w *"</span>)</span><br><span class="line"><span class="built_in">print</span>(unpacked_rgb.shape,unpacked_depth.shape)</span><br></pre></td></tr></table></figure><h3 id="结合torch使用layers"><a href="#结合torch使用layers" class="headerlink" title="结合torch使用layers"></a>结合torch使用layers</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> einops.layers.torch <span class="keyword">import</span> Rearrange,Reduce</span><br></pre></td></tr></table></figure><h3 id="Einx"><a href="#Einx" class="headerlink" title="Einx"></a>Einx</h3><p>一种类似<code>torch.einsum</code>的计算方式,einsum<a href="https://rockt.github.io/2018/04/30/einsum">einsum tutorial</a>是一种方便计算多个tensor乘积的方式,而Einx方便了写MLP-based架构代码,通过weight_shape和bias_shape结合pattern构造mlp</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> einops.layers.torch <span class="keyword">import</span> EinMix <span class="keyword">as</span> Mix</span><br><span class="line">mlp = Mix(<span class="string">'t b c-> t b c_out'</span>,weight_shape=<span class="string">'c c_out'</span>,c=<span class="number">10</span>,c_out=<span class="number">20</span>)</span><br><span class="line">x = torch.randn(<span class="number">10</span>,<span class="number">30</span>,<span class="number">10</span>)</span><br><span class="line">y = mlp(x)</span><br></pre></td></tr></table></figure><p>值得一提的是,einops也有einsum</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> einops <span class="keyword">import</span> einsum, pack, unpack</span><br><span class="line"><span class="comment"># einsum is like ... einsum, generic and flexible dot-product</span></span><br><span class="line"><span class="comment"># but 1) axes can be multi-lettered 2) pattern goes last 3) works with multiple frameworks</span></span><br><span class="line">C = einsum(A, B, <span class="string">'b t1 head c, b t2 head c -> b head t1 t2'</span>)</span><br></pre></td></tr></table></figure><h2 id="相关资料"><a href="#相关资料" class="headerlink" title="相关资料"></a>相关资料</h2><ol><li><a href="https://github.com/MishaLaskin/vqvae">MishaLaskin/vqvae: A pytorch implementation of the vector quantized variational autoencoder (https://arxiv.org/abs/1711.00937)</a></li><li><a href="https://github.com/nadavbh12/VQ-VAE/blob/master/vq_vae/auto_encoder.py">VQ-VAE/vq_vae/auto_encoder.py at master · nadavbh12/VQ-VAE</a></li><li><a href="https://github.com/AndrewBoessen/VQ-VAE/blob/main/vqvae.py">VQ-VAE/vqvae.py at main · AndrewBoessen/VQ-VAE</a></li><li><a href="https://github.com/vvvm23/vqvae-2/blob/main/vqvae.py">vqvae-2/vqvae.py at main · vvvm23/vqvae-2</a></li><li><a href="https://www.georgeho.org/deep-autoregressive-models/">Autoregressive Models in Deep Learning — A Brief Survey | George Ho</a></li><li><a href="https://github.com/lucidrains/vector-quantize-pytorch/tree/master">lucidrains/vector-quantize-pytorch: Vector (and Scalar) Quantization, in Pytorch</a></li><li><a href="https://spaces.ac.cn/archives/6760">VQ-VAE的简明介绍:量子化自编码器 - 科学空间|Scientific Spaces</a></li><li><a href="https://spaces.ac.cn/archives/10489">VQ的旋转技巧:梯度直通估计的一般推广 - 科学空间|Scientific Spaces</a></li><li><a href="https://spaces.ac.cn/archives/10519">VQ的又一技巧:给编码表加一个线性变换 - 科学空间|Scientific Spaces</a></li><li><a href="https://einops.rocks/pytorch-examples.html">Writing better code with pytorch+einops</a></li><li><a href="https://notesbylex.com/residual-vector-quantisation">Residual Vector Quantisation - Notes by Lex</a></li><li><a href="https://github.com/rese1f/awesome-VQVAE?tab=readme-ov-file">rese1f/Awesome-VQVAE: A collection of resources and papers on Vector Quantized Variational Autoencoder (VQ-VAE) and its application</a></li></ol><link rel="stylesheet" href="/css/spoiler.css" type="text/css"><script src="/js/spoiler.js" type="text/javascript" async></script>]]></content>
<summary type="html"><p>vqvae出自<a href="https://arxiv.org/abs/1711.00937">[1711.00937] Neural Discrete Representation Learning</a>,用于无监督学习离散表征,目前在多模态生成领域还有使用. 这里学习一下代码</p></summary>
<category term="deep learning" scheme="https://www.sekyoro.top/tags/deep-learning/"/>
<category term="vqvae" scheme="https://www.sekyoro.top/tags/vqvae/"/>
</entry>
<entry>
<title>文生图相关模型最新进展小结</title>
<link href="https://www.sekyoro.top/2024/11/03/%E6%96%87%E7%94%9F%E5%9B%BE%E7%9B%B8%E5%85%B3%E6%A8%A1%E5%9E%8B%E6%9C%80%E6%96%B0%E8%BF%9B%E5%B1%95%E5%B0%8F%E7%BB%93/"/>
<id>https://www.sekyoro.top/2024/11/03/%E6%96%87%E7%94%9F%E5%9B%BE%E7%9B%B8%E5%85%B3%E6%A8%A1%E5%9E%8B%E6%9C%80%E6%96%B0%E8%BF%9B%E5%B1%95%E5%B0%8F%E7%BB%93/</id>
<published>2024-11-03T11:47:36.000Z</published>
<updated>2024-11-13T02:41:33.481Z</updated>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="\assets\js\Meting.min.js"></script><p>在较早的VAE和GAN时代,通过图形生成模型,可能出现模式坍塌(个人理解就是生成图像多样性不足)并且只有图像数据参与,在之后的diffusion时代(包括现在),有了多模态的加入,通过文本、深度图以及骨骼图(e.g.ControlNet)控制图像生成有了更高的自由度和创造性.此外有了类似LoRA,IP-Adapter等等技术提供了更好的微调方式用于在更新模型权重保持良好的风格迁移(比如原本模型训练集中没有的人物,在微调时增加新人物训练能有良好表现)或人物一致性(不同的角色不至于长得一样)</p><p>而在最近有了更多在一些较大模型(stable diffusionv3,novelai,sdxl等)上微调的模型,它们更加专注于某种画风。此外本文尝试总结目前文生图、艺术图片等生成式AI应用,最后从图片展望生成式视频创作。相对于论文、原理,这里更注重模型与应用。</p><span id="more"></span><p>之前我介绍过生成式模型与相关技术,这里不作过多介绍。</p><h2 id="模型与技术的进展"><a href="#模型与技术的进展" class="headerlink" title="模型与技术的进展"></a>模型与技术的进展</h2><p>首先,我们需要一个较大的模型,这个模型在较大的数据集上经过充分训练,已经有了相应的”知识”.作为普通用户一般不去训练这样的模型,直接下载即可<a href="https://huggingface.co/models?pipeline_tag=text-to-image&sort=downloads">Models - Hugging Face</a>. 这类模型的技术从较早的VAE,flow-based,GAN到目前处于统治地位的diffusion. 可以说目前依然是diffusion的天下,基于diffusion的常用模型包括stability(或者说compvis,stability公司争议很大)下的stable diffusion,目前到了SDXL和v3.5,<a href="https://novelai.net/">NovelAI - The AI Storyteller</a>下的<a href="https://docs.novelai.net/image/models.html#novelai-diffusion-anime-v3">NovelAI Diffusion Anime V3</a>(之前<a href="https://rentry.org/sdg_FAQ">泄露过novelai早期模型</a>),但novelai本身并没有开源模型,此外还有midjourney等,但这些大都需要付费而且定制化不高大概率也不能<a href="https://zh.wikipedia.org/zh-hans/NSFW">NSFW</a>.</p><p>所以,从目前看来开源的底模(也就是上面所说的diffusion较大的模型)看起来只有stability下的对用户友好了,因为开源,社区也很给力,有了很多相关工具.</p><p>目前来看<a href="https://huggingface.co/models?pipeline_tag=text-to-image&sort=downloads">Models - Hugging Face</a>,<a href="https://civitai.com/">Civitai: The Home of Open-Source Generative AI</a>以及<a href="https://rentry.co/stablediffusion">Stable Diffusion Rentries + Simple Installation Tutorial!</a>是较好的模型下载和学习资源. </p><p>此外也有<a href="https://www.reddit.com/r/StableDiffusion/wiki/index/">Reddit</a>社区,x上的相关社区以及一些相关discord作为被动看看新闻和高手们在干什么.</p><p>在两三年前,用的比较多的是stable diffusionv1.5(目前被runway删了,但网上还是有很多备份),后来有了sdxl,sdv3以及现在的sdv3.5</p><p><img data-src="https://s2.loli.net/2024/11/04/5WEsn1PwASLDbce.png" alt="image-20241104213845495"></p><p>而目前最新的就是3.5版本和sdxl,往往我们都需要这些底模. 至于这些底模有什么大差别,我认为主要还是在数据集和模型参数量上,当然如果模型太大,本地可能也不好运行.所以还是量力而行.</p><p>在这些底模基础上,通过微调技术,衍生出了一大堆社区的模型.最早的微调模型基本都是基于sd1.5或早期泄露的nai</p><ul><li><a href="https://civitai.com/models/66/anything-v3">Anything V3 - fp16 | Stable Diffusion Checkpoint | Civitai</a></li><li><a href="https://huggingface.co/gsdf/Counterfeit-V2.0">gsdf/Counterfeit-V2.0 · Hugging Face</a></li><li><a href="https://huggingface.co/hakurei/waifu-diffusion-v1-4">hakurei/waifu-diffusion-v1-4 · Hugging Face</a></li><li><a href="https://civitai.com/models/4201/realistic-vision-v13">Realistic Vision V6.0 B1 - V5.1 Hyper (VAE) | Stable Diffusion Checkpoint | Civitai</a></li></ul><p>可以看到还是专注生成人物的居多</p><p>这里挑选几个目前最火的,大多基于SDXL,除了Flux之外都是社区基于Stable diffusion的开源模型.</p><h3 id="Animagine3"><a href="#Animagine3" class="headerlink" title="Animagine3"></a>Animagine3</h3><p><a href="https://huggingface.co/cagliostrolab/animagine-xl-3.0">cagliostrolab/animagine-xl-3.0 · Hugging Face</a></p><p>基于SDXL</p><h3 id="Pony"><a href="#Pony" class="headerlink" title="Pony"></a>Pony</h3><p><a href="https://civitai.com/models/257749?modelVersionId=290640">Pony Diffusion V6 XL - V6 (start with this one) | Stable Diffusion Checkpoint | Civitai</a></p><p><a href="https://civitai.com/models/288584?modelVersionId=324619">AutismMix SDXL - AutismMix_pony | Stable Diffusion Checkpoint | Civitai</a></p><h3 id="Flux"><a href="#Flux" class="headerlink" title="Flux"></a>Flux</h3><p><a href="https://github.com/black-forest-labs/flux">black-forest-labs/flux: Official inference repo for FLUX.1 models</a></p><p>Stability出走人员新开的公司Black Forest Lab下的开源模型<a href="https://blackforestlabs.io/flux-1/">FLUX.1 - BlackForestLabs</a></p><h3 id="illustrious"><a href="#illustrious" class="headerlink" title="illustrious"></a>illustrious</h3><p><a href="https://civitai.com/models/795765/illustrious-xl">Illustrious-XL - v0.1 | Illustrious Checkpoint | Civitai</a></p><h4 id="Noob"><a href="#Noob" class="headerlink" title="Noob"></a>Noob</h4><p><a href="https://civitai.com/models/833294/noobai-xl-nai-xl">NoobAI-XL (NAI-XL) - Epsilon-pred 1.0-Version | Stable Diffusion XL Checkpoint | Civitai</a></p><p>一些评价<a href="https://tieba.baidu.com/p/9224285639#151086745100l">了解下大家都在用什么版本-百度贴吧</a></p><blockquote><p>对于普通爱好者<br>sd1.5仅有的用处是controlnet的inpaint扩图换衣,缺点是手脚画不好<br>pony是sdxl早期的威力加强版,主打涩图,社区分享r18/r18g最广泛,最优模型是tponynai3/wai等<br>sdxl近一个月推出illustrious力争无限接近nai3,比pony省略角色lora姿势lora画风lora可以单tag直接呼出省略调权步骤色彩污染等,最优模型是tillu3/wai/noobai等<br>flux是画三次元专精,因为模型是拿三维深度图训练的,缺点是显存占用极高</p></blockquote><p>可以常逛civita看看那些些不错的图片使用的什么模型.</p><h2 id="本地使用工具的进展"><a href="#本地使用工具的进展" class="headerlink" title="本地使用工具的进展"></a>本地使用工具的进展</h2><p>目前经常看见的几个本地使用UI工具.</p><ul><li><a href="https://github.com/AUTOMATIC1111/stable-diffusion-webui">Automatic1111 WebUI</a>: (<a href="https://github.com/AUTOMATIC1111/stable-diffusion-webui#installation-and-running">Install Guide</a>|<a href="https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Features">Features Guide</a>) - Most feature-packed browser interface.✨✨✨✨✨</li><li><a href="https://github.com/lllyasviel/Fooocus">lllyasviel/Fooocus: Focus on prompting and generating</a></li><li><a href="https://github.com/lllyasviel/stable-diffusion-webui-forge">lllyasviel/stable-diffusion-webui-forge</a>✨✨✨✨</li><li><a href="https://github.com/comfyanonymous/ComfyUI">ComfyUI</a>: (<a href="https://github.com/comfyanonymous/ComfyUI?tab=readme-ov-file#installing">Installation</a>) - API and back-end with a graph/nodes interface.✨✨✨✨</li><li><a href="https://github.com/mcmonkeyprojects/SwarmUI">SwarmUI</a>: (<a href="https://github.com/mcmonkeyprojects/SwarmUI?tab=readme-ov-file#installing-on-windows">Installation</a>) - Super easy to install but still powerful UI, wraps and extends <a href="https://github.com/comfyanonymous/ComfyUI">ComfyUI</a>.</li></ul><p>此外还有<a href="https://github.com/invoke-ai/InvokeAI">invoke-ai/InvokeAI</a></p><h3 id="LoRA训练"><a href="#LoRA训练" class="headerlink" title="LoRA训练"></a>LoRA训练</h3><p>LoRA本身是一种微调方式,技术上修改了模型权重更新的方式,使得迁移更好. 可以下载一些脚本方便训练</p><ul><li><a href="https://github.com/Nerogar/OneTrainer">Nerogar/OneTrainer: OneTrainer is a one-stop solution for all your stable diffusion training needs.</a></li><li><a href="https://github.com/derrian-distro/LoRA_Easy_Training_Scripts">derrian-distro/LoRA_Easy_Training_Scripts: A UI made in Pyside6 to make training LoRA/LoCon and other LoRA type models in sd-scripts easy</a></li><li><a href="https://github.com/kohya-ss/sd-scripts">kohya-ss/sd-scripts</a></li><li><a href="https://github.com/bmaltais/kohya_ss">bmaltais/kohya_ss</a></li></ul><h2 id="现有应用"><a href="#现有应用" class="headerlink" title="现有应用"></a>现有应用</h2><p>目前文生图的应用虽然多,但是在商业上其实挺失败的(这也许收到了开源的影响),况且如果只是图片,也许还不够,如果结合视频、音频,那么对于辅助内容创作,面向目前广大视频博主还是很不错的.</p><p>如果只谈文生图或者图生图应用,目前大致有三种方向,第一种通过AI生成图片节省成本甚至通过AI噱头赚取更多关注度.具体来说,类似上面视频博主方案,只不过是图文博主,发短文搭配图片,发文章搭配图片,我也看见了有些学术会议的介绍图也使用了AI生成图. 这种方法商业上也是通过快速拿到还不错的搭配的图片吸引用户引流. 还有一些卖家,比如卖衣服的,不花请模特的钱,直接通过AI生成而且目前也能做到一个人物只换衣服面貌身材类似,相当于有了一个看起来很真实的人物的多种换衣图. 此外一些游戏开发者生成AI像素图,三视图等快速获得了游戏素材,比如之前的幻兽帕鲁. 上面这些方式都是通过AI辅助原本就有相关技能并以此赚钱的. 第二种更偏向兴趣,比如<a href="https://anifusion.ai/">Anifusion - AI Manga Generator</a>使用了画风更偏向漫画的模型搭配画图工作流,让画漫画更方便,但是专业的漫画家可能就不会去用了.</p><p>关于目前一些有趣的开源应用<a href="https://github.com/guoyww/animatediff/">AnimateDiff.</a>将文生图模型直接转为生成动画的模型而不需要额外训练,<a href="https://github.com/Doubiiu/ToonCrafter">ToonCrafter</a>通过两张图插值生成动画等等都是目前非常有趣的产品.</p><p>总的来说,目前商业应用前景依然不明朗,但是在一些小赛道还是不错的,其实不止AI图片,乃至AI相关的比如LLM也仍然面临这类问题.</p><h2 id="从图片到视频的展望"><a href="#从图片到视频的展望" class="headerlink" title="从图片到视频的展望"></a>从图片到视频的展望</h2><p>有了图片还是不够,如果有连续的、能体现更多故事的图片更好,那就是视频了. stability公司早已发布了<a href="https://stability.ai/stable-video">Stable Video</a>模型,runway也有了<a href="https://runwayml.com/research/gen-2">Runway Research | Gen-2: Generate novel videos with text, images or video clips</a>,OpenAI的Sora也是上了国内的大众热搜,鬼畜视频常客<a href="https://www.heygen.com/">HeyGen - AI Video Generator</a>和<a href="https://www.genmo.ai/">Genmo | Open Video Generation</a>. 但是目前仍然存在一些问题,一个是许多只允许用于调用而且还收费,通常免费生成的视频也就按秒算,由于视频生成难度更高,训练要求更高,相关开源社区发展不够. 另外目前技术也有进步空间,连贯性和人物一致性还需要进步,往往也缺少一些基本逻辑. 比如人物多手多腿,突然窜出一个莫名其妙的生物,脸部变形等等.</p><p>当然,到最后依然可能面临着商业化的问题. 但是考虑到目前互联网上有许多视频博主,他们应该是一个好的目标对象,结合AI生成的文字、音频、图片以及视频开发一个内容创作平台看起来还是有前景的.</p><h2 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h2><ol><li><a href="https://arxiv.org/abs/1906.00446">1906.00446] Generating Diverse High-Fidelity Images with VQ-VAE-2</a></li><li><a href="https://rentry.org/lora_train">LoRA Training Guide</a></li><li><a href="https://www.reddit.com/r/StableDiffusion/wiki/index/">r/StableDiffusion</a></li><li><a href="https://www.reddit.com/r/StableDiffusion/wiki/index/"><a href="https://huggingface.co/models?pipeline_tag=text-to-image&sort=downloads">Models - Hugging Face</a></a></li><li><a href="https://stable-diffusion-book.vercel.app/">StableDiffusionBook - StableDiffusionBook</a></li></ol><link rel="stylesheet" href="/css/spoiler.css" type="text/css"><script src="/js/spoiler.js" type="text/javascript" async></script>]]></content>
<summary type="html"><p>在较早的VAE和GAN时代,通过图形生成模型,可能出现模式坍塌(个人理解就是生成图像多样性不足)并且只有图像数据参与,在之后的diffusion时代(包括现在),有了多模态的加入,通过文本、深度图以及骨骼图(e.g.ControlNet)控制图像生成有了更高的自由度和创造性.此外有了类似LoRA,IP-Adapter等等技术提供了更好的微调方式用于在更新模型权重保持良好的风格迁移(比如原本模型训练集中没有的人物,在微调时增加新人物训练能有良好表现)或人物一致性(不同的角色不至于长得一样)</p>
<p>而在最近有了更多在一些较大模型(stable diffusionv3,novelai,sdxl等)上微调的模型,它们更加专注于某种画风。此外本文尝试总结目前文生图、艺术图片等生成式AI应用,最后从图片展望生成式视频创作。相对于论文、原理,这里更注重模型与应用。</p></summary>
<category term="deep learning" scheme="https://www.sekyoro.top/tags/deep-learning/"/>
<category term="generative AI" scheme="https://www.sekyoro.top/tags/generative-AI/"/>
</entry>
<entry>
<title>c++17中的新东西</title>
<link href="https://www.sekyoro.top/2024/10/21/c-17%E4%B8%AD%E7%9A%84%E6%96%B0%E4%B8%9C%E8%A5%BF/"/>
<id>https://www.sekyoro.top/2024/10/21/c-17%E4%B8%AD%E7%9A%84%E6%96%B0%E4%B8%9C%E8%A5%BF/</id>
<published>2024-10-21T14:14:30.000Z</published>
<updated>2024-11-02T13:30:38.436Z</updated>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="\assets\js\Meting.min.js"></script><p>c++的几个重点版本,c++11,c++17,c++20都是有必要去了解的. c++11是现代c++的基石,c++20有了更现代的东西,而c++17承前启后,多了许多现代c++常用但又常忽略的东西,这里简单介绍一些.<br><span id="more"></span></p><h2 id="std-string-view"><a href="#std-string-view" class="headerlink" title="std::string_view"></a>std::string_view</h2><p>std::string_view让我们像处理字符串一样处理字符序列,而不需要为它们分配内存空间。也就是说,std::string_view类型的对象只是引用一个外部的字符序列,而不需要持有它们。因此,一个字符串视图对象可以被看作字符串序列的引用</p><p>使用字符串视图的开销很小,速度却很快(以值传递一个string_view的开销总是很小)。然而,它也有一些潜在的危险,就和原生指针一样,<strong>在使用string_view时也必须由程序员自己来保证引用的字符串序列是有效的</strong></p><p>和 std::string相比,std::string_view对象有以下特点:</p><ul><li>底层的字符序列是只读的。没有操作可以修改底层的字符。你只能赋予一个新值、交换值、把视图缩小为字符序列的子序列。</li><li>字符序列不保证有空字符(‘\0’)终止。因此,字符串视图并不是一个空字符终止的字节流 </li></ul><link rel="stylesheet" href="/css/spoiler.css" type="text/css"><script src="/js/spoiler.js" type="text/javascript" async></script>]]></content>
<summary type="html"><p>c++的几个重点版本,c++11,c++17,c++20都是有必要去了解的. c++11是现代c++的基石,c++20有了更现代的东西,而c++17承前启后,多了许多现代c++常用但又常忽略的东西,这里简单介绍一些.<br></summary>
<category term="c++17" scheme="https://www.sekyoro.top/tags/c-17/"/>
</entry>
<entry>
<title>现代C++中的异常处理</title>
<link href="https://www.sekyoro.top/2024/10/16/%E7%8E%B0%E4%BB%A3C-%E4%B8%AD%E7%9A%84%E5%BC%82%E5%B8%B8%E5%A4%84%E7%90%86/"/>
<id>https://www.sekyoro.top/2024/10/16/%E7%8E%B0%E4%BB%A3C-%E4%B8%AD%E7%9A%84%E5%BC%82%E5%B8%B8%E5%A4%84%E7%90%86/</id>
<published>2024-10-16T11:38:41.000Z</published>
<updated>2024-10-16T16:01:26.284Z</updated>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="\assets\js\Meting.min.js"></script><p>处理异常和错误是现代编程中的重要一环,许多框架中都有一些API会返回错误以供处理. 在现代c++中,也有专门用于处理的方法<br><span id="more"></span></p><h2 id="old-style"><a href="#old-style" class="headerlink" title="old-style"></a>old-style</h2><p>程序错误通常分为两类:</p><ul><li>编程错误导致的逻辑错误。 例如,“索引超出范围”错误。</li><li>超出程序员控制的运行时错误。 例如,“网络服务不可用”错误。</li></ul><p>在 C 样式的编程和 COM 中,错误报告的管理方式是返回一个表示错误代码或特定函数的状态代码的值,或者设置一个全局变量,调用方可以在每次执行函数调用后选择性地检索该变量来查看是否报告了错误。</p><p>在c语言中使用errno来表示错误,当出现错误时,errno会被修改为对应错误代码,通过strerror转为对应错误信息.</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><cerrno></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><clocale></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><cstdio></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><cstdlib></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><cstring></span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span> </span>{</span><br><span class="line"> errno = ENODATA;</span><br><span class="line"> <span class="built_in">setlocale</span>(LC_ALL, <span class="string">"en_US.utf8"</span>);</span><br><span class="line"> std::<span class="built_in">puts</span>(<span class="string">"Hello, World!"</span>);</span><br><span class="line"> FILE *fp;</span><br><span class="line"></span><br><span class="line"> fp = <span class="built_in">fopen</span>(<span class="string">"file.txt"</span>, <span class="string">"r"</span>);</span><br><span class="line"> <span class="keyword">if</span> (fp == <span class="literal">NULL</span>) {</span><br><span class="line"> <span class="built_in">fprintf</span>(stderr, <span class="string">"Value of errno: %d\n"</span>, errno);</span><br><span class="line"> <span class="built_in">fprintf</span>(stderr, <span class="string">"Error opening file: %s\n"</span>, <span class="built_in">strerror</span>(errno));</span><br><span class="line"> <span class="built_in">perror</span>(<span class="string">"Error printed by perror"</span>);</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="built_in">fclose</span>(fp);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br><span class="line"></span><br></pre></td></tr></table></figure><p><strong>常见的 <code>errno</code> 值</strong></p><ul><li><code>EPERM</code>:操作不允许</li><li><code>ENOENT</code>:没有这样的文件或目录</li><li><code>ESRCH</code>:没有这样的进程</li><li><code>EINTR</code>:中断的系统调用</li><li><code>EIO</code>:输入/输出错误</li></ul><p><strong>c++中的std::errc</strong></p><p>如果要自己创建业务上的错误码,可以考虑<code>enum</code>或者<code>enum class</code>,有更好的语义.c++标准库中有<code>std::errc</code>这个枚举类,起到错误码的作用</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">_STD_BEGIN</span><br><span class="line">_EXPORT_STD <span class="class"><span class="keyword">enum</span> <span class="keyword">class</span> <span class="title">errc</span> {</span> <span class="comment">// names for generic error codes</span></span><br><span class="line"> address_family_not_supported = <span class="number">102</span>, <span class="comment">// EAFNOSUPPORT</span></span><br><span class="line"> address_in_use = <span class="number">100</span>, <span class="comment">// EADDRINUSE</span></span><br><span class="line"> address_not_available = <span class="number">101</span>, <span class="comment">// EADDRNOTAVAIL</span></span><br><span class="line"> already_connected = <span class="number">113</span>, <span class="comment">// EISCONN</span></span><br><span class="line"> argument_list_too_long = <span class="number">7</span>, <span class="comment">// E2BIG</span></span><br><span class="line"> argument_out_of_domain = <span class="number">33</span>, <span class="comment">// EDOM</span></span><br><span class="line"> bad_address = <span class="number">14</span>, <span class="comment">// EFAULT</span></span><br><span class="line"> ...</span><br><span class="line">_STD_END</span><br></pre></td></tr></table></figure><p><strong>c++中的std::error_code</strong></p><p>此外标准库中还有<code>std::error_code</code>和<code>std::error_category</code>,这样相当于提供了分类,error_category有不同名字用以区分,继承error_category,实现name和message方法.</p><p><img data-src="https://breese.github.io/assets/customize/error_code.png" alt="error_code"></p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">auto</span> error_code = std::<span class="built_in">make_error_code</span>(std::errc::invalid_argument);</span><br><span class="line">std::cout<<error_code.<span class="built_in">message</span>()<<std::endl;</span><br><span class="line">std::cout<<error_code.<span class="built_in">value</span>()<<std::endl;</span><br></pre></td></tr></table></figure><p>新式 C++ 中优先使用异常的原因如下:</p><ul><li>异常会强制调用代码识别并处理错误状态。 未经处理的异常会停止程序执行。</li><li>异常跳转到调用堆栈中可以处理错误的位置。 中间函数可以让异常传播。 这些函数不必与其他层协调。</li><li>引发异常后,异常堆栈展开机制将根据妥善定义的规则销毁范围内的所有对象。</li><li>异常可以在检测错误的代码与处理错误的代码之间实现明确的分离</li></ul><p>在c++中目前常用<code>try</code>,<code>catch</code>处理异常</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">try</span> {</span><br><span class="line"><span class="built_in">func</span>(<span class="number">3</span>);</span><br><span class="line">} <span class="built_in"><span class="keyword">catch</span></span> (<span class="keyword">const</span> std::invalid_argument& e) {</span><br><span class="line">std::cerr << e.<span class="built_in">what</span>() << <span class="string">'\n'</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>异常来通常来自<code>std::exception</code>或标准库中定义的派生类,也可以自己派生<code>std::exception</code>异常类.</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><iostream></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><exception></span></span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"> </span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">MyException</span> :</span> <span class="keyword">public</span> exception</span><br><span class="line">{</span><br><span class="line"> <span class="function"><span class="keyword">const</span> <span class="keyword">char</span> * <span class="title">what</span> <span class="params">()</span> <span class="keyword">const</span> <span class="title">throw</span> <span class="params">()</span></span></span><br><span class="line"><span class="function"> </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"C++ Exception"</span>;</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"> </span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="keyword">try</span></span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">throw</span> <span class="built_in">MyException</span>();</span><br><span class="line"> }</span><br><span class="line"> <span class="built_in"><span class="keyword">catch</span></span>(MyException& e)</span><br><span class="line"> {</span><br><span class="line"> std::cout << <span class="string">"MyException caught"</span> << std::endl;</span><br><span class="line"> std::cout << e.<span class="built_in">what</span>() << std::endl;</span><br><span class="line"> }</span><br><span class="line"> <span class="built_in"><span class="keyword">catch</span></span>(std::exception& e)</span><br><span class="line"> {</span><br><span class="line"> <span class="comment">//其他的错误</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>如果无法找到当前异常的匹配处理程序(或省略号 <strong><code>catch</code></strong> 处理程序),则调用预定义的 <code>terminate</code> 运行时函数.<code>erminate</code> 的默认操作是调用 <code>abort</code>。 如果你希望 <code>terminate</code> 在退出应用程序之前调用程序中的某些其他函数,则用被调用函数的名称作为其单个自变量调用 <code>set_terminate</code> 函数</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// #include <cerrno></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><cerrno></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><clocale></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><cstdio></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><cstdlib></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><cstring></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><iostream></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><limits></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><stdexcept></span></span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">func</span><span class="params">(<span class="keyword">int</span> c)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (c > std::numeric_limits<<span class="keyword">char</span>>::<span class="built_in">max</span>()) {</span><br><span class="line"> <span class="keyword">throw</span> std::<span class="built_in">invalid_argument</span>(<span class="string">"Invalid argument"</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">term_func</span><span class="params">()</span> </span>{</span><br><span class="line"> std::cerr << <span class="string">"terminate handler called\n"</span>;</span><br><span class="line"> std::<span class="built_in">abort</span>();</span><br><span class="line">}</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span> </span>{</span><br><span class="line"> std::<span class="built_in">set_terminate</span>(term_func);</span><br><span class="line"> <span class="built_in">setlocale</span>(LC_ALL, <span class="string">"en_US.utf8"</span>);</span><br><span class="line"> std::<span class="built_in">puts</span>(<span class="string">"Hello, World!"</span>);</span><br><span class="line"> FILE* fp;</span><br><span class="line"> fp = <span class="built_in">fopen</span>(<span class="string">"file.txt"</span>, <span class="string">"r"</span>);</span><br><span class="line"> <span class="keyword">if</span> (fp == <span class="literal">NULL</span>) {</span><br><span class="line"> <span class="built_in">fprintf</span>(stderr, <span class="string">"Value of errno: %d\n"</span>, errno);</span><br><span class="line"> <span class="built_in">fprintf</span>(stderr, <span class="string">"Error opening file: %s\n"</span>, <span class="built_in">strerror</span>(errno));</span><br><span class="line"> <span class="built_in">perror</span>(<span class="string">"Error printed by perror"</span>);</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="built_in">fclose</span>(fp);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="built_in">func</span>(<span class="number">3</span>);</span><br><span class="line"> } <span class="built_in"><span class="keyword">catch</span></span> (<span class="keyword">const</span> std::invalid_argument& e) {</span><br><span class="line"> std::cerr << e.<span class="built_in">what</span>() << <span class="string">'\n'</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="std-optional"><a href="#std-optional" class="headerlink" title="std::optional"></a>std::optional</h2><p>类模板 <code>std::optional</code> 管理一个<strong>可选</strong>的所含值,即既可以存在也可以不存在的值。</p><p>一种常见的 <code>optional</code> 使用情况是作为可能失败的函数的返回值。与如 std::pair<T, bool> 等其他手段相比,<code>optional</code> 可以很好地处理构造开销高昂的对象,并更加可读,因为它明确表达了意图。<code>optional<T></code> 的任何实例在任意给定时间点要么<em>含值</em>,要么<em>不含值</em>。</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><iostream></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><optional></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><string></span></span></span><br><span class="line"> </span><br><span class="line"><span class="comment">// optional 可用作可能失败的工厂的返回类型</span></span><br><span class="line"><span class="function">std::optional<std::string> <span class="title">create</span><span class="params">(<span class="keyword">bool</span> b)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="keyword">if</span> (b)</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"Godzilla"</span>;</span><br><span class="line"> <span class="keyword">return</span> {};</span><br><span class="line">}</span><br><span class="line"> </span><br><span class="line"><span class="comment">// 能用 std::nullopt 创建任何(空的)std::optional</span></span><br><span class="line"><span class="function"><span class="keyword">auto</span> <span class="title">create2</span><span class="params">(<span class="keyword">bool</span> b)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="keyword">return</span> b ? std::optional<std::string>{<span class="string">"Godzilla"</span>} : std::nullopt;</span><br><span class="line">}</span><br><span class="line"> </span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> std::cout << <span class="string">"create(false) 返回 "</span></span><br><span class="line"> << <span class="built_in">create</span>(<span class="literal">false</span>).<span class="built_in">value_or</span>(<span class="string">"empty"</span>) << <span class="string">'\n'</span>;</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 返回 optional 的工厂函数可用作 while 和 if 的条件</span></span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">auto</span> str = <span class="built_in">create2</span>(<span class="literal">true</span>))</span><br><span class="line"> std::cout << <span class="string">"create2(true) 返回 "</span> << *str << <span class="string">'\n'</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">auto</span> oDouble = std::<span class="built_in">make_optional</span>(<span class="number">3.0</span>);</span><br><span class="line"><span class="keyword">auto</span> oComplex = make_optional<complex<<span class="keyword">double</span>>>(<span class="number">3.0</span>, <span class="number">4.0</span>);</span><br></pre></td></tr></table></figure><p><code>std::in_place</code> 、 <code>std::in_place_type</code> 和 <code>std::in_place_index</code> 是消歧义标签,能传递给std::optional 、std::variant和std::any的构造函数,以指示应该原位构造对象,以及(对于后二者)要构造的对象的类型。</p><p>对应的类型/类型模板 <code>std::in_place_t</code> 、 <code>std::in_place_type_t</code> 和 <code>std::in_place_index_t</code> 能用于构造函数的参数列表中,以匹配有意的标签</p><h2 id="std-variant"><a href="#std-variant" class="headerlink" title="std::variant"></a>std::variant</h2><p>类模板 <code>std::variant</code> 表示一个类型安全的联合体(以下称“变体”).一个 <code>std::variant</code> 的实例在任意时刻要么保有它的可选类型之一的值,要么在错误情况下无值</p><ul><li>std::in_place_type - 用于指定你想在 variant 里改变或者设定哪个类型</li><li>std::in_place_index - 用于指定你想改变或者设定的索引。类型从0开始枚举。<br>在 invariant std::variant<int, float, std::string> 中 - int 的索引是0,float的索引是1,string的索引是2。索引和 variant::index 方法的返回值是一样的</li></ul><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><cassert></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><iostream></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><string></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><variant></span></span></span><br><span class="line"> </span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> std::variant<<span class="keyword">int</span>, <span class="keyword">float</span>> v, w;</span><br><span class="line"> std::variant<<span class="keyword">int</span>,<span class="keyword">double</span>> myv{std::in_place_index<<span class="number">0</span>>,<span class="number">1</span>,std::in_place_index<<span class="number">1</span>>,<span class="number">1.2f</span>};</span><br><span class="line"> v = <span class="number">42</span>; <span class="comment">// v 含 int</span></span><br><span class="line"> <span class="keyword">int</span> i = std::get<<span class="keyword">int</span>>(v);</span><br><span class="line"> <span class="built_in">assert</span>(<span class="number">42</span> == i); <span class="comment">// 成功</span></span><br><span class="line"> w = std::get<<span class="keyword">int</span>>(v);</span><br><span class="line"> w = std::get<<span class="number">0</span>>(v); <span class="comment">// 与前一行效果相同</span></span><br><span class="line"> w = v; <span class="comment">// 与前一行效果相同</span></span><br><span class="line"> </span><br><span class="line"><span class="comment">// std::get<double>(v); // 错误:[int, float] 中无 double</span></span><br><span class="line"><span class="comment">// std::get<3>(v); // 错误:有效索引值为 0 与 1</span></span><br><span class="line"> </span><br><span class="line"> <span class="keyword">try</span></span><br><span class="line"> {</span><br><span class="line"> std::get<<span class="keyword">float</span>>(w); <span class="comment">// w 含 int 而非 float:会抛出异常</span></span><br><span class="line"> }</span><br><span class="line"> <span class="built_in"><span class="keyword">catch</span></span> (<span class="keyword">const</span> std::bad_variant_access& ex)</span><br><span class="line"> {</span><br><span class="line"> std::cout << ex.<span class="built_in">what</span>() << <span class="string">'\n'</span>;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">using</span> <span class="keyword">namespace</span> std::literals;</span><br><span class="line"> </span><br><span class="line"> <span class="function">std::variant<std::string> <span class="title">x</span><span class="params">(<span class="string">"abc"</span>)</span></span>;</span><br><span class="line"> <span class="comment">// 转换构造函数在无歧义时起作用</span></span><br><span class="line"> x = <span class="string">"def"</span>; <span class="comment">// 转换赋值在无歧义时亦起作用</span></span><br><span class="line"> </span><br><span class="line"> <span class="function">std::variant<std::string, <span class="keyword">void</span> <span class="keyword">const</span>*> <span class="title">y</span><span class="params">(<span class="string">"abc"</span>)</span></span>;</span><br><span class="line"> <span class="comment">// 传递 char const* 时转换成 void const*</span></span><br><span class="line"> <span class="built_in">assert</span>(std::holds_alternative<<span class="keyword">void</span> <span class="keyword">const</span>*>(y)); <span class="comment">// 成功</span></span><br><span class="line"> y = <span class="string">"xyz"</span>s;</span><br><span class="line"> <span class="built_in">assert</span>(std::holds_alternative<std::string>(y)); <span class="comment">// 成功</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="std-expected"><a href="#std-expected" class="headerlink" title="std::expected"></a>std::expected</h2><p>在c++23中实现,类模板 <code>std::expected</code> 提供表示两个值之一的方式:它要么表示一个 <code>T</code> 类型的<em>预期</em> 值,要么表示一个 <code>E</code> 类型的<em>非预期</em> 值。<code>std::expected</code> 决不会无值。</p><p>1) 主模板。在自身的存储中包含预期值或非预期值。不会进行动态分配。<br>2) void 部分特化。表示一个 void 类型的预期值或在自身的存储中包含非预期值。不会进行动态分配。</p><p>如果程序以引用类型、函数类型,或 std::unexpected的特化实例化 <code>expected</code>,那么程序非良构.另外,<code>T</code> 必须不是std::in_place_t或std::unexpect_t</p><p>非预期值是类模板 <code>std::unexpected</code> 代表一个 std::expected 中存储的非预期值。特别地,std::expected 具有接受 <code>std::unexpected</code> 为唯一实参的构造函数,创建含有非预期值的expected 对象。</p><p>用非对象类型、数组类型、<code>std::unexpected</code> 的特化或有 cv 限定的类型实例化 <code>unexpected</code> 的程序非良构</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">auto</span> <span class="title">parse_num</span><span class="params">(std::string_view& str)</span>->std::expected<<span class="keyword">int</span>, std::string> </span>{</span><br><span class="line"> <span class="keyword">if</span> (str.<span class="built_in">empty</span>()) {</span><br><span class="line"> <span class="keyword">return</span> std::unexpected<std::string>(<span class="string">"empty string"</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">size_t</span> pos;</span><br><span class="line"> <span class="keyword">int</span> i = std::<span class="built_in">stoi</span>(std::<span class="built_in">string</span>(str), &pos);</span><br><span class="line"> str.<span class="built_in">remove_prefix</span>(pos);</span><br><span class="line"> <span class="keyword">return</span> i;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h2><ol><li><a href="https://www.runoob.com/cplusplus/cpp-exceptions-handling.html">C++ 异常处理 | 菜鸟教程 (runoob.com)</a></li><li><a href="https://breese.github.io/2017/05/12/customizing-error-codes.html">Customizing Error Codes (breese.github.io)</a></li></ol><link rel="stylesheet" href="/css/spoiler.css" type="text/css"><script src="/js/spoiler.js" type="text/javascript" async></script>]]></content>
<summary type="html"><p>处理异常和错误是现代编程中的重要一环,许多框架中都有一些API会返回错误以供处理. 在现代c++中,也有专门用于处理的方法<br></summary>
<category term="c++" scheme="https://www.sekyoro.top/tags/c/"/>
</entry>
<entry>
<title>现代化的浏览器可重用元素:web component</title>
<link href="https://www.sekyoro.top/2024/10/15/%E7%8E%B0%E4%BB%A3%E5%8C%96%E7%9A%84%E6%B5%8F%E8%A7%88%E5%99%A8%E5%8F%AF%E9%87%8D%E7%94%A8%E5%85%83%E7%B4%A0-web-component/"/>
<id>https://www.sekyoro.top/2024/10/15/%E7%8E%B0%E4%BB%A3%E5%8C%96%E7%9A%84%E6%B5%8F%E8%A7%88%E5%99%A8%E5%8F%AF%E9%87%8D%E7%94%A8%E5%85%83%E7%B4%A0-web-component/</id>
<published>2024-10-15T11:29:00.000Z</published>
<updated>2024-10-15T14:55:36.024Z</updated>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="\assets\js\Meting.min.js"></script><p>Web components 是用于创建独立组件的一组标准:自定义 HTML 元素,它们具有自己的属性和方法,封装好的 DOM 和样式。</p><span id="more"></span><p>当我们写html/css时,如何想要创建自己的html元素或者进行代码复用,这就需要通过使用web components或者react/vue这种前端库.</p><p>Web Components 旨在解决这些问题 — 它由三项主要技术组成,它们可以一起使用来创建封装功能的定制元素,可以在你喜欢的任何地方重用,不必担心代码冲突。</p><ul><li><strong>Custom element(自定义元素)</strong>:一组 JavaScript API,允许你定义 custom elements 及其行为,然后可以在你的用户界面中按照需要使用它们。</li><li><strong>Shadow DOM(影子 DOM)</strong>:一组 JavaScript API,用于将封装的“影子”DOM 树附加到元素(与主文档 DOM 分开呈现)并控制其关联的功能。通过这种方式,你可以保持元素的功能私有,这样它们就可以被脚本化和样式化,而不用担心与文档的其他部分发生冲突。</li><li><strong>HTML template(HTML 模板):</strong> template和slot元素使你可以编写不在呈现页面中显示的标记模板。然后它们可以作为自定义元素结构的基础被多次重用。</li></ul><p>实现 web component 的基本方法通常如下所示:</p><ol><li>创建一个类或函数来指定 web 组件的功能,如果使用类,请使用 ECMAScript 2015 的类语法 </li><li>使用 CustomElementRegistry.define()方法注册的新自定义元素,并向其传递要定义的元素名称、指定元素功能的类、以及可选的其所继承自的元素。</li><li>如果需要的话,使用Element.attachShadow()方法将一个 shadow DOM 附加到自定义元素上。使用通常的 DOM 方法向 shadow DOM 中添加子元素、事件监听器等等。</li><li>如果需要的话,使用template和slot 定义一个 HTML 模板。再次使用常规 DOM 方法克隆模板并将其附加到你的 shadow DOM 中。</li><li>在页面任何你喜欢的位置使用自定义元素,就像使用常规 HTML 元素那样。</li></ol><h2 id="自定义元素"><a href="#自定义元素" class="headerlink" title="自定义元素"></a>自定义元素</h2><p>有两种类型的自定义元素:</p><ul><li><strong>自定义内置元素</strong>(Customized built-in element)继承自标准的 HTML 元素,例如 <a href="https://developer.mozilla.org/zh-CN/docs/Web/API/HTMLImageElement"><code>HTMLImageElement</code></a> 或 <a href="https://developer.mozilla.org/zh-CN/docs/Web/API/HTMLParagraphElement"><code>HTMLParagraphElement</code></a>。它们的实现定义了标准元素的行为。</li><li><strong>独立自定义元素</strong>(Autonomous custom element)继承自 HTML 元素基类 <a href="https://developer.mozilla.org/zh-CN/docs/Web/API/HTMLElement"><code>HTMLElement</code></a>。你必须从头开始实现它们的行为。</li></ul><p>自定义元素作为一个类来实现,该类可以扩展 <a href="https://developer.mozilla.org/zh-CN/docs/Web/API/HTMLElement"><code>HTMLElement</code></a>(在独立元素的情况下)或者你想要定制的接口(在自定义内置元素的情况下)。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">WordCount</span> <span class="keyword">extends</span> <span class="title">HTMLParagraphElement</span> </span>{</span><br><span class="line"> <span class="function"><span class="title">constructor</span>(<span class="params"></span>)</span> {</span><br><span class="line"> <span class="built_in">super</span>();</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 此处编写元素功能</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>构造函数中可以添加一些元素的状态、注册事件监听器等. 此外有一些特别的回调方法,一旦自定义元素被注册,当页面中的代码以特定方式与自定义元素交互时,浏览器将调用类的某些方法。通过提供这些方法的实现,规范称之为<em>生命周期回调</em>,你可以运行代码来响应这些事件。</p><ul><li><code>connectedCallback()</code>:每当元素添加到文档中时调用。规范建议开发人员尽可能在此回调中实现自定义元素的设定,而不是在构造函数中实现。</li><li><code>disconnectedCallback()</code>:每当元素从文档中移除时调用。</li><li><code>adoptedCallback()</code>:每当元素被移动到新文档中时调用。</li><li><code>attributeChangedCallback()</code>:在属性更改、添加、移除或替换时调用。</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">WordCount</span> <span class="keyword">extends</span> <span class="title">HTMLElement</span> </span>{</span><br><span class="line"> <span class="function"><span class="title">constructor</span>(<span class="params"></span>)</span> {</span><br><span class="line"> <span class="built_in">super</span>();</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="title">connectedCallback</span>(<span class="params"></span>)</span> {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">"自定义元素添加至页面。"</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="title">disconnectedCallback</span>(<span class="params"></span>)</span> {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">"自定义元素从页面中移除。"</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="title">adoptedCallback</span>(<span class="params"></span>)</span> {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">"自定义元素移动至新页面。"</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="title">attributeChangedCallback</span>(<span class="params">name, oldValue, newValue</span>)</span> {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">`属性 <span class="subst">${name}</span> 已变更。`</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">customElements.define(<span class="string">"word-count"</span>, WordCount);</span><br><span class="line"></span><br></pre></td></tr></table></figure><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta"><!DOCTYPE <span class="meta-keyword">html</span>></span></span><br><span class="line"><span class="tag"><<span class="name">html</span> <span class="attr">lang</span>=<span class="string">"en"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">head</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">"UTF-8"</span> /></span></span><br><span class="line"> <span class="tag"><<span class="name">meta</span> <span class="attr">name</span>=<span class="string">"viewport"</span> <span class="attr">content</span>=<span class="string">"width=device-width, initial-scale=1.0"</span> /></span></span><br><span class="line"> <span class="tag"><<span class="name">title</span>></span>Document<span class="tag"></<span class="name">title</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">head</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">body</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">word-count</span>/></span></span><br><span class="line"> <span class="tag"><<span class="name">script</span> <span class="attr">src</span>=<span class="string">"main.js"</span>></span><span class="tag"></<span class="name">script</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">body</span>></span></span><br><span class="line"><span class="tag"></<span class="name">html</span>></span></span><br></pre></td></tr></table></figure><p><img data-src="https://s2.loli.net/2024/10/15/TaoEQKDzCR8bV1j.png" alt="image-20241015204756114"></p><h3 id="注册元素"><a href="#注册元素" class="headerlink" title="注册元素"></a>注册元素</h3><p>要使自定义元素在页面中可用,请调用<code>Window.customElements</code> 的 define() 方法。</p><p>元素的名称。必须以小写字母开头,包含一个连字符,并符合规范中有效名称的定义中列出的一些其他规则</p><figure class="highlight awk"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">customElements.define(<span class="string">"word-count"</span>, WordCount); <span class="regexp">//</span> 独立自定义元素 <word-count> </word-count></span><br><span class="line">customElements.define(<span class="string">"word-count"</span>, WordCount,{extends:<span class="string">"p"</span>}); <span class="regexp">//</span>自定义内置元素 命名了要扩展的内置元素 <p is=<span class="string">"word-count"</span>></p></span><br></pre></td></tr></table></figure><h3 id="使用元素"><a href="#使用元素" class="headerlink" title="使用元素"></a>使用元素</h3><p>定义并注册元素之后,就可以直接在html中使用了.</p><p>要使用<strong>自定义内置元素</strong>,请使用内置元素,但将自定义名称作为is 属性的值:</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">p</span> <span class="attr">is</span>=<span class="string">"word-count"</span>></span><span class="tag"></<span class="name">p</span>></span></span><br></pre></td></tr></table></figure><p>使用<strong>独立自定义元素</strong>,,就像使用内置的 HTML 元素一样,使用自定义名称即可:</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">word-count</span>></span></span><br><span class="line"> <span class="comment"><!-- 元素的内容 --></span></span><br><span class="line"><span class="tag"></<span class="name">word-count</span>></span></span><br></pre></td></tr></table></figure><h3 id="相应属性变化"><a href="#相应属性变化" class="headerlink" title="相应属性变化"></a>相应属性变化</h3><p>与内置元素一样,自定义元素可以使用 HTML 属性来配置元素的行为。为了有效地使用属性,元素必须能够响应属性值的变化。为此,自定义元素需要将以下成员添加到实现自定义元素的类中:</p><ul><li>一个名为 <code>observedAttributes</code> 的静态属性。这必须是一个包含元素需要变更通知的所有属性名称的数组。</li><li><code>attributeChangedCallback()</code> 生命周期回调的实</li></ul><p><code>attributeChangedCallback()</code> 回调在列在元素的 <code>observedAttributes</code> 属性中的属性被添加、修改、移除或替换时调用。</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line">class WordCount extends HTMLElement {</span><br><span class="line"> static observedAttributes = ["size"];</span><br><span class="line"> constructor() {</span><br><span class="line"> super();</span><br><span class="line"> }</span><br><span class="line"> connectedCallback() {</span><br><span class="line"> console.log("自定义元素添加至页面。");</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> disconnectedCallback() {</span><br><span class="line"> console.log("自定义元素从页面中移除。");</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> adoptedCallback() {</span><br><span class="line"> console.log("自定义元素移动至新页面。");</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> attributeChangedCallback(name, oldValue, newValue) {</span><br><span class="line"> console.log(`属性 ${name} 已变更。从${oldValue}变为${newValue}`);</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">customElements.define("word-count", WordCount);</span><br><span class="line"></span><br></pre></td></tr></table></figure><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta"><!DOCTYPE <span class="meta-keyword">html</span>></span></span><br><span class="line"><span class="tag"><<span class="name">html</span> <span class="attr">lang</span>=<span class="string">"en"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">head</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">"UTF-8"</span> /></span></span><br><span class="line"> <span class="tag"><<span class="name">meta</span> <span class="attr">name</span>=<span class="string">"viewport"</span> <span class="attr">content</span>=<span class="string">"width=device-width, initial-scale=1.0"</span> /></span></span><br><span class="line"> <span class="tag"><<span class="name">title</span>></span>Document<span class="tag"></<span class="name">title</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">style</span>></span><span class="css"></span></span><br><span class="line"><span class="css"> word-count {</span></span><br><span class="line"><span class="css"> <span class="attribute">display</span>: block;</span></span><br><span class="line"><span class="css"> <span class="attribute">margin-top</span>: <span class="number">20px</span>;</span></span><br><span class="line"><span class="css"> }</span></span><br><span class="line"><span class="css"> </span><span class="tag"></<span class="name">style</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">head</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">body</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">word-count</span> <span class="attr">size</span>=<span class="string">"100"</span>/></span></span><br><span class="line"> <span class="tag"><<span class="name">script</span> <span class="attr">src</span>=<span class="string">"main.js"</span>></span><span class="tag"></<span class="name">script</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">body</span>></span></span><br><span class="line"><span class="tag"></<span class="name">html</span>></span></span><br><span class="line"></span><br></pre></td></tr></table></figure><h2 id="使用Shadow-DOM"><a href="#使用Shadow-DOM" class="headerlink" title="使用Shadow DOM"></a>使用Shadow DOM</h2><p><em>影子</em>(shadow) DOM 允许将隐藏的DOM 树附加到常规 DOM 树中的元素上——这个影子 DOM 始于一个影子根,在其之下可以用与普通 DOM 相同的方式附加任何元素</p><p><img data-src="https://developer.mozilla.org/zh-CN/docs/Web/API/Web_components/Using_shadow_DOM/shadowdom.svg" alt="显示文档、影子根和影子宿主交互的图示的 SVG 版本"></p><ul><li><strong>影子宿主(Shadow host)</strong>: 影子 DOM 附加到的常规 DOM 节点。</li><li><strong>影子树(Shadow tree)</strong>: 影子 DOM 内部的 DOM 树。</li><li><strong>影子边界(Shadow boundary)</strong>: 影子 DOM 终止,常规 DOM 开始的地方。</li><li><strong>影子根(Shadow root)</strong>: 影子树的根节点。</li></ul><p>使用<code>attachShadow</code>给一个元素挂在影子DOM,出于安全考虑,一些元素不能使用 shadow DOM(例如<a href="https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/a">``</a>),以及许多其他的元素。下面是一个<strong>可以</strong>挂载 shadow root 的元素列表:</p><p><img data-src="https://s2.loli.net/2024/10/15/14mv6JB29iEafI8.png" alt="image-20241015212842605"></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> pEle = <span class="built_in">document</span>.querySelector(<span class="string">"#para"</span>);</span><br><span class="line">pEle.attachShadow({ <span class="attr">mode</span>: <span class="string">"open"</span> });</span><br></pre></td></tr></table></figure><p>影子 DOM 中元素对页面中的 JavaScript 来说基本上是隐藏的</p><p>当 <code>mode</code> 设置为 <code>"open"</code> 时,页面中的 JavaScript 可以通过影子宿主的shadowRoot属性访问影子 DOM 的内部。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> shadow = pEle.attachShadow({ <span class="attr">mode</span>: <span class="string">"open"</span> });</span><br><span class="line">shadow.appendChild(<span class="built_in">document</span>.createTextNode(<span class="string">"这是一个自定义元素"</span>));</span><br><span class="line"><span class="keyword">const</span> spans = shadow.querySelectorAll(<span class="string">"span"</span>);</span><br><span class="line"><span class="keyword">for</span>(<span class="keyword">const</span> span <span class="keyword">of</span> spans) {</span><br><span class="line"> span.textContent = span.textContent.toLocaleLowerCase();</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>页面的 CSS 不会影响影子 DOM 内的节点,有两种方法可以改变影子DOM树中的样式.</p><ul><li>编程式,通过构建一个 <a href="https://developer.mozilla.org/zh-CN/docs/Web/API/CSSStyleSheet"><code>CSSStyleSheet</code></a> 对象并将其附加到影子根。</li><li>声明式,通过在一个 template元素的声明中添加一个style元素</li></ul><p>影子 DOM 树中定义的样式局限在该树内,所以就像页面样式就像不会影响影子 DOM 中的元素一样,影子 DOM 样式也不会影响页面中其它元素的样式。</p><p><strong>编程式</strong></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">customElements.define(<span class="string">"word-count"</span>, WordCount);</span><br><span class="line"><span class="keyword">const</span> pEle = <span class="built_in">document</span>.querySelector(<span class="string">"#para"</span>);</span><br><span class="line"><span class="keyword">const</span> shadow = pEle.attachShadow({ <span class="attr">mode</span>: <span class="string">"open"</span> });</span><br><span class="line"><span class="keyword">const</span> sheet = <span class="keyword">new</span> CSSStyleSheet();</span><br><span class="line">sheet.replaceSync(<span class="string">"span {color: red;}"</span>);</span><br><span class="line">shadow.adoptedStyleSheets = [sheet]; </span><br><span class="line"><span class="keyword">const</span> span = <span class="built_in">document</span>.createElement(<span class="string">"span"</span>);</span><br><span class="line">span.appendChild(<span class="built_in">document</span>.createTextNode(<span class="string">"这是一个span元素"</span>));</span><br><span class="line">shadow.appendChild(<span class="built_in">document</span>.createTextNode(<span class="string">"这是一个自定义元素"</span>));</span><br><span class="line">shadow.appendChild(span);</span><br><span class="line"><span class="keyword">const</span> spans = shadow.querySelectorAll(<span class="string">"span"</span>);</span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">const</span> span <span class="keyword">of</span> spans) {</span><br><span class="line"> span.textContent = span.textContent.toLocaleLowerCase();</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><strong>声明式</strong></p><p>在template中声明样式和元素,通过获取这个元素content,加到shadow dom中.</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">template</span> <span class="attr">id</span>=<span class="string">"wc"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">style</span>></span><span class="css"></span></span><br><span class="line"><span class="css"> <span class="selector-tag">span</span> {</span></span><br><span class="line"><span class="css"> <span class="attribute">display</span>: block;</span></span><br><span class="line"><span class="css"> <span class="attribute">margin-top</span>: <span class="number">20px</span>;</span></span><br><span class="line"><span class="css"> }</span></span><br><span class="line"><span class="css"> </span><span class="tag"></<span class="name">style</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">span</span>></span>I'm in the shadow DOM<span class="tag"></<span class="name">span</span>></span></span><br><span class="line"><span class="tag"></<span class="name">template</span>></span></span><br><span class="line"><span class="tag"><<span class="name">div</span> <span class="attr">id</span>=<span class="string">"host"</span>></span><span class="tag"></<span class="name">div</span>></span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> wc = <span class="built_in">document</span>.getElementById(<span class="string">"wc"</span>);</span><br><span class="line"><span class="keyword">const</span> host = <span class="built_in">document</span>.getElementById(<span class="string">"host"</span>);</span><br><span class="line"><span class="keyword">const</span> other_shadow = host.attachShadow({ <span class="attr">mode</span>: <span class="string">"open"</span> });</span><br><span class="line">other_shadow.appendChild(wc.content);</span><br></pre></td></tr></table></figure><p>使用哪种方式取决于你的应用程序和个人喜好。</p><p>创建一个 <code>CSSStyleSheet</code> 并通过 <code>adoptedStyleSheets</code> 将其赋给影子根允许你创建单一样式表并将其与多个 DOM 树共享。例如,一个组件库可以创建单个样式表,然后将其与该库的所有自定义元素共享。浏览器将仅解析该样式表。此外,你可以对样式表进行动态更改,并将更改传播到使用表的所有组件。</p><p>而当希望是声明式的、需要较少的样式并且不需要在不同组件之间共享样式的时候,附加 <code><style></code> 元素的方法则非常适合。</p><p>如果没有影子 DOM 提供的封装,自定义元素就无法使用。因为只需在某个页面上运行一些 JavaScript 或 CSS,就有可能无意间破坏自定义元素的行为或布局。</p><h2 id="模板与插槽"><a href="#模板与插槽" class="headerlink" title="模板与插槽"></a>模板与插槽</h2><h4 id="模板"><a href="#模板" class="headerlink" title="模板"></a>模板</h4><p>template很方便,搭配web component效果更好. 使用声明式搭配模板的方式</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">MyParagraph</span> <span class="keyword">extends</span> <span class="title">HTMLElement</span> </span>{</span><br><span class="line"> <span class="function"><span class="title">constructor</span>(<span class="params"></span>)</span> {</span><br><span class="line"> <span class="built_in">super</span>();</span><br><span class="line"> <span class="keyword">let</span> template = <span class="built_in">document</span>.getElementById(<span class="string">"my-paragraph"</span>);</span><br><span class="line"> <span class="keyword">let</span> templateContent = template.content;</span><br><span class="line"> <span class="keyword">const</span> shadowRoot = <span class="built_in">this</span>.attachShadow({ <span class="attr">mode</span>: <span class="string">"open"</span> });</span><br><span class="line"> shadowRoot.appendChild(templateContent.cloneNode(<span class="literal">true</span>));</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="title">connectedCallback</span>(<span class="params"></span>)</span> {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">"自定义元素添加至页面。"</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line">customElements.define(<span class="string">"my-paragraph"</span>, MyParagraph);</span><br></pre></td></tr></table></figure><p>首先定义一个元素,shadowRoot就是独立的自定义元素,然后添加template的内容,在html中声明template并使用元素. 使用cloneNode拷贝使得能创建多个自定义元素</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"> <span class="tag"><<span class="name">template</span> <span class="attr">id</span>=<span class="string">"my-paragraph"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">style</span>></span><span class="css"></span></span><br><span class="line"><span class="css"> <span class="selector-tag">p</span> {</span></span><br><span class="line"><span class="css"> <span class="attribute">color</span>: white;</span></span><br><span class="line"><span class="css"> <span class="attribute">background-color</span>: <span class="number">#666</span>;</span></span><br><span class="line"><span class="css"> <span class="attribute">padding</span>: <span class="number">5px</span>;</span></span><br><span class="line"><span class="css"> }</span></span><br><span class="line"><span class="css"> </span><span class="tag"></<span class="name">style</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">p</span>></span>我的段落<span class="tag"></<span class="name">p</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">template</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">my-paragraph</span>></span><span class="tag"></<span class="name">my-paragraph</span>></span></span><br><span class="line"><span class="tag"><<span class="name">my-paragraph</span>></span><span class="tag"></<span class="name">my-paragraph</span>></span></span><br></pre></td></tr></table></figure><p>通过元素上的属性设置</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">my-paragraph</span> <span class="attr">margin</span>=<span class="string">"10"</span>></span><span class="tag"></<span class="name">my-paragraph</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">template</span> <span class="attr">id</span>=<span class="string">"my-paragraph"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">style</span>></span><span class="css"></span></span><br><span class="line"><span class="css"> <span class="selector-tag">p</span> {</span></span><br><span class="line"><span class="css"> <span class="attribute">color</span>: white;</span></span><br><span class="line"><span class="css"> <span class="attribute">background-color</span>: <span class="number">#666</span>;</span></span><br><span class="line"><span class="css"> <span class="attribute">padding</span>: <span class="number">5px</span>;</span></span><br><span class="line"><span class="css"> }</span></span><br><span class="line"><span class="css"> </span><span class="tag"></<span class="name">style</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">p</span>></span>我的段落<span class="tag"></<span class="name">p</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">template</span>></span></span><br></pre></td></tr></table></figure><p>通过挂载到独立的自定义元素,并将template的内容放在shadowroot下即可,当属性改变时改变对应html原生元素样式,还可以监听事件等.</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">MyParagraph</span> <span class="keyword">extends</span> <span class="title">HTMLElement</span> </span>{</span><br><span class="line"> <span class="keyword">static</span> observedAttributes = [<span class="string">"margin"</span>];</span><br><span class="line"> <span class="function"><span class="title">constructor</span>(<span class="params"></span>)</span> {</span><br><span class="line"> <span class="built_in">super</span>();</span><br><span class="line"> <span class="keyword">let</span> template = <span class="built_in">document</span>.getElementById(<span class="string">"my-paragraph"</span>);</span><br><span class="line"> <span class="keyword">let</span> templateContent = template.content;</span><br><span class="line"> <span class="built_in">this</span>._shadowRoot = <span class="built_in">this</span>.attachShadow({ <span class="attr">mode</span>: <span class="string">"open"</span> });</span><br><span class="line"> <span class="built_in">this</span>._shadowRoot.appendChild(templateContent.cloneNode(<span class="literal">true</span>));</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="title">connectedCallback</span>(<span class="params"></span>)</span> {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">"自定义元素添加至页面。"</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="title">attributeChangedCallback</span>(<span class="params">name, oldValue, newValue</span>)</span> {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">`属性 <span class="subst">${name}</span> 已变更。从<span class="subst">${oldValue}</span>变为<span class="subst">${newValue}</span>`</span>);</span><br><span class="line"> <span class="keyword">if</span> (name == <span class="string">"margin"</span>) {</span><br><span class="line"> <span class="built_in">this</span>._shadowRoot.querySelector(<span class="string">"p"</span>).style.margin = newValue+<span class="string">'px'</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line">customElements.define(<span class="string">"my-paragraph"</span>, MyParagraph);</span><br></pre></td></tr></table></figure><h4 id="插槽"><a href="#插槽" class="headerlink" title="插槽"></a>插槽</h4><p>可以使用slot元素通过声明式的语法在每个元素实例中显示不同的文本。插槽由其 <code>name</code> 属性标识,并且允许在模板中定义占位符,当在标记中使用该元素时,该占位符可以填充所需的任何 HTML 标记片段。</p><p>在template中声明slot,它可以有一个name属性,在自定义元素中加入想添加的元素,可以设置其slot属性,其与name相匹配,如果slot的name和元素的slot属性均不声明则进行填充.</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">template</span> <span class="attr">id</span>=<span class="string">"my-paragraph"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">style</span>></span><span class="css"></span></span><br><span class="line"><span class="css"> <span class="selector-tag">p</span> {</span></span><br><span class="line"><span class="css"> <span class="attribute">color</span>: white;</span></span><br><span class="line"><span class="css"> <span class="attribute">background-color</span>: <span class="number">#666</span>;</span></span><br><span class="line"><span class="css"> <span class="attribute">padding</span>: <span class="number">5px</span>;</span></span><br><span class="line"><span class="css"> }</span></span><br><span class="line"><span class="css"> </span><span class="tag"></<span class="name">style</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">p</span>></span>我的段落<span class="tag"></<span class="name">p</span>></span></span><br><span class="line"> <span class="comment"><!-- <slot></slot> --></span></span><br><span class="line"> <span class="tag"><<span class="name">slot</span> <span class="attr">name</span>=<span class="string">"text"</span>></span><span class="tag"></<span class="name">slot</span>></span></span><br><span class="line"><span class="tag"></<span class="name">template</span>></span></span><br><span class="line"><span class="tag"><<span class="name">my-paragraph</span> <span class="attr">margin</span>=<span class="string">"10"</span>></span><span class="tag"></<span class="name">my-paragraph</span>></span></span><br><span class="line"><span class="tag"><<span class="name">my-paragraph</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">h1</span> <span class="attr">slot</span>=<span class="string">"text"</span>></span>Hi this is default<span class="tag"></<span class="name">h1</span>></span></span><br><span class="line"><span class="tag"></<span class="name">my-paragraph</span>></span></span><br></pre></td></tr></table></figure><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">hello-world</span> <span class="attr">name</span>=<span class="string">"Craig"</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">template</span> <span class="attr">shadowroot</span>=<span class="string">"closed"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">slot</span> <span class="attr">name</span>=<span class="string">"msgtext"</span> <span class="attr">class</span>=<span class="string">"hw-text"</span>></span><span class="tag"></<span class="name">slot</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">slot</span>></span><span class="tag"></<span class="name">slot</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">template</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">h1</span> <span class="attr">slot</span>=<span class="string">"msgtext"</span>></span>Hello Default!<span class="tag"></<span class="name">h1</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">p</span>></span>This text will become part of the component.<span class="tag"></<span class="name">p</span>></span></span><br><span class="line"></span><br><span class="line"><span class="tag"></<span class="name">hello-world</span>></span></span><br></pre></td></tr></table></figure><p>这相当于给了一定的灵活度.</p><h2 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h2><ol><li><a href="https://developer.mozilla.org/zh-CN/docs/Web/API/Web_components">Web Component - Web API | MDN (mozilla.org)</a></li><li><a href="https://zh.javascript.info/web-components">Web components (javascript.info)</a></li><li><a href="https://www.ruanyifeng.com/blog/2019/08/web_components.html">Web Components 入门实例教程 - 阮一峰的网络日志 (ruanyifeng.com)</a></li><li><a href="https://github.com/mdn/web-components-examples/tree/main/word-count-web-component">web-components-examples/word-count-web-component at main · mdn/web-components-examples (github.com)</a></li><li><a href="https://kinsta.com/blog/web-components/">A Complete Introduction to Web Components in 2024 (kinsta.com)</a></li><li><a href="https://www.robinwieruch.de/web-components-tutorial/">Web Components Tutorial for Beginners [2019] (robinwieruch.de)</a></li></ol><link rel="stylesheet" href="/css/spoiler.css" type="text/css"><script src="/js/spoiler.js" type="text/javascript" async></script>]]></content>
<summary type="html"><p>Web components 是用于创建独立组件的一组标准:自定义 HTML 元素,它们具有自己的属性和方法,封装好的 DOM 和样式。</p></summary>
<category term="web" scheme="https://www.sekyoro.top/tags/web/"/>
</entry>
<entry>
<title>也许需要知道的c++概念</title>
<link href="https://www.sekyoro.top/2024/10/09/%E4%B9%9F%E8%AE%B8%E9%9C%80%E8%A6%81%E7%9F%A5%E9%81%93%E7%9A%84c-%E6%A6%82%E5%BF%B5/"/>
<id>https://www.sekyoro.top/2024/10/09/%E4%B9%9F%E8%AE%B8%E9%9C%80%E8%A6%81%E7%9F%A5%E9%81%93%E7%9A%84c-%E6%A6%82%E5%BF%B5/</id>
<published>2024-10-09T15:34:28.000Z</published>
<updated>2024-12-24T13:01:18.686Z</updated>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="\assets\js\Meting.min.js"></script><p>在学习c++的过程中时不时冒出一些概念和常用语,但是却苦于不得其解,这里整理一下.主要来源是cppreference.<br><span id="more"></span></p><h2 id="三-五-零原则"><a href="#三-五-零原则" class="headerlink" title="三/五/零原则"></a>三/五/零原则</h2><h3 id="三原则"><a href="#三原则" class="headerlink" title="三原则"></a>三原则</h3><p>如果某个类需要用户定义的<strong>析构函数</strong>、用户定义的<strong>复制构造函数</strong>或用户定义的<strong>复制赋值运算符</strong>,那么它几乎肯定需要全部三个。</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">MyLockGuard</span> {</span></span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line"> <span class="keyword">using</span> mutext_type = T;</span><br><span class="line"> <span class="function"><span class="keyword">explicit</span> <span class="title">MyLockGuard</span><span class="params">(T t_mutex)</span> : _MyMutex(t_mutex) {</span> _MyMutex.<span class="built_in">lock</span>(); }</span><br><span class="line"> <span class="built_in">MyLockGuard</span>(T& _Mtx, std::<span class="keyword">adopt_lock_t</span>) <span class="keyword">noexcept</span> : _MyMutex(_Mtx) {}</span><br><span class="line"> <span class="built_in">MyLockGuard</span>(<span class="keyword">const</span> MyLockGuard&) = <span class="keyword">delete</span>;</span><br><span class="line"> MyLockGuard& <span class="keyword">operator</span>=(<span class="keyword">const</span> MyLockGuard&) = <span class="keyword">delete</span>;</span><br><span class="line"></span><br><span class="line"> ~<span class="built_in">MyLockGuard</span>() <span class="keyword">noexcept</span> { _MyMutex.<span class="built_in">unlock</span>(); }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span>:</span><br><span class="line"> T& _MyMutex;</span><br><span class="line">};</span><br></pre></td></tr></table></figure><h3 id="五复制"><a href="#五复制" class="headerlink" title="五复制"></a>五复制</h3><p>因为用户定义(包括 = default 或 = delete)的<strong>析构函数</strong>、<strong>复制构造函数</strong>或<strong>复制赋值运算符</strong>,会阻止隐式定义移动构造函数和移动赋值运算符,所以<strong>任何想要移动语义的类必须声明全部五个特殊成员函数</strong></p><p>与三原则不同的是,<strong>不提供移动构造函数和移动赋值运算符通常不是错误</strong>,但会损失性能。</p><h3 id="零原则"><a href="#零原则" class="headerlink" title="零原则"></a>零原则</h3><p>有自定义析构函数、复制/移动构造函数或复制/移动赋值运算符的类应该专门处理所有权(这遵循单一责任原则)。其他类都不应该拥有自定义的析构函数、复制/移动构造函数或复制/移动赋值运算符</p><p>当有意<strong>将某个基类用于多态用途时,可能需要将它的析构函数声明为 public 和 virtual</strong>。由于<strong>这会阻止生成隐式移动(并弃用隐式复制)</strong>,因此必须将各特殊成员函数定义为 = default</p><h2 id="类模板实参推导-CTAD"><a href="#类模板实参推导-CTAD" class="headerlink" title="类模板实参推导(CTAD)"></a>类模板实参推导(CTAD)</h2><p>为了实例化一个类模板,需要知晓每个模板实参,但并非每个模板实参都必须指定。在下列语境中,<strong>编译器会从初始化式的类型推导缺失的模板实参</strong>:</p><ul><li>任意指定变量及变量模板的初始化的声明,其声明的类型是类模板(可有 cv 限定)</li></ul><h2 id="具名返回值优化"><a href="#具名返回值优化" class="headerlink" title="具名返回值优化"></a>具名返回值优化</h2><h2 id="ADL"><a href="#ADL" class="headerlink" title="ADL"></a>ADL</h2><h2 id="未定义行为"><a href="#未定义行为" class="headerlink" title="未定义行为"></a>未定义行为</h2><p><a href="https://zh.cppreference.com/w/cpp/language/rule_of_three#.E4.BA.94.E4.B9.8B.E6.B3.95.E5.88.99">More</a></p><h2 id="相关资料"><a href="#相关资料" class="headerlink" title="相关资料"></a>相关资料</h2><ol><li><a href="https://en.cppreference.com/w/">cppreference.com</a></li><li><a href="https://www.learncpp.com/">Learn C++ – Skill up with our free tutorials (learncpp.com)</a></li><li><a href="https://github.com/fffaraz/awesome-cpp?tab=readme-ov-file#networking">fffaraz/awesome-cpp: A curated list of awesome C++ (or C) frameworks, libraries, resources, and shiny things. Inspired by awesome-… stuff. (github.com)</a></li><li><a href="https://c.biancheng.net/index.html">C语言中文网:C语言程序设计门户网站(入门教程、编程软件) (biancheng.net)</a></li></ol><link rel="stylesheet" href="/css/spoiler.css" type="text/css"><script src="/js/spoiler.js" type="text/javascript" async></script>]]></content>
<summary type="html"><p>在学习c++的过程中时不时冒出一些概念和常用语,但是却苦于不得其解,这里整理一下.主要来源是cppreference.<br></summary>
<category term="c++" scheme="https://www.sekyoro.top/tags/c/"/>
</entry>
<entry>
<title>现代c++并发深入</title>
<link href="https://www.sekyoro.top/2024/10/05/%E7%8E%B0%E4%BB%A3c-%E5%B9%B6%E5%8F%91%E6%B7%B1%E5%85%A5/"/>
<id>https://www.sekyoro.top/2024/10/05/%E7%8E%B0%E4%BB%A3c-%E5%B9%B6%E5%8F%91%E6%B7%B1%E5%85%A5/</id>
<published>2024-10-05T03:09:17.000Z</published>
<updated>2024-10-15T11:58:54.006Z</updated>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="\assets\js\Meting.min.js"></script><p>更加深入的探讨并发.<br><span id="more"></span></p><h2 id="std-thread"><a href="#std-thread" class="headerlink" title="std::thread"></a>std::thread</h2><p>多线程的构造,使用std::thread时我们需要注意哪些?</p><h3 id="可以接受哪些参数"><a href="#可以接受哪些参数" class="headerlink" title="可以接受哪些参数"></a>可以接受哪些参数</h3><p>std::thread接受一个<strong>可调用对象</strong>和其参数.</p><p><strong><em>可调用\</em> *(Callable)*</strong> 类型是可应用INVOKE和INVOKE操作(例如用于 std::function、std::bind和 std::thread::thread)的类型.</p><p>如果满足下列条件,那么类型 <code>T</code> 是<em>可调用</em> <em>(Callable)</em> 的:</p><p>给定</p><ul><li><code>T</code> 类型的对象 <code>f</code></li><li>适合的实参类型列表 <code>ArgTypes</code></li><li>适合的返回类型 <code>R</code></li></ul><p>那么下列表达式必须合法:</p><div class="table-container"><table><thead><tr><th style="text-align:center">表达式</th><th style="text-align:center">要求</th></tr></thead><tbody><tr><td style="text-align:center">INVOKE<R>(f, [std::declval<ArgTypes>()…)</td><td style="text-align:center">该表达式在不求值语境中良构</td></tr></tbody></table></div><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Task_2</span> {</span></span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="keyword">void</span> <span class="title">operator</span><span class="params">()</span><span class="params">()</span> </span>{ std::<span class="built_in">puts</span>(<span class="string">"operator()()const\n"</span>); }</span><br><span class="line"> <span class="function"><span class="keyword">operator</span> <span class="title">bool</span><span class="params">()</span> </span>{ <span class="keyword">return</span> <span class="literal">true</span>; }</span><br><span class="line">}; <span class="comment">// 函数对象</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">X</span>{</span></span><br><span class="line"> <span class="function"><span class="keyword">void</span> <span class="title">task_run</span><span class="params">(<span class="keyword">int</span>)</span> <span class="keyword">const</span></span>;</span><br><span class="line">}</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">f</span><span class="params">(<span class="keyword">int</span>)</span></span>; <span class="comment">// 函数</span></span><br><span class="line">X x;</span><br><span class="line">std::thread t{&X::task_run,&x,<span class="number">3</span>}; <span class="comment">//成员指针必须和对象一起使用,这是唯一标准用法,成员指针不可以转换到函数指针单独使用,即使是非静态成员函数没有使用任何数据成员.</span></span><br><span class="line">std::thread t{ std::<span class="built_in">bind</span>(&X::task_run, &x ,<span class="number">3</span>) };</span><br><span class="line">std::thread t{[]{std::<span class="built_in">puts</span>(<span class="string">"Hi!"</span>)}}; <span class="comment">// lambda函数</span></span><br><span class="line">Task_2 task_2{};</span><br><span class="line"> std::thread t{task_2}; <span class="comment">// 左值 左值引用</span></span><br><span class="line"> std::thread ta{Task_2{}}; <span class="comment">// 临时对象</span></span><br><span class="line"> std::thread ta1{[] { std::<span class="built_in">puts</span>(<span class="string">"Hi"</span>); }}; <span class="comment">// 临时对象</span></span><br><span class="line"> std::thread ta2{h}; <span class="comment">// 左值</span></span><br></pre></td></tr></table></figure><p>重要的构造函数如下(MSVC实现)</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="keyword">template</span> <<span class="class"><span class="keyword">class</span> _<span class="title">Fn</span>, <span class="title">class</span>... _<span class="title">Args</span>, <span class="title">enable_if_t</span><</span>!is_same_v<_Remove_cvref_t<_Fn>, thread>, <span class="keyword">int</span>> = <span class="number">0</span>></span><br><span class="line"> _NODISCARD_CTOR_THREAD <span class="keyword">explicit</span> <span class="built_in">thread</span>(_Fn&& _Fx, _Args&&... _Ax) {</span><br><span class="line"> _Start(_STD forward<_Fn>(_Fx), _STD forward<_Args>(_Ax)...);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">template</span> <<span class="class"><span class="keyword">class</span> _<span class="title">Fn</span>, <span class="title">class</span>... _<span class="title">Args</span>></span></span><br><span class="line"> <span class="keyword">void</span> _Start(_Fn&& _Fx, _Args&&... _Ax) {</span><br><span class="line"> <span class="keyword">using</span> _Tuple = tuple<<span class="keyword">decay_t</span><_Fn>, <span class="keyword">decay_t</span><_Args>...>;</span><br><span class="line"> <span class="keyword">auto</span> _Decay_copied = _STD make_unique<_Tuple>(_STD forward<_Fn>(_Fx), _STD forward<_Args>(_Ax)...); </span><br><span class="line"> <span class="keyword">constexpr</span> <span class="keyword">auto</span> _Invoker_proc = _Get_invoke<_Tuple>(make_index_sequence<<span class="number">1</span> + <span class="keyword">sizeof</span>...(_Args)>{});</span><br><span class="line"></span><br><span class="line"> _Thr._Hnd =</span><br><span class="line"> <span class="keyword">reinterpret_cast</span><<span class="keyword">void</span>*>(_CSTD _beginthreadex(<span class="literal">nullptr</span>, <span class="number">0</span>, _Invoker_proc, _Decay_copied.<span class="built_in">get</span>(), <span class="number">0</span>, &_Thr._Id));</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (_Thr._Hnd) { <span class="comment">// ownership transferred to the thread</span></span><br><span class="line"> (<span class="keyword">void</span>) _Decay_copied.<span class="built_in">release</span>();</span><br><span class="line"> } <span class="keyword">else</span> { <span class="comment">// failed to start thread</span></span><br><span class="line"> _Thr._Id = <span class="number">0</span>;</span><br><span class="line"> _Throw_Cpp_error(_RESOURCE_UNAVAILABLE_TRY_AGAIN);</span><br><span class="line"> }</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><p>首先根据传入的可调用对象和参数使用转发引用,并确保传入的可调用对象去掉const/volatile引用后不等于std::thread. 如果传入左值,则为左值引用,调用_Start时传入左引用,否则传入右值. forward内部根据传入的是左值还是右值转为对应的引用</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">_EXPORT_STD <span class="keyword">template</span> <<span class="class"><span class="keyword">class</span> _<span class="title">Ty</span>></span></span><br><span class="line"><span class="function">_NODISCARD _MSVC_INTRINSIC <span class="keyword">constexpr</span> _Ty&& <span class="title">forward</span><span class="params">(<span class="keyword">remove_reference_t</span><_Ty>& _Arg)</span> <span class="keyword">noexcept</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">static_cast</span><_Ty&&>(_Arg);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">_EXPORT_STD <span class="keyword">template</span> <<span class="class"><span class="keyword">class</span> _<span class="title">Ty</span>></span></span><br><span class="line"><span class="function">_NODISCARD _MSVC_INTRINSIC <span class="keyword">constexpr</span> _Ty&& <span class="title">forward</span><span class="params">(<span class="keyword">remove_reference_t</span><_Ty>&& _Arg)</span> <span class="keyword">noexcept</span> </span>{</span><br><span class="line"> <span class="keyword">static_assert</span>(!is_lvalue_reference_v<_Ty>, <span class="string">"bad forward call"</span>);</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">static_cast</span><_Ty&&>(_Arg);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>在_Start中使用tuple保存函数和参数类型,使用make_unique得到指向_tuple的指针,使用传入的左值或者右值,比如如果参数如果传递一个左值num(即使是引用),会使用拷贝构造,如果传入一个右值会调用对应移动构造,</p><p>这里需要介绍一些左值和右值,以及模板编程中<code>template<T> void( std::remove_reference_t<T>&)</code>与<code>template<T> void( std::remove_reference_t<T>&&)</code></p><p><a href="https://zh.cppreference.com/w/cpp/language/value_category">值类别 - cppreference.com</a></p><p>变量的类型是右值引用,由它的名字构成的表达式仍是左值表达式</p><p>转换到左值引用类型的转型表达式以及转换到函数的右值引用类型的转型表达式是左值;而转换到对象的右值引用类型的类型转换表达式是亡值,也就是右值,通过static_cast<T&&>这种方式返回了右值</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">template</span> <<span class="class"><span class="keyword">class</span> _<span class="title">Tuple</span>, <span class="title">size_t</span>... _<span class="title">Indices</span>></span></span><br><span class="line">_NODISCARD <span class="keyword">static</span> <span class="keyword">constexpr</span> <span class="keyword">auto</span> _Get_invoke(index_sequence<_Indices...>) <span class="keyword">noexcept</span> {</span><br><span class="line"> <span class="keyword">return</span> &_Invoke<_Tuple, _Indices...>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">template</span> <<span class="class"><span class="keyword">class</span> _<span class="title">Tuple</span>, <span class="title">size_t</span>... _<span class="title">Indices</span>></span></span><br><span class="line"><span class="keyword">static</span> <span class="keyword">unsigned</span> <span class="keyword">int</span> __stdcall _Invoke(<span class="keyword">void</span>* _RawVals) <span class="keyword">noexcept</span> <span class="comment">/* terminates */</span> {</span><br><span class="line"> <span class="comment">// adapt invoke of user's callable object to _beginthreadex's thread procedure</span></span><br><span class="line"> <span class="keyword">const</span> unique_ptr<_Tuple> _FnVals(<span class="keyword">static_cast</span><_Tuple*>(_RawVals));</span><br><span class="line"> _Tuple& _Tup = *_FnVals.<span class="built_in">get</span>(); <span class="comment">// avoid ADL, handle incomplete types</span></span><br><span class="line"> <span class="function">_STD <span class="title">invoke</span><span class="params">(_STD move(_STD get<_Indices>(_Tup))...)</span></span>;</span><br><span class="line"> _Cnd_do_broadcast_at_thread_exit(); <span class="comment">// TRANSITION, ABI</span></span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><code>_beginthreadex</code>是windows上创建多线程的API,传入一个函数指针(也就是_Invoke)和指向一个tuple(包含函数和参数)的指针,在<code>_Invoke</code>中, _STD invoke(_STD move(_STD get<_Indices>(_Tup))…);`相当于根据可调用对象及其参数进行了调用</p><h3 id="传递的参数"><a href="#传递的参数" class="headerlink" title="传递的参数"></a>传递的参数</h3><p>根据源码实现,传递的参数会在thread对象中创建新值.</p><p><code>auto _Decay_copied = _STD make_unique<_Tuple>(_STD forward<_Fn>(_Fx), _STD forward<_Args>(_Ax)...);</code>创建,而_Tuple中的类型就是左值,<code>using _Tuple = tuple<decay_t<_Fn>, decay_t<_Args>...>;</code>,所以创建时会调用拷贝或移动构造创建新的值,而在实际调用对应可调用对象时,又会使用右值<code>_STD invoke(_STD move(_STD get<_Indices>(_Tup))...);</code>,这意味着,即使函数参数是引用,</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">f</span><span class="params">(<span class="keyword">const</span> <span class="keyword">int</span>& n)</span></span>{};</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span> </span>{</span><br><span class="line"><span class="keyword">auto</span> num{<span class="number">5</span>};</span><br><span class="line">std::<span class="built_in">jthread</span>(f,num);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>上面的num首先会在std::jthread中创建变量,auto _Decay<em>copied = std::make_unique<_Tuple</em>>(std::forward\<Fn>(__Fn),std::forward\<Args>(args)…);</p><p>使用invoke调用,std::invoke(std::move(std::get<_Indices>(_Tup))…);调用函数时使用了<code>std::move</code>作为右值传递,所以有<code>const int& n = std::move(_num)</code>,如果不加上const会发生运行编译错误,此外这样也支持了只能移动的对象.</p><p>如何使得传递引用呢,</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">f</span><span class="params">(<span class="keyword">const</span> <span class="keyword">int</span>& n)</span></span>{};</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span> </span>{</span><br><span class="line"><span class="keyword">auto</span> num{<span class="number">5</span>};</span><br><span class="line">std::<span class="built_in">jthread</span>(f,std::<span class="built_in">ref</span>(num));</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>实现中将类型先经过 <code>decay</code> 处理,如果要传递引用,则必须用类包装,使用 <code>std::ref</code> 函数会返回一个包装对象</p><p>使用可调用对象的方式是利用<code>_Invoke</code>,其参数是上面的<code>_Decay_copied</code>,将其转为unique_ptr,再通过智能指针转为_Tup&,然后使用 <code>std::invoke</code> 进行调用</p><p>std::ref是一个对象,但是使用时隐式转为引用,所以在构造时直接拷贝赋值std::reference_wrapper\<T>.</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"> <span class="keyword">int</span> num2{<span class="number">3</span>};</span><br><span class="line"> <span class="keyword">auto</span> n2 = std::<span class="built_in">ref</span>(num2);</span><br><span class="line"> <span class="keyword">auto</span> ttc = n2;</span><br><span class="line"> std::cout << ttc << std::endl;</span><br><span class="line"> ttc += <span class="number">12</span>;</span><br><span class="line">std::cout << n2 << std::endl;</span><br></pre></td></tr></table></figure><h3 id="join与detach"><a href="#join与detach" class="headerlink" title="join与detach"></a>join与detach</h3><p>detach() 是线程分离,线程对象放弃了线程资源的所有权,此时thread根本没有关联任何线程.调用 join() 是:“阻塞当前线程直至 *this 所标识的线程结束其执行”,线程对象都没有线程,就不需要阻塞了.</p><h3 id="不能拷贝构造-赋值与转移所有权"><a href="#不能拷贝构造-赋值与转移所有权" class="headerlink" title="不能拷贝构造/赋值与转移所有权"></a>不能拷贝构造/赋值与转移所有权</h3><p>传入可调用对象以及参数,构造 <code>std::thread</code> 对象,启动线程,而线程对象拥有了线程的所有权,线程是一种系统资源,所以可称作“<em>线程资源</em>”.</p><p>std::thread 不可复制.两个 std::thread 对象不可表示一个线程,std::thread 对线程资源是独占所有权.而<strong>移动</strong>操作可以将一个 <code>std::thread</code> 对象的线程资源所有权转移给另一个 <code>std::thread</code> 对象.</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="built_in">std</span>::thread t{ [] {</span><br><span class="line"> <span class="built_in">std</span>::<span class="built_in">cout</span> << <span class="built_in">std</span>::this_thread::get_id() << <span class="string">'\n'</span>;</span><br><span class="line"> } };</span><br><span class="line"> <span class="built_in">std</span>::<span class="built_in">cout</span> << t.joinable() << <span class="string">'\n'</span>; <span class="comment">// 线程对象 t 当前关联了活跃线程 打印 1</span></span><br><span class="line"> <span class="built_in">std</span>::thread t2{ <span class="built_in">std</span>::move(t) }; <span class="comment">// 将 t 的线程资源的所有权移交给 t2</span></span><br><span class="line"> <span class="built_in">std</span>::<span class="built_in">cout</span> << t.joinable() << <span class="string">'\n'</span>; <span class="comment">// 线程对象 t 当前没有关联活跃线程 打印 0</span></span><br><span class="line"> <span class="comment">//t.join(); // Error! t 没有线程资源</span></span><br><span class="line"> t2.join(); <span class="comment">// t2 当前持有线程资源</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">std::thread <span class="title">f</span><span class="params">()</span></span>{</span><br><span class="line"> std::thread t{ [] {} };</span><br><span class="line"> <span class="keyword">return</span> t;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span></span>{</span><br><span class="line"> std::thread rt = <span class="built_in">f</span>();</span><br><span class="line"> rt.<span class="built_in">join</span>();</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><code>return t</code> <em>重载决议</em><a href="https://mq-b.github.io/ModernCpp-ConcurrentProgramming-Tutorial/md/02使用线程.html#footnote1">[1]</a>选择到了<strong>移动构造</strong>,将 <code>t</code> 线程资源的所有权转移给函数调用 <code>f()</code> 返回的临时 <code>std::thread</code> 对象中,然后这个临时对象再用来初始化 <code>rt</code> ,临时对象是右值表达式,这里一样选择到<strong>移动构造</strong>,将临时对象的线程资源所有权移交给 <code>rt</code>.此时 <code>rt</code> 具有线程资源的所有权,由它调用 <code>join()</code> 正常析构</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">void f(std::thread t){</span><br><span class="line"> t.join();</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">int main(){</span><br><span class="line"> std::thread t{ [] {} };</span><br><span class="line"> f(std::move(t));</span><br><span class="line"> f(std::thread{ [] {} });</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><code>std::move</code> 将 t 转换为了一个右值表达式,初始化函数<code>f</code> 形参 <code>t</code>,选择到了移动构造转移线程资源的所有权,在函数中调用 <code>t.join()</code> 后正常析构.<code>std::thread{ [] {} }</code> 构造了一个临时对象,本身就是右值表达式,初始化函数<code>f</code> 形参 <code>t</code>,移动构造转移线程资源的所有权到 <code>t</code>,<code>t.join()</code> 后正常析构.</p><h3 id="std-this-thread"><a href="#std-this-thread" class="headerlink" title="std::this_thread"></a>std::this_thread</h3><p><img data-src="https://s2.loli.net/2024/10/09/z68akUwnbqjS9KH.png" alt="image-20241009225729722"></p><ul><li><p>get_id</p></li><li><p>sleep_for</p></li><li><p>yield</p></li><li><p>sleep_until</p></li></ul><h2 id="数据竞争"><a href="#数据竞争" class="headerlink" title="数据竞争"></a>数据竞争</h2><p><strong>当某个表达式的求值写入某个内存位置,而另一求值读或修改同一内存位置时</strong>,称这些表达式冲突<strong>.</strong>拥有两个冲突的求值的程序就有数据竞争,除非</p><ul><li>两个求值都在同一线程上,或者在同一信号处理函数中执行,或</li><li>两个冲突的求值都是原子操作(见 std::atomic),或</li><li>一个冲突的求值发生早于 另一个(见 std::memory_order)</li></ul><p>如果出现数据竞争,那么程序的行为未定义.</p><h3 id="互斥量"><a href="#互斥量" class="headerlink" title="互斥量"></a>互斥量</h3><blockquote><p>互斥量用于保护多线程下的共享数据的读写</p></blockquote><p>互斥量(Mutex),又常被称为互斥锁、互斥体(或者直接被称作“锁”),是一种用来保护临界区的特殊对象,其相当于实现了一个公共的“<strong>标志位</strong>”.它可以处于锁定(locked)状态,也可以处于解锁(unlocked)状态:</p><ol><li>如果互斥量是锁定的,通常说某个特定的线程正持有这个锁.</li><li>如果没有线程持有这个互斥量,那么这个互斥量就处于解锁状态.</li></ol><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">std::mutex m{};</span><br><span class="line">m.<span class="built_in">lock</span>();</span><br><span class="line"><span class="comment">// do something...</span></span><br><span class="line">m.<span class="built_in">unlock</span>()</span><br></pre></td></tr></table></figure><p>如果多个线程中,其中一个线程在执行互斥区操作,其他线程执行到了m.lock()时会阻塞直到m.unlock释放.</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">std::mutex m{};</span><br><span class="line">m.<span class="built_in">try_lock</span>();</span><br><span class="line"><span class="comment">// do something...</span></span><br><span class="line">m.<span class="built_in">unlock</span>()</span><br></pre></td></tr></table></figure><p>try_lock不会阻塞,而是会返回一个bool值,如果失败了就返回false,上锁成功返回true</p><h3 id="如何管理互斥量"><a href="#如何管理互斥量" class="headerlink" title="如何管理互斥量"></a>如何管理互斥量</h3><p>使用<code>std::lock_guard</code>与<code>std::scoped_lock</code></p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">MyLockGuard</span> {</span></span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line"> <span class="keyword">using</span> mutext_type = T;</span><br><span class="line"> <span class="function"><span class="keyword">explicit</span> <span class="title">MyLockGuard</span><span class="params">(T t_mutex)</span> : _MyMutex(t_mutex) {</span> _MyMutex.<span class="built_in">lock</span>(); }</span><br><span class="line"> <span class="built_in">MyLockGuard</span>(T& _Mtx, std::<span class="keyword">adopt_lock_t</span>) <span class="keyword">noexcept</span> : _MyMutex(_Mtx) {}</span><br><span class="line"> <span class="built_in">MyLockGuard</span>(<span class="keyword">const</span> MyLockGuard&) = <span class="keyword">delete</span>;</span><br><span class="line"> MyLockGuard& <span class="keyword">operator</span>=(<span class="keyword">const</span> MyLockGuard&) = <span class="keyword">delete</span>;</span><br><span class="line"></span><br><span class="line"> ~<span class="built_in">MyLockGuard</span>() <span class="keyword">noexcept</span> { _MyMutex.<span class="built_in">unlock</span>(); }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span>:</span><br><span class="line"> T& _MyMutex;</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>提供便利 RAII 风格机制的互斥包装器,它在作用域块的存在期间占有一或多个互斥体.不可复制、移动.当创建对象时,它尝试取得给定互斥体的所有权.当控制离开创建对象的作用域时,析构并释放互斥体</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">foor_guard</span><span class="params">()</span> </span>{</span><br><span class="line"> std::lock_guard<std::mutex> lock{m};</span><br><span class="line"> std::cout << <span class="string">"foor"</span> << std::endl;</span><br><span class="line">}</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">add_to_list</span><span class="params">(<span class="keyword">int</span> n, std::list<<span class="keyword">int</span>>& list)</span> </span>{</span><br><span class="line"> <span class="function">std::vector<<span class="keyword">int</span>> <span class="title">numbers</span><span class="params">(n + <span class="number">1</span>)</span></span>;</span><br><span class="line"> std::<span class="built_in">iota</span>(numbers.<span class="built_in">begin</span>(), numbers.<span class="built_in">end</span>(), <span class="number">0</span>);</span><br><span class="line"> <span class="keyword">int</span> sum = std::<span class="built_in">accumulate</span>(numbers.<span class="built_in">begin</span>(), numbers.<span class="built_in">end</span>(), <span class="number">0</span>);</span><br><span class="line"> {</span><br><span class="line"> std::lock_guard<std::mutex> lc{m};</span><br><span class="line"> list.<span class="built_in">push_back</span>(sum);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><code>scoped_lock</code> 类类似,它在作用域块的存在期间占有一或多个互斥体.</p><h3 id="互斥量保护数据的问题"><a href="#互斥量保护数据的问题" class="headerlink" title="互斥量保护数据的问题"></a>互斥量保护数据的问题</h3><p>当使用<code>lock_guard</code>时,如果将指针或者引用传递给外部值,这样就脱离mutex管理了.</p><p><em>简而言之:<strong>切勿将受保护数据的指针或引用传递到互斥量作用域之外</strong>,不然保护将<strong>形同虚设<a href="https://mq-b.github.io/ModernCpp-ConcurrentProgramming-Tutorial/md/03共享数据.html#保护共享数据">共享数据 | 现代C++并发编程教程 (mq-b.github.io)</a></strong></em></p><h3 id="互斥可能导致的死锁"><a href="#互斥可能导致的死锁" class="headerlink" title="互斥可能导致的死锁"></a>互斥可能导致的死锁</h3><p>当有多个互斥量时可能遇到死锁.避免死锁的一般建议是让两个互斥量以相同的顺序上锁,总在互斥量 B 之前锁住互斥量 A,就通常不会死锁</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">std::mutex m1,m2;</span><br><span class="line">std::<span class="keyword">size_t</span> n{};</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">f</span><span class="params">()</span></span>{</span><br><span class="line"> std::lock_guard<std::mutex> lc1{ m1 };</span><br><span class="line"> std::lock_guard<std::mutex> lc2{ m2 };</span><br><span class="line"> ++n;</span><br><span class="line">}</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">f2</span><span class="params">()</span> </span>{</span><br><span class="line"> std::lock_guard<std::mutex> lc1{ m2 };</span><br><span class="line"> std::lock_guard<std::mutex> lc2{ m1 };</span><br><span class="line"> ++n;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>上面代码就有可能死锁. 修改上锁顺序即可.</p><p>但是即使上锁顺序相同,也有可能导致死锁</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">X</span>{</span></span><br><span class="line"> <span class="built_in">X</span>(<span class="keyword">const</span> std::string& str) :object{ str } {}</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">friend</span> <span class="keyword">void</span> <span class="title">swap</span><span class="params">(X& lhs, X& rhs)</span></span>;</span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line"> std::string object;</span><br><span class="line"> std::mutex m;</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">swap</span><span class="params">(X& lhs, X& rhs)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (&lhs == &rhs) <span class="keyword">return</span>;</span><br><span class="line"> std::lock_guard<std::mutex> lock1{ lhs.m }; </span><br><span class="line"> std::lock_guard<std::mutex> lock2{ rhs.m }; </span><br><span class="line"> <span class="built_in">swap</span>(lhs.object, rhs.object);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">X <span class="selector-tag">a</span>{"<span class="selector-tag">a</span>"},<span class="selector-tag">b</span>{"<span class="selector-tag">b</span>"};</span><br><span class="line">std::thread t{[&]{<span class="built_in">swap</span>(a,b);}};</span><br><span class="line">std::thread t2{[&]{<span class="built_in">swap</span>(b,a);}};</span><br></pre></td></tr></table></figure><p>解决方法是是使用<code>std::lock</code>,可以同时对多个互斥量上锁,如果已经上锁会抛出异常并unlock解锁这些互斥量,或者使用刚才的std::scoped_lock,提供与lock_guard同样的RAII包装.</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">swap</span><span class="params">(Xa& lhs, Xa& rhs)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (&lhs == &rhs) <span class="keyword">return</span>;</span><br><span class="line"> std::<span class="built_in">lock</span>(lhs.m, rhs.m);</span><br><span class="line"> std::lock_guard<std::mutex> lock1{lhs.m,std::adopt_lock};</span><br><span class="line"> std::lock_guard<std::mutex> lock2{rhs.m,std::adopt_lock};</span><br><span class="line"> <span class="built_in">swap</span>(lhs.object, rhs.object);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">swap</span><span class="params">(Xa& lhs, Xa& rhs)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (&lhs == &rhs) <span class="keyword">return</span>;</span><br><span class="line"> std::<span class="built_in">scoped_lock</span>(lhs.m, rhs.m);</span><br><span class="line"> <span class="built_in">swap</span>(lhs.object, rhs.object);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>使用 <code>std::scoped_lock</code> 可以将所有 <code>std::lock</code> 替换掉,减少错误发生</p><h3 id="Tips-for-avoiding-dead-lock"><a href="#Tips-for-avoiding-dead-lock" class="headerlink" title="Tips for avoiding dead lock"></a>Tips for avoiding dead lock</h3><h4 id="避免嵌套锁"><a href="#避免嵌套锁" class="headerlink" title="避免嵌套锁"></a>避免嵌套锁</h4><p>线程获取一个锁时,就别再获取第二个锁.每个线程只持有一个锁,自然不会产生死锁.<strong>如果必须要获取多个锁,使用 <code>std::lock</code>或<code>std::scoped_lock</code></strong></p><h4 id="避免在持有锁时调用外部代码"><a href="#避免在持有锁时调用外部代码" class="headerlink" title="避免在持有锁时调用外部代码"></a>避免在持有锁时调用外部代码</h4><p>因为代码是外部提供的,所以没办法确定外部要做什么.外部程序可能做任何事情,包括获取锁.在持有锁的情况下,如果用外部代码要获取一个锁,就会违反第一个指导意见,并造成死锁(有时这是无法避免的)</p><h4 id="使用固定顺序获取锁"><a href="#使用固定顺序获取锁" class="headerlink" title="使用固定顺序获取锁"></a>使用固定顺序获取锁</h4><p>避免死锁</p><h3 id="std-unique-lock"><a href="#std-unique-lock" class="headerlink" title="std::unique_lock"></a>std::unique_lock</h3><p><code>unique_lock</code>更加灵活,它不能拷贝,内部有一个<code>_Owns</code>变量表明是否有锁的拥有权(或者说是否已经上锁),默认构造函数调用时owns为false并上锁</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">_NODISCARD_CTOR_LOCK <span class="keyword">explicit</span> <span class="title">unique_lock</span><span class="params">(_Mutex& _Mtx)</span></span></span><br><span class="line"><span class="function"> : _Pmtx(_STD addressof(_Mtx)), _Owns(false) {</span> <span class="comment">// construct and lock</span></span><br><span class="line"> _Pmtx-><span class="built_in">lock</span>();</span><br><span class="line"> _Owns = <span class="literal">true</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>此外还有<code>std::defer_lock</code>和<code>std::adopt_lock</code>分别表示没有上锁(_Owns为false),构造函数中不会上锁和已经上锁(_Owns为true),,构造函数中不会 上锁</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">unique_lock</span>(_Mutex& _Mtx, <span class="keyword">defer_lock_t</span>) <span class="keyword">noexcept</span></span><br><span class="line"> : _Pmtx(_STD <span class="built_in">addressof</span>(_Mtx)), _Owns(<span class="literal">false</span>) {} <span class="comment">// construct but don't lock</span></span><br><span class="line"><span class="built_in">lock_guard</span>(_Mutex& _Mtx, <span class="keyword">adopt_lock_t</span>) <span class="keyword">noexcept</span> <span class="comment">// strengthened</span></span><br><span class="line"> : _MyMutex(_Mtx) {} <span class="comment">// construct but don't lock</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><p><code>unique_lock</code>类中也有lock和unlock方法,</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">lock</span><span class="params">()</span> </span>{ <span class="comment">// lock the mutex</span></span><br><span class="line"> _Validate();</span><br><span class="line"> _Pmtx-><span class="built_in">lock</span>();</span><br><span class="line"> _Owns = <span class="literal">true</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">unlock</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (!_Pmtx || !_Owns) {</span><br><span class="line"> _Throw_system_error(errc::operation_not_permitted);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> _Pmtx-><span class="built_in">unlock</span>();</span><br><span class="line"> _Owns = <span class="literal">false</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>简而言之:</p><ul><li>使用 <code>std::defer_lock</code> 构造函数不上锁,要求构造之后上锁</li><li>使用 <code>std::adopt_lock</code> 构造函数不上锁,要求在构造之前互斥量上锁</li><li>默认构造会上锁,要求构造函数之前和构造函数之后都不能再次上锁</li></ul><p>通常建议优先 <code>std::lock_guard</code>,当无法满足你的需求或者显得代码非常繁琐,那么可以考虑使用 <code>std::unique_lock</code></p><p>std::mutex是<strong>不能拷贝也不能移动的量</strong>,在unique_lock中保存了指向它的指针,而unique_lock是可以移动的,所以可以利用unique_lock转移互斥量(准确地说需要互斥量在这些作用域内存活并通过移动构造、赋值进行转移)</p><h3 id="保护共享数据初始化"><a href="#保护共享数据初始化" class="headerlink" title="保护共享数据初始化"></a>保护共享数据初始化</h3><p>一些数据在多线程环境下进行初始化时可能会导致多次初始化,也是数据竞争的行为. 可以采用其他方式进行保护.</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">Data</span>{</span></span><br><span class="line"></span><br><span class="line">};</span><br><span class="line">std::once_flag flag;</span><br><span class="line">std::shared_ptr<Data> data;</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">init_resouce</span><span class="params">()</span> </span>{</span><br><span class="line"> data.<span class="built_in">reset</span>(<span class="keyword">new</span> Data);</span><br><span class="line">}</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">foo</span><span class="params">()</span> </span>{</span><br><span class="line">std::<span class="built_in">call_once</span>(flag,init_resouce);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><code>std::call_once</code>可以接收可调用对象,传入flag表明之及逆行一次初始化,使得线程安全. <strong>静态局部变量初始化在 C++11 是线程安全</strong></p><h3 id="读写锁保护不常更新的数据结构"><a href="#读写锁保护不常更新的数据结构" class="headerlink" title="读写锁保护不常更新的数据结构"></a>读写锁保护不常更新的数据结构</h3><p>有时多线程环境下,一个线程基本只用来写,其他线程用来读. 可以使用<code>std::shared_mutex</code>保证写线程独占权和读线程的访问权.</p><p><code>shared_mutex</code> 类是一个同步原语,可用于保护共享数据不被多个线程同时访问.与便于独占访问的其他互斥体类型不同,shared_mutex 拥有两个访问级别:</p><ul><li><p><em>共享</em> - 多个线程能共享同一互斥体的所有权.</p></li><li><p><em>独占</em> - 仅一个线程能占有互斥.</p></li></ul><p>若一个线程已获取<em>独占</em> 锁(通过 lock、try_lock,则无其他线程能获取该锁(包括<em>共享</em>的).</p><p>若一个线程已获取<em>共享</em> 锁(通过 lock_shared、try_lock_shared),则无其他线程能获取<em>独占</em> 锁,但可以获取<em>共享</em> 锁.</p><p>仅当任何线程均未获取<em>独占</em> 锁时,<em>共享</em> 锁能被多个线程获取.</p><p>在一个线程内,同一时刻只能获取一个锁(<em>共享</em>或<em>独占</em>)</p><p><code>std::shared_lock</code>主要是提供了lock_shared的作用,区分了共享锁和独占锁,如果有独占锁,多个线程不管使用共享锁还是独占锁都需要阻塞,反之如果全是共享锁则不会阻塞(因为全是读操作)</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">std::map<std::string, std::string> data_;</span><br><span class="line">std::shared_mutex mtx;</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">writeData</span><span class="params">()</span> </span>{</span><br><span class="line"> std::lock_guard lg{mtx};</span><br><span class="line"> data_[<span class="string">"fa"</span>] = <span class="string">"af"</span>;</span><br><span class="line">}</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">readData</span><span class="params">()</span> </span>{</span><br><span class="line"> std::shared_lock sl{mtx};</span><br><span class="line"> <span class="keyword">auto</span> it = data_.<span class="built_in">find</span>(<span class="string">"aa"</span>);</span><br><span class="line"> std::cout << (it == data_.<span class="built_in">end</span>() ? it->second : <span class="string">""</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><img data-src="https://s2.loli.net/2024/10/11/Jt2lAn7xmdCULoy.png" alt="image-20241011211429754"></p><h3 id="std-recursive-mutex"><a href="#std-recursive-mutex" class="headerlink" title="std::recursive_mutex"></a>std::recursive_mutex</h3><p>在同一线程多次lock一个普通mutex,是未定义行为. 如果在一个线程多次lock,另一个线程就一直无法拿到锁了. 使用std::recursive_mutex使得同一线程在lock和unlock次数一样的情况下才会真正释放锁.</p><p>它允许同一线程多次锁定同一个互斥量,而不会造成死锁.当同一线程多次对同一个 <code>std::recursive_mutex</code> 进行锁定时,<strong>只有在解锁与锁定次数相匹配时,互斥量才会真正释放</strong>.但它并不影响不同线程对同一个互斥量进行锁定的情况.不同线程对同一个互斥量进行锁定时,会按照互斥量的规则<strong>进行阻塞</strong></p><p>在使用迭代函数中使用锁时可以使用这个互斥量</p><p>通常不直接调用 <code>unlock()</code>,而是使用std::unique_lock与std::lock_guard管理排他性锁定.</p><p>调用<code>lock</code>时所有权层数+1,调用<code>unlock</code>时,如果所有权层数为1,解锁互斥量,否则-1.</p><h3 id="new、delete的线程安全性"><a href="#new、delete的线程安全性" class="headerlink" title="new、delete的线程安全性"></a>new、delete的线程安全性</h3><p>如果标准达到 <strong>C++11</strong>,要求下列<strong>函数</strong>是线程安全的:</p><ul><li><code>new</code>和 <code>delete</code> 运算符的<strong>库</strong>版本</li><li>全局 <code>new</code> 运算符和 <code>delete</code> 运算符的用户替换版本</li><li>std::calloc、std::malloc、std::realloc、std::aligned_alloc](C++17 起)、std::free</li></ul><p><strong>内存分配、释放操作是线程安全</strong>,构造和析构不涉及共享资源.而<strong>局部对象 <code>p</code> 对于每个线程来说是独立的</strong>.换句话说,每个线程都有其自己的 <code>p</code> 对象实例,因此它们不会共享同一个对象,自然没有数据竞争</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">T* p = <span class="literal">nullptr</span>;</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">f</span><span class="params">()</span></span>{</span><br><span class="line"> p = <span class="keyword">new</span> T{}; <span class="comment">// 存在数据竞争</span></span><br><span class="line"> <span class="keyword">delete</span> p;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>如果 <code>p</code> 是全局对象(或者外部的,只要可被多个线程读写),<strong>多个线程同时对其进行访问和修改时,就可能会导致数据竞争和未定义行为.因此,确保全局对象的线程安全访问通常需要额外的同步措施,比如互斥量或原子操作</strong>.</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">int</span> n = <span class="number">1</span>;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">X</span>{</span></span><br><span class="line"> <span class="built_in">X</span>(<span class="keyword">int</span> v){</span><br><span class="line"> ::n += v;</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">f</span><span class="params">()</span></span>{</span><br><span class="line"> X* p = <span class="keyword">new</span> X{ <span class="number">1</span> }; <span class="comment">// 存在数据竞争</span></span><br><span class="line"> <span class="keyword">delete</span> p;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>C++ 只保证了 <code>operator new</code>、<code>operator delete</code> 这两个方面的线程安全</p><p><code>new</code> 表达式线程安全要考虑三方面:<code>operator new</code>、构造函数、修改指针.</p><p><code>delete</code> 表达式线程安全考虑两方面:<code>operator delete</code>、析构函数</p><h3 id="线程存储期"><a href="#线程存储期" class="headerlink" title="线程存储期"></a>线程存储期</h3><p>线程存储期的对象在线程开始时分配,并在线程结束时释放. 使用<code>thread_local</code>声明变量,声明线程存储期的对象,每一个线程都有独立的 <code>thread_local</code> 对象</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">int</span> global_counter = <span class="number">0</span>; <span class="comment">// 静态存储期</span></span><br><span class="line"><span class="keyword">thread_local</span> <span class="keyword">int</span> thread_local_counter=<span class="number">0</span>; <span class="comment">// 线程存储期</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">print_counters</span><span class="params">()</span> </span>{</span><br><span class="line"> std::cout<<<span class="string">"global: "</span><<global_counters++<<<span class="string">'\n'</span>;</span><br><span class="line"> std::cout<<<span class="string">"thread_local"</span><<thread_local_counter<<<span class="string">'\n'</span>;</span><br><span class="line">}</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span></span>{</span><br><span class="line"> std::thread{print_counters}.<span class="built_in">join</span>();</span><br><span class="line"> std::thread{print_counters}.<span class="built_in">join</span>();</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="同步操作"><a href="#同步操作" class="headerlink" title="同步操作"></a>同步操作</h2><h3 id="条件变量"><a href="#条件变量" class="headerlink" title="条件变量"></a>条件变量</h3><p>条件变量有<code>std::condition_variable</code>和<code>std::condition_variable_any</code>,</p><p>std::condition_variable是与std::mutex 一起使用的同步原语,它能用于阻塞一个线程,或同时阻塞多个线程,直至另一线程修改共享变量(<em>条件</em>)并通知<code>std::condition_variable</code></p><p>条件变量用于同步,可以阻塞线程并使用<code>notify_one</code>让解除相关线程阻塞</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">wait_for_flag</span><span class="params">()</span> </span>{</span><br><span class="line"> std::unique_lock ul{cv_mutex};</span><br><span class="line"> <span class="comment">// ul.unlock(); and block the thread</span></span><br><span class="line"> cv.<span class="built_in">wait</span>(ul, [] { <span class="keyword">return</span> flag; });</span><br><span class="line"> <span class="comment">// when get notified,relive the thread and lock the mutex</span></span><br><span class="line"> std::cout << <span class="string">"arrived\n"</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">set_flag_true</span><span class="params">()</span> </span>{</span><br><span class="line"> {</span><br><span class="line"> std::lock_guard lg{cv_mutex};</span><br><span class="line"> std::cout << <span class="string">"set_flag_true\n"</span>;</span><br><span class="line"> std::this_thread::<span class="built_in">sleep_for</span>(<span class="number">1</span>s);</span><br><span class="line"> flag = <span class="literal">true</span>;</span><br><span class="line"> }</span><br><span class="line"> cv.<span class="built_in">notify_all</span>();</span><br><span class="line">}</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span> </span>{</span><br><span class="line"> std::jthread t1{wait_for_flag}, t2{wait_for_flag}, t3{set_flag_true};</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>此外还有<code>std::condition_variable_any</code>,相对于只在 std::unique_lock上工作的 std::condition_variable,<code>condition_variable_any</code> 能在任何满足可基本锁定要求的锁上工作(只需要lock和unlock方法)</p><h3 id="future获得线程结果"><a href="#future获得线程结果" class="headerlink" title="future获得线程结果"></a>future获得线程结果</h3><p>如果要获得一个线程处理后的结果,可以通过使用condition_variable同步,cv.notify(),cv.wait(). 但是更好的方式是通过future获取返回值</p><p>类模板 <code>std::future</code> 提供访问异步操作结果的机制:</p><ul><li><p>(通过std::async,std::packaged_task或 std::promise创建的)异步操作能提供一个 <code>std::future</code> 对象给该异步操作的创建者.</p></li><li><p>然后,异步操作的创建者可以使用多个方法查询、等待或从 <code>std::future</code> 提取值.若异步操作尚未提供值,则这些方法可能阻塞.</p></li><li><p>当异步操作准备好发送结果给创建者时,它可以修改与创建者的 <code>std::future</code> 相链接的<em>共享状态</em></p></li></ul><p><code>std::thread</code> 没提供直接从线程获取返回值的机制.所以可以使用 std::async 函数模板,使用async与thread类似,默认按值赋值,内部将参数副本转换为右值. future的get和wait方法也是用于同步的,</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">f</span><span class="params">(<span class="keyword">const</span> <span class="keyword">int</span>& p)</span> </span>{}</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">f2</span><span class="params">(<span class="keyword">int</span>& p )</span></span>{}</span><br><span class="line"></span><br><span class="line"><span class="keyword">int</span> n = <span class="number">0</span>;</span><br><span class="line">std::<span class="built_in">async</span>(f, n); <span class="comment">// OK! 可以通过编译,不过引用的并非是局部的n</span></span><br><span class="line">std::<span class="built_in">async</span>(f2, n); <span class="comment">// Error! 无法通过编译</span></span><br></pre></td></tr></table></figure><p>async接受所有可调用对象(函数,类成员方法,仿函数类,lambda),与thread类似,其有不同的异步执行策略,std::launch::defered与std::launch::async</p><ol><li><code>std::launch::async</code> 在不同<strong>线程上</strong>执行异步任务.</li><li><code>std::launch::deferred</code> 惰性求值,<strong>不创建线程</strong>,等待 <code>future</code> 对象调用 <code>wait</code> 或 <code>get</code> 成员函数的时候执行任务.</li></ol><p>如果从 <code>std::async</code> 获得的 <code>std::future</code>没有被移动或绑定到引用,那么在完整表达式结尾, <code>std::future</code> 的<strong>析构函数将阻塞,直到到异步任务完成</strong>.因为临时对象的生存期就在这一行,而对象生存期结束就会调用调用析构函数.</p><p>被移动的 <code>std::future</code> 没有所有权,失去共享状态,不能调用 <code>get</code>、<code>wait</code> 成员函数. 此外还有<code>valid</code>检查 future 当前是否关联共享状态,即是否当前关联任务.还未关联,或者任务已经执行完(调用了 get()、set()),都会返回 <strong><code>false</code></strong></p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">X</span>{</span></span><br><span class="line"> <span class="function"><span class="keyword">int</span> <span class="title">operator</span><span class="params">()</span><span class="params">(<span class="keyword">int</span> n)</span><span class="keyword">const</span></span>{</span><br><span class="line"> <span class="keyword">return</span> n * n;</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">Y</span>{</span></span><br><span class="line"> <span class="function"><span class="keyword">int</span> <span class="title">f</span><span class="params">(<span class="keyword">int</span> n)</span><span class="keyword">const</span></span>{</span><br><span class="line"> <span class="keyword">return</span> n * n;</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">f</span><span class="params">(<span class="keyword">int</span>& p)</span> </span>{ std::cout << &p << <span class="string">'\n'</span>; }</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span></span>{</span><br><span class="line"> Y y;</span><br><span class="line"> <span class="keyword">int</span> n = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">auto</span> t1 = std::<span class="built_in">async</span>(X{}, <span class="number">10</span>);</span><br><span class="line"> <span class="keyword">auto</span> t2 = std::<span class="built_in">async</span>(&Y::f,&y,<span class="number">10</span>);</span><br><span class="line"> <span class="keyword">auto</span> t3 = std::<span class="built_in">async</span>([] {}); </span><br><span class="line"> <span class="keyword">auto</span> t4 = std::<span class="built_in">async</span>(f, std::<span class="built_in">ref</span>(n));</span><br><span class="line"> std::cout << &n << <span class="string">'\n'</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>得到的future使用<code>wait</code>同步等待处理,或者使用<code>get</code>获得结果.</p><h3 id="packaged-task"><a href="#packaged-task" class="headerlink" title="packaged_task"></a>packaged_task</h3><p>类模板 <code>std::packaged_task</code> 包装任何可调用目标(函数、lambda 表达式、bind 表达式或其他函数对象),使得能异步调用它.其返回值或所抛异常被存储于能通过 std::future对象访问的共享状态中.</p><p>其重载了<code>()</code>操作符,所以本身也是一个可调用目标,可以传给一个线程. 如果不使用多线程</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">f</span><span class="params">(<span class="keyword">int</span>)</span></span>{};</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">auto</span> task_2{std::packaged_task<<span class="built_in"><span class="keyword">int</span></span>(<span class="keyword">int</span>)>{f}};</span><br><span class="line"> <span class="keyword">auto</span> fut_2{task_2.<span class="built_in">get_future</span>()};</span><br><span class="line"> <span class="built_in">task_2</span>(<span class="number">10</span>);</span><br><span class="line"> std::cout << fut_2.<span class="built_in">get</span>() << <span class="string">'\n'</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>上面任务并不会在线程中执行,所以并没有并发、异步. 所以需要结合多线程,但注意packaged_task不能拷贝,只能移动.</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">auto</span> task_2{std::packaged_task<<span class="built_in"><span class="keyword">int</span></span>(<span class="keyword">int</span>)>{foo}};</span><br><span class="line"><span class="comment">// std::packaged_task<int(int)> task_2{f};</span></span><br><span class="line"><span class="keyword">auto</span> fut_2{task_2.<span class="built_in">get_future</span>()};</span><br><span class="line">std::thread <span class="keyword">fut_t</span>{std::<span class="built_in">move</span>(task_2), <span class="number">10</span>};</span><br><span class="line"><span class="comment">// task_2(10);</span></span><br><span class="line"><span class="keyword">fut_t</span>.<span class="built_in">join</span>();</span><br><span class="line">std::cout << fut_2.<span class="built_in">get</span>() << <span class="string">'\n'</span>;</span><br></pre></td></tr></table></figure><p><code>std::packaged_task</code> 也可以在线程中传递,在需要的时候获取返回值,而非将它自己作为可调用对象. 也就是说thread启动一个可调用对象,这个可调用对象中会调用这个packaged_task,可以通过future获得值.</p><h3 id="使用std-promise设置值"><a href="#使用std-promise设置值" class="headerlink" title="使用std::promise设置值"></a>使用std::promise设置值</h3><p>如果要设置一个值,可以传递reference_wrapper\<T>然后join线程即可获取值.</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">set_val</span><span class="params">(<span class="keyword">int</span> &n)</span> </span>{ n = <span class="number">20</span>; }</span><br><span class="line"><span class="keyword">int</span> tn{<span class="number">0</span>};</span><br><span class="line">std::jthread jd{set_val, std::<span class="built_in">ref</span>(tn)};</span><br><span class="line">jd.<span class="built_in">join</span>();</span><br></pre></td></tr></table></figure><p>但是考虑到这样也许并不好,不仅必须传入通过传入引用、指针,控制颗粒度也不够.</p><p>类模板 <code>std::promise</code> 提供用以存储一个值或一个异常,之后通过 <code>std::promise</code> 对象所创建的<code>std::future</code>对象异步获得.注意 <code>std::promise</code> 只应当使用一次.</p><p>每个promise都与一个<em>共享状态</em> 关联,其中含有一些状态信息和一个<em>结果</em>,它可能尚未求值、已求值为一个值(可能为 <code>void</code>),或者求值为一个异常.promise可以对共享状态做三件事:</p><ul><li><em>使就绪</em>:promise存储结果或异常于共享状态.标记共享状态为就绪,并除阻在该共享状态所关联的未来体上等待的任何线程.</li><li><em>释放</em>:promise放弃其对共享状态的引用.若这是最后一个这种引用,则销毁共享状态.除非这是 std::async所创建的未就绪的共享状态,否则此操作不阻塞.</li><li><em>抛弃</em>:promise存储以 std::future_errc::broken_promise 为错误码的 std::future_error 类型的异常,令共享状态为<em>就绪</em>,然后<em>释放</em>它</li></ul><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">f</span><span class="params">(std::promise<<span class="keyword">int</span>> obj,<span class="keyword">int</span> num)</span></span>{</span><br><span class="line"> obj.<span class="built_in">set_value</span>(num*num);</span><br><span class="line">}</span><br><span class="line">std::promise<<span class="keyword">int</span>> p;</span><br><span class="line"><span class="keyword">auto</span> fut = p.<span class="built_in">get_future</span>();</span><br><span class="line">std::jthread t{f,std::<span class="built_in">move</span>(p),<span class="number">3</span>};</span><br><span class="line"><span class="keyword">int</span> result = p.<span class="built_in">get</span>();</span><br></pre></td></tr></table></figure><p><code>std::promise</code> 只能移动,不可复制,所以需要使用<code>std::move</code>.</p><p>在主线程中通过与其关联的 future 对象的 <code>get()</code> 成员函数获取这个值,如果<code>promise</code>的值还没有被设置,那么将阻塞当前线程</p><p>除了返回一般值外还可以设置异常,但一个promise只能要么设置异常要么设置值,如果设置异常,则通过在promise所在函数中使用try与get进行捕获. </p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">calculate_square</span><span class="params">(std::promise<<span class="keyword">int</span>> promiseObj, <span class="keyword">int</span> num)</span> </span>{</span><br><span class="line"> std::this_thread::<span class="built_in">sleep_for</span>(<span class="number">100</span>ms);</span><br><span class="line"> <span class="keyword">if</span> (!num) {</span><br><span class="line"> promiseObj.<span class="built_in">set_value</span>(num * num);</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> promiseObj.<span class="built_in">set_exception</span>(std::<span class="built_in">current_exception</span>());</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"> <span class="function">std::thread <span class="title">t1t</span><span class="params">(calculate_square, std::move(promise), num)</span></span>;</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> std::cout << <span class="string">"等待线程执行...\n"</span>;</span><br><span class="line"> <span class="keyword">int</span> result = fut_3.<span class="built_in">get</span>(); <span class="comment">// 获取结果或等待异常</span></span><br><span class="line"> std::cout << <span class="string">"Result is "</span> << result << std::endl;</span><br><span class="line"> } <span class="built_in"><span class="keyword">catch</span></span> (std::exception &e) {</span><br><span class="line"> std::cerr << <span class="string">"来自线程的异常"</span> << e.<span class="built_in">what</span>() << <span class="string">'\n'</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> t1t.<span class="built_in">join</span>();</span><br></pre></td></tr></table></figure><h3 id="future的状态变化"><a href="#future的状态变化" class="headerlink" title="future的状态变化"></a>future的状态变化</h3><p>future保有共享状态,只能移动,调用 <code>get</code> 函数后,future对象会失去共享状态,<code>std::future</code> 所引用的共享状态不与另一异步返回对象共享</p><ul><li><strong>移动语义</strong>:因为<strong>移动操作标志着所有权的转移</strong>,意味着 <code>future</code> 不再拥有共享状态.<code>get</code> 和 <code>wait</code> 函数要求 <code>future</code> 对象拥有共享状态,否则会抛出异常.</li><li><strong>共享状态失效</strong>:调用 <code>get</code> 成员函数时,<code>future</code> 对象必须拥有共享状态,但调用完成后,它就会<strong>失去共享状态</strong>,不能再次调用 <code>get</code>.</li></ul><p><strong>future 是一次性的</strong>,它的结果只能被一个线程获取.<code>get()</code> 成员函数只能调用一次,当结果被某个线程获取后,<code>std::future</code> 就无法再用于其他线程.</p><h3 id="使用shared-future共享状态"><a href="#使用shared-future共享状态" class="headerlink" title="使用shared_future共享状态"></a>使用shared_future共享状态</h3><p>目前shared_xx学习到的有,shared_ptr,shared_mutex,shared_lock,现在又有了shared_future. unique_ptr与unique_lock,future都表示独占所有权(只能移动),而shared_xx本身可以复制,并且可以共享.</p><p>主要用于在不同线程中共享一个任务/线程中的数据,它也通过wait和get获取数据.</p><p>通过future.share或直接通过future移动构造shared_future</p><p>具体使用通过传入shared_future的拷贝</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">task</span><span class="params">(<span class="keyword">int</span> num)</span></span>{ <span class="keyword">return</span> num*num;}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span> </span>{</span><br><span class="line"><span class="keyword">auto</span> fut = std::<span class="built_in">async</span>(task,<span class="number">10</span>);</span><br><span class="line">std::shared_future<<span class="keyword">int</span>> fut_shared = fut.<span class="built_in">share</span>();</span><br><span class="line">std::jthread thread1{[fut_shared]{</span><br><span class="line"> <span class="keyword">int</span> result = fut_shared.<span class="built_in">get</span>();</span><br><span class="line"> <span class="keyword">return</span> result*<span class="number">2</span>;</span><br><span class="line">}}</span><br><span class="line">std::jthread thread2{[fut_shared]{</span><br><span class="line"> <span class="keyword">int</span> result = fut_shared.<span class="built_in">get</span>();</span><br><span class="line"> <span class="keyword">return</span> result*<span class="number">2</span>;</span><br><span class="line">}}</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>按复制捕获 <code>std::shared_future</code> 对象,每个线程都有一个 shared_future 的副本,这样不会出现数据竞争问题.</p><h3 id="限时等待"><a href="#限时等待" class="headerlink" title="限时等待"></a>限时等待</h3><p>使用<code>wait_for</code>和<code>wait_until</code>进行限时等待,可以通过future或者条件变量等, 限时等待用于在while循环中等待,可以判断结果,与<code>std::future_status</code>,<code>std::cv_status</code></p><h2 id="Concurrency-in-C-20"><a href="#Concurrency-in-C-20" class="headerlink" title="Concurrency in C++20"></a>Concurrency in C++20</h2><h3 id="信号量"><a href="#信号量" class="headerlink" title="信号量"></a>信号量</h3><p>C++ 提供了两个信号量类型:<code>std::counting_semaphore</code> 与<code>std::binary_semaphore</code></p><p>信号量是更轻量的同步原语.</p><blockquote><p>mutex,条件变量都是同步原语. 但mutex常用于互斥解决数据竞争,而条件</p></blockquote><p>提供<code>release</code>和<code>acquire</code>两种方法,分别增加内部计数器并解除获得者以及减少内部计数器或阻塞到直至能如此</p><p>信号量常用于<em>发信/提醒</em>而非互斥,通过初始化该信号量为 0 从而阻塞尝试 acquire() 的接收者,直至提醒者通过调用 release(n) “发信”.在此方面可把信号量当作<strong>条件变量的替代品</strong>,<strong>通常它有更好的性能</strong></p><p>counting_semaphore能设置最大信号量值,binary_semaphore最大值就是1,相当于控制了同时访问者.</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">std::counting_semaphore<<span class="number">3</span>> semaphore{<span class="number">3</span>};</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">handle_request</span><span class="params">(<span class="keyword">int</span> request_id)</span> </span>{</span><br><span class="line"> std::cout << <span class="string">"进入handle_request尝试获取信号量\n"</span>;</span><br><span class="line"> semaphore.<span class="built_in">acquire</span>(); <span class="comment">// similar to cv.wait(lk)??</span></span><br><span class="line"> std::cout << <span class="string">"成功获取信号量\n"</span>;</span><br><span class="line"> <span class="comment">// do something</span></span><br><span class="line"> std::this_thread::<span class="built_in">sleep_for</span>(<span class="number">1</span>s);</span><br><span class="line"></span><br><span class="line"> std::random_device rd;</span><br><span class="line"> std::mt19937 gen{<span class="built_in">rd</span>()};</span><br><span class="line"> std::uniform_int_distribution dis{<span class="number">1</span>, <span class="number">10</span>};</span><br><span class="line"> <span class="keyword">int</span> process_time = <span class="built_in">dis</span>(gen);</span><br><span class="line"> std::this_thread::<span class="built_in">sleep_for</span>(std::chrono::seconds{process_time});</span><br><span class="line"> std::cout << std::format(<span class="string">"请求 {} 已被处理\n"</span>, request_id);</span><br><span class="line"> semaphore.<span class="built_in">release</span>();</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="std-latch"><a href="#std-latch" class="headerlink" title="std::latch"></a>std::latch</h3><p>信号量方便同步,与条件变量类似. 而latch与barrier是线程协调机制,阻塞已知大小的线程组直至该组中的所有线程到达该屏障.</p><p>允许任何数量的线程阻塞<strong>直至期待数量的线程到达</strong>. latch是单次使用的线程屏障,latch不能重复使用,它会等到需要的线程的数量.</p><p><code>latch</code> 类维护着一个 std::ptrdiff_t 类型的计数,且只能减少计数,无法增加计数.在创建对象的时候初始化计数器的值.线程可以阻塞,直到 latch 对象的计数减少到零.由于无法增加计数,这使得 <code>latch</code> 成为一种**单次使用的屏障</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">std::latch work_start{<span class="number">3</span>};</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">work</span><span class="params">()</span> </span>{</span><br><span class="line"> std::cout << <span class="string">"等待其他线程执行\n"</span>;</span><br><span class="line"> work_start.<span class="built_in">wait</span>();</span><br><span class="line"> std::cout << <span class="string">"任务开始执行\n"</span>;</span><br><span class="line">}</span><br><span class="line">std::jthread thread{work};</span><br><span class="line">std::this_thread::<span class="built_in">sleep_for</span>(<span class="number">1</span>s);</span><br><span class="line">work_start.<span class="built_in">count_down</span>();</span><br></pre></td></tr></table></figure><p>count_down默认将值-1,直到为0时,wait解除阻塞. 此外也有<code>arrive_and_wait</code>相当于<code>count_down(n);wait();</code>,这样在可调用对象内部直接使用</p><h3 id="std-barrier"><a href="#std-barrier" class="headerlink" title="std::barrier"></a>std::barrier</h3><p>可复用的线程屏障,可以在阶段完成之后将计数重置为构造时传递的值.</p><blockquote><p>不同于 std::latch,屏障是可重用的:一旦到达的线程组被解除阻塞,即可重用同一屏障.与 std::latch 不同,会在线程解除阻塞前执行一个可能为空的可调用对象.</p></blockquote><p>barrier也有wait与arrive,它能够多次使用,也就是说如果创建10个线程,每个线程的可调用对象使用barrier值设置为3,那么就会有阻塞,因为9个线程结束,最后一个线程减少barrier设置的值,但依旧不为0. 如果是latch,当内部计数值为0时还调用count_down是未定义行为</p><p><code>arrive_and_wait()</code> 会在期待计数减少至 <code>0</code> 时调用我们构造 barrier 对象时传入的 lambda 表达式,并解除所有在阶段同步点上阻塞的线程.之后重置期待计数为构造中指定的值.屏障的一个阶段就完成了. 还有<code>arrive_and_drop</code>会将当前与最大的计数均-1.</p><p><code>std::barrier</code> 要求其函数对象类型必须是不抛出异常的. </p><h3 id="内存模型与原子操作"><a href="#内存模型与原子操作" class="headerlink" title="内存模型与原子操作"></a>内存模型与原子操作</h3><ul><li>内存模型定义了多线程程序中,读写操作如何在不同线程之间可见,以及这些操作在何种顺序下执行.内存模型确保程序的行为在并发环境下是可预测的.</li><li>原子操作即不可分割的操作.系统的所有线程,不可能观察到原子操作完成了一半</li></ul><h4 id="std-atomic"><a href="#std-atomic" class="headerlink" title="std::atomic"></a>std::atomic</h4><p>c++标准定义了原子类型,这些类型的操作都是原子的,语言定义中只有这些类型的操作是原子的,虽然也可以用互斥量来模拟原子操作.</p><p>每个 <code>std::atomic</code> 模板的实例化和全特化均定义一个原子类型.如果一个线程写入原子对象,同时另一线程从它读取,那么行为有良好定义(使用load和store),<code>std::atomic</code> 既不可复制也不可移动.</p><p>标准原子类型的实现通常包括一个 <code>is_lock_free()</code> 成员函数,允许用户查询特定原子类型的操作是否是通过直接的原子指令实现(返回 true),还是通过锁来实现(返回 false)</p><p>也可以通过is_always_lock_free和一些宏来检查</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">std::atomic<<span class="keyword">int</span>> aint = <span class="number">10</span>;</span><br><span class="line">aint.<span class="built_in">is_lock_free</span>(); <span class="comment">// 成员函数</span></span><br><span class="line">aint.is_always_lock_free; <span class="comment">//编译器常量 constexpr</span></span><br><span class="line">std::cout<<ATOMIC_INT_LOCK_FREE;</span><br></pre></td></tr></table></figure><p>always_lock_free意味着一定无锁,ATOMIC_INT_LOCK_FREE的值若为0则一定有锁,为1则有时无锁,为2则一定无锁.</p><p>在实际应用中,如果一个类型的原子操作总是无锁的,可以更放心地在性能关键的代码路径中使用它.</p><p>如果发现某些原子类型在目标平台上是有锁的,我们可以考虑以下优化策略:</p><ol><li><strong>使用不同的数据结构</strong>:有时可以通过改变数据结构来避免对原子操作的依赖.</li><li><strong>减少原子操作的频率</strong>:通过批处理等技术,减少对原子操作的调用次数.</li><li><strong>使用更高效的同步机制</strong>:在一些情况下,其它同步机制(如读写锁)可能比原子操作更高效.</li></ol><p>其实很多时候根本没这种性能的担忧,很多时候使用原子对象只是为了简单方便,比如 <code>std::atomic<bool></code> 表示状态、<code>std::atomic<int></code> 进行计数等.即使它们是用了锁,那也是封装好了的,起码用着方便,而不需要在代码中引入额外的互斥量来保护,更加简洁.这也是很正常的需求,各位不但要考虑程序的性能,同时也要考虑代码的简洁性、易用性.即使使用原子类型无法带来效率的提升,那也没有负提升.</p><p>常用的atomic特化有int,bool,flag,等</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">using</span> <span class="keyword">atomic_char</span> = atomic<<span class="keyword">char</span>>;</span><br><span class="line"><span class="keyword">using</span> <span class="keyword">atomic_schar</span> = atomic<<span class="keyword">signed</span> <span class="keyword">char</span>>;</span><br><span class="line"><span class="keyword">using</span> <span class="keyword">atomic_uchar</span> = atomic<<span class="keyword">unsigned</span> <span class="keyword">char</span>>;</span><br><span class="line"><span class="keyword">using</span> <span class="keyword">atomic_short</span> = atomic<<span class="keyword">short</span>>;</span><br><span class="line"><span class="keyword">using</span> <span class="keyword">atomic_ushort</span> = atomic<<span class="keyword">unsigned</span> <span class="keyword">short</span>>;</span><br><span class="line"><span class="keyword">using</span> <span class="keyword">atomic_int</span> = atomic<<span class="keyword">int</span>>;</span><br><span class="line"><span class="keyword">using</span> <span class="keyword">atomic_uint</span> = atomic<<span class="keyword">unsigned</span> <span class="keyword">int</span>>;</span><br><span class="line"><span class="keyword">using</span> <span class="keyword">atomic_long</span> = atomic<<span class="keyword">long</span>>;</span><br><span class="line"><span class="keyword">using</span> <span class="keyword">atomic_ulong</span> = atomic<<span class="keyword">unsigned</span> <span class="keyword">long</span>>;</span><br><span class="line"><span class="keyword">using</span> <span class="keyword">atomic_llong</span> = atomic<<span class="keyword">long</span> <span class="keyword">long</span>>;</span><br><span class="line"><span class="keyword">using</span> <span class="keyword">atomic_ullong</span> = atomic<<span class="keyword">unsigned</span> <span class="keyword">long</span> <span class="keyword">long</span>>;</span><br></pre></td></tr></table></figure><p>原子类型常用方法包括<code>load</code>,<code>store</code>,<code>exchange</code>等,不同特化也有不同方法. 可以为自定义类型创建atomic,需要满足可复制构造,可复制赋值以及可平凡复制</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in"><span class="keyword">static_assert</span></span>(std::is_trivially_copyable<trivial_type>::value, <span class="string">""</span>);</span><br><span class="line"><span class="built_in"><span class="keyword">static_assert</span></span>(std::is_copy_constructible<trivial_type>::value, <span class="string">""</span>);</span><br><span class="line"><span class="built_in"><span class="keyword">static_assert</span></span>(std::is_move_constructible<trivial_type>::value, <span class="string">""</span>);</span><br><span class="line"><span class="built_in"><span class="keyword">static_assert</span></span>(std::is_copy_assignable<trivial_type>::value, <span class="string">""</span>);</span><br><span class="line"><span class="built_in"><span class="keyword">static_assert</span></span>(std::is_move_assignable<trivial_type>::value, <span class="string">""</span>);</span><br></pre></td></tr></table></figure><p>原子类型的操作函数有一个内存序参数,对原子对象的访问可以建立线程间同步,并按<code>std::memory_order</code>对非原子内存访问定序. 任何 <code>std::atomic</code>类型,初始化不是原子操作,其他方法是原子操作.与大多数赋值运算符不同,<strong>原子类型的赋值运算不返回到它的左侧参数的引用.它们会返回存储值的副本</strong></p><h4 id="std-atomic-flag"><a href="#std-atomic-flag" class="headerlink" title="std::atomic_flag"></a>std::atomic_flag</h4><p><code>std::atomic_flag</code> 是一种原子布尔类型.与所有std::atomic的特化不同,它保证是无锁的.与std::atomic\<bool> 不同,<code>std::atomic_flag</code> 不提供加载或存储操作.</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">std::atomic_flag flag{};</span><br><span class="line"><span class="keyword">bool</span> r = flag.<span class="built_in">test_and_set</span>();</span><br></pre></td></tr></table></figure><p>当标志对象已初始化,它只能做三件事情:<strong>销毁、清除、设置</strong>.这些操作对应的函数分别是:</p><ol><li><strong><code>clear()</code></strong> (清除):将标志对象的状态原子地更改为清除(false)</li><li><strong><code>test_and_set</code></strong>(测试并设置):将标志对象的状态原子地更改为设置(true),并返回它先前保有的值.</li><li>销毁:对象的生命周期结束时,自动调用析构函数进行销毁操作.</li></ol><p>适合使用atomic_flag做一个自旋锁,也就是通过<code>while</code>忙等</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">spinlock_mutex</span> {</span></span><br><span class="line"> std::atomic_flag flag{};</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="built_in">spinlock_mutex</span>()<span class="keyword">noexcept</span> = <span class="keyword">default</span>;</span><br><span class="line"> <span class="function"><span class="keyword">void</span> <span class="title">lock</span><span class="params">()</span><span class="keyword">noexcept</span> </span>{</span><br><span class="line"> <span class="keyword">while</span> (flag.<span class="built_in">test_and_set</span>(std::memory_order_acquire));</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">void</span> <span class="title">unlock</span><span class="params">()</span><span class="keyword">noexcept</span> </span>{</span><br><span class="line"> flag.<span class="built_in">clear</span>(std::memory_order_release);</span><br><span class="line"> }</span><br><span class="line">};</span><br></pre></td></tr></table></figure><h4 id="std-atomic-1"><a href="#std-atomic-1" class="headerlink" title="std::atomic\"></a>std::atomic\<bool></h4><p>布尔原子类型,但比atomic_flag多load,store等方法,</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">std::atomic<<span class="keyword">bool</span>> b{<span class="literal">true</span>};</span><br><span class="line">b.<span class="built_in">load</span>(<span class="literal">true</span>);</span><br><span class="line">b = <span class="literal">false</span>;<span class="comment">//表达式值为false</span></span><br><span class="line"><span class="keyword">auto</span> value = (b = <span class="literal">false</span>); </span><br></pre></td></tr></table></figure><p><code>exchange</code>以 desired 原子地替换底层值.操作为读-修改-写操作.根据 order 的值影响内存</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">std::atomic<<span class="keyword">bool</span>> b{<span class="literal">true</span>};</span><br><span class="line"><span class="keyword">bool</span> x = b.<span class="built_in">load</span>();</span><br><span class="line">b.<span class="built_in">store</span>(<span class="literal">true</span>);</span><br><span class="line">x = b.<span class="built_in">exchange</span>(<span class="literal">false</span>); <span class="comment">// x->true</span></span><br></pre></td></tr></table></figure><p><code>compare_exchange_weak</code>和<code>compare_exchange_strong</code></p><p>原子地比较 <em>this 和 expected 的对象表示(C++20 前),值表示 (C++20 起).如果它们逐位相等,那么以 desired 替换前者(进行读修改写操作).否则,将 </em>this 中的实际值加载进 expected(进行加载操作).</p><p>也就是当前值与预期一致时,存储新值否则得到当前值存储在expected中</p><p><strong>compare_exchange_weak</strong>:尝试将原子对象的当前值与预期值进行比较,如果相等则将其更新为新值并返回 <code>true</code>;否则,将原子对象的值加载进 expected(进行加载操作)并返回 <code>false</code>.此操作可能会由于某些硬件的特性而出现<strong>假失败</strong>,需要在循环中重试</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">std::atomic<<span class="keyword">bool</span>> flag{ <span class="literal">false</span> };</span><br><span class="line"><span class="keyword">bool</span> expected = <span class="literal">false</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">while</span> (!flag.<span class="built_in">compare_exchange_weak</span>(expected, <span class="literal">true</span>));</span><br></pre></td></tr></table></figure><p><strong>compare_exchange_strong</strong>:类似于 <code>compare_exchange_weak</code>,<strong>但不会出现假失败,因此不需要重试</strong>.适用于需要确保操作成功的场合.</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">std::atomic<<span class="keyword">bool</span>> flag{ <span class="literal">false</span> };</span><br><span class="line"><span class="keyword">bool</span> expected = <span class="literal">false</span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">try_set_flag</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="comment">// 尝试将 flag 设置为 true,如果当前值为 false</span></span><br><span class="line"> <span class="keyword">if</span> (flag.<span class="built_in">compare_exchange_strong</span>(expected, <span class="literal">true</span>)) {</span><br><span class="line"> std::cout << <span class="string">"flag 为 false,设为 true.\n"</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span> {</span><br><span class="line"> std::cout << <span class="string">"flag 为 true, expected 设为 true.\n"</span>;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><code>compare_exchange_weak</code> 和 <code>compare_exchange_strong</code> 允许指定成功和失败情况下的内存序.这意味着你可以根据成功或失败的情况,为原子操作指定不同的内存序</p><h4 id="std-atmoc"><a href="#std-atmoc" class="headerlink" title="std::atmoc"></a>std::atmoc<T*></h4><p><code>std::atomic<T*></code> 是一个原子指针类型,<code>T</code> 是指针所指向的对象类型.操作是针对 <code>T</code> 类型的指针进行的.虽然 <code>std::atomic<T*></code> 不能被拷贝和移动,但它可以通过符合类型的指针进行构造和赋值.</p><p>除了常见的<code>load</code>,<code>store</code>,<code>exchange</code>等,还有<code>fetch_add</code>,<code>fetch_sub</code>等,确保多线程下的指针操作.</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">Foo</span> {</span>};</span><br><span class="line"></span><br><span class="line">Foo array[<span class="number">5</span>]{};</span><br><span class="line">std::atomic<Foo*> p{ array };</span><br><span class="line"></span><br><span class="line"><span class="comment">// p 加 2,并返回原始值</span></span><br><span class="line">Foo* x = p.<span class="built_in">fetch_add</span>(<span class="number">2</span>);</span><br><span class="line"><span class="built_in">assert</span>(x == array);</span><br><span class="line"><span class="built_in">assert</span>(p.<span class="built_in">load</span>() == &array[<span class="number">2</span>]);</span><br><span class="line"></span><br><span class="line"><span class="comment">// p 减 1,并返回原始值</span></span><br><span class="line">x = (p -= <span class="number">1</span>);</span><br><span class="line"><span class="built_in">assert</span>(x == &array[<span class="number">1</span>]);</span><br><span class="line"><span class="built_in">assert</span>(p.<span class="built_in">load</span>() == &array[<span class="number">1</span>]);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 函数也允许内存序作为给定函数的参数</span></span><br><span class="line">p.<span class="built_in">fetch_add</span>(<span class="number">3</span>, std::memory_order_release);</span><br></pre></td></tr></table></figure><h4 id="std-atomic-2"><a href="#std-atomic-2" class="headerlink" title="std::atomic\"></a>std::atomic\<std::shared_ptr></h4><p>若多个执行线程不同步地同时访问<strong>同一</strong> <code>std::shared_ptr</code> 对象,且任何这些访问使用了 shared_ptr 的非 const 成员函数,则将出现数据竞争<strong>,</strong>除非通过 <code>std::atomic<std::shared_ptr></code> 的实例进行所有访问</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">std::atomic<std::shared> data{};</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">writer</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">for</span>(<span class="keyword">int</span> i=<span class="number">0</span>;i<<span class="number">10</span>;++i) {</span><br><span class="line"> std::shared_ptr<Data> new_data = std::make_shared<Data>(i);</span><br><span class="line"> data.<span class="built_in">store</span>(new_data);</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">reader</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < <span class="number">10</span>; ++i) {</span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">auto</span> sp = data.<span class="built_in">load</span>()) {</span><br><span class="line"> std::cout << <span class="string">"读取线程值: "</span> << sp-><span class="built_in">get_value</span>() << std::endl;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span> {</span><br><span class="line"> std::cout << <span class="string">"没有读取到数据"</span> << std::endl;</span><br><span class="line"> }</span><br><span class="line"> std::this_thread::<span class="built_in">sleep_for</span>(<span class="number">10</span>ms);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>最后原子类型还提供了<code>wait</code>和<code>notify_xx</code>的方法,<code>wait</code>进行原子等待操作,如果值与this->load()值表示相同,则阻塞直到 <em>this 被 notify_one() 或 notify_all() 提醒,如果不同直接返回. <code>notif_xx</code>进行原子提醒操作,如果有线程被 </em>this 上的原子等待操作(即 wait())阻塞,那么解除锁定这种线程;否则不做任何事</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line">std::atomic<std::shared_ptr<<span class="keyword">int</span>>> ptr = std::make_shared<<span class="keyword">int</span>>();</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">wait_for_wake_up</span><span class="params">()</span></span>{</span><br><span class="line"> std::osyncstream{ std::cout }</span><br><span class="line"> << <span class="string">"线程 "</span></span><br><span class="line"> << std::this_thread::<span class="built_in">get_id</span>()</span><br><span class="line"> << <span class="string">" 阻塞,等待更新唤醒\n"</span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 等待 ptr 变为其它值</span></span><br><span class="line"> ptr.<span class="built_in">wait</span>(ptr.<span class="built_in">load</span>());</span><br><span class="line"></span><br><span class="line"> std::osyncstream{ std::cout }</span><br><span class="line"> << <span class="string">"线程 "</span></span><br><span class="line"> << std::this_thread::<span class="built_in">get_id</span>()</span><br><span class="line"> << <span class="string">" 已被唤醒\n"</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">wake_up</span><span class="params">()</span></span>{</span><br><span class="line"> std::this_thread::<span class="built_in">sleep_for</span>(<span class="number">5</span>s);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 更新值并唤醒</span></span><br><span class="line"> ptr.<span class="built_in">store</span>(std::make_shared<<span class="keyword">int</span>>(<span class="number">10</span>));</span><br><span class="line"> ptr.<span class="built_in">notify_one</span>();</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="std-atomic-ref"><a href="#std-atomic-ref" class="headerlink" title="std::atomic_ref"></a>std::atomic_ref</h4><p><code>std::atomic_ref</code> 类模板对它引用的对象应用原子操作.在 <code>std::atomic_ref</code> 对象的生存期中,认为它引用的对象是原子对象.如果一个线程写入原子对象,同时另一线程从它读取,那么其行为有良好定义.另外,对原子对象的访问可以建立线程间同步,和按 std::memory_order 所指定定序非原子内存访问.</p><p>对象的生存期必须超出所有引用该对象的 <code>std::atomic_ref</code> 的生存期.任何 <code>std::atomic_ref</code> 实例所引用的对象仍存在时,必须只通过这些 <code>std::atomic_ref</code> 实例排他地访问该对象.<code>std::atomic_ref</code> 对象所引用对象的任何子对象均不可同时被任何其他 <code>std::atomic_ref</code> 对象引用.</p><p>通过 <code>std::atomic_ref</code> 应用到对象的原子操作,相对于通过任何其他引用同一对象的 <code>std::atomic_ref</code> 应用的操作来说都是原子的.</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">std::atomic<std::shared_ptr<<span class="keyword">int</span>>> ptr = std::make_shared<<span class="keyword">int</span>>(<span class="number">10</span>);</span><br><span class="line">std::atomic_ref<<span class="keyword">int</span>> ref{ *ptr.<span class="built_in">load</span>() };</span><br><span class="line">ref = <span class="number">100</span>; <span class="comment">// 原子地赋 100 给被引用的对象</span></span><br></pre></td></tr></table></figure><h4 id="std-memory-order"><a href="#std-memory-order" class="headerlink" title="std::memory_order"></a>std::memory_order</h4><p><code>std::memory_order</code> 指定内存访问,包括常规的非原子内存访问,如何围绕原子操作排序.在没有任何约束的多处理器系统上,多个线程同时读或写数个变量时,一个线程能观测到变量值更改的顺序不同于另一个线程写它们的顺序.实际上,更改的顺序甚至能在多个读取线程间相异.一些类似的效果还能在单处理器系统上出现,因为内存模型允许编译器进行变换.</p><p>库中所有原子操作的默认行为提供<em>序列一致定序</em>.该默认行为可能有损性能,不过可以给予库的原子操作额外的 <code>std::memory_order</code> 实参,以指定确切的约束,在原子性外,编译器和处理器还必须强制该操作.</p><p><code>std::memory_order</code> 是一个枚举类型,用来指定原子操作的内存顺序,影响这些操作的行为</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">enum</span> <span class="title">memory_order</span> {</span></span><br><span class="line"> memory_order_relaxed,</span><br><span class="line"> memory_order_consume,</span><br><span class="line"> memory_order_acquire,</span><br><span class="line"> memory_order_release,</span><br><span class="line"> memory_order_acq_rel,</span><br><span class="line"> memory_order_seq_cst</span><br><span class="line">} memory_order;</span><br><span class="line"></span><br><span class="line"><span class="comment">// C++20 起则为:</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">enum</span> <span class="keyword">class</span> <span class="title">memory_order</span> :</span> <span class="comment">/* 未指明 */</span> {</span><br><span class="line"> relaxed, consume, acquire, release, acq_rel, seq_cst</span><br><span class="line">};</span><br><span class="line"><span class="keyword">inline</span> <span class="keyword">constexpr</span> memory_order memory_order_relaxed = memory_order::relaxed;</span><br><span class="line"><span class="keyword">inline</span> <span class="keyword">constexpr</span> memory_order memory_order_consume = memory_order::consume;</span><br><span class="line"><span class="keyword">inline</span> <span class="keyword">constexpr</span> memory_order memory_order_acquire = memory_order::acquire;</span><br><span class="line"><span class="keyword">inline</span> <span class="keyword">constexpr</span> memory_order memory_order_release = memory_order::release;</span><br><span class="line"><span class="keyword">inline</span> <span class="keyword">constexpr</span> memory_order memory_order_acq_rel = memory_order::acq_rel;</span><br><span class="line"><span class="keyword">inline</span> <span class="keyword">constexpr</span> memory_order memory_order_seq_cst = memory_order::seq_cst;</span><br></pre></td></tr></table></figure><p>这 6 个常量,每一个常量都表示不同的内存次序</p><p>大体来说可以将它们分为三类.</p><ol><li><p><code>memory_order_relaxed</code> 宽松定序:不是定序约束,<strong>仅对此操作要求原子性</strong>.</p></li><li><p><code>memory_order_seq_cst</code> 序列一致定序,这是库中所有原子操作的<strong>默认行为</strong>,也是<strong>最严格的内存次序</strong>,是<strong>绝对安全</strong>的.</p><p><img data-src="https://s2.loli.net/2024/10/15/sTrc7EOAY9ubaRF.png" alt="image-20241015195228114"></p></li></ol><h2 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h2><ol><li><a href="https://mq-b.github.io/ModernCpp-ConcurrentProgramming-Tutorial/md/02使用线程.html#启动新线程">使用线程 | 现代C++并发编程教程 (mq-b.github.io)</a></li><li>C++ Concurrency in Action</li></ol><link rel="stylesheet" href="/css/spoiler.css" type="text/css"><script src="/js/spoiler.js" type="text/javascript" async></script>]]></content>
<summary type="html"><p>更加深入的探讨并发.<br></summary>
<category term="c++" scheme="https://www.sekyoro.top/tags/c/"/>
</entry>
<entry>
<title>窗口工具库GLFW使用</title>
<link href="https://www.sekyoro.top/2024/10/04/%E7%AA%97%E5%8F%A3%E5%B7%A5%E5%85%B7%E5%BA%93GLFW%E4%BD%BF%E7%94%A8/"/>
<id>https://www.sekyoro.top/2024/10/04/%E7%AA%97%E5%8F%A3%E5%B7%A5%E5%85%B7%E5%BA%93GLFW%E4%BD%BF%E7%94%A8/</id>
<published>2024-10-04T11:53:18.000Z</published>
<updated>2024-10-08T14:19:12.563Z</updated>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="\assets\js\Meting.min.js"></script><p>使用OpenGL或者Vulkan的图形库时经常使用一些窗口工具库搭配,常见的就是<a href="https://www.glfw.org/docs/latest/index.html">GLFW: Introduction</a>了,类似的有SDL,SFML和win32库,这里也会简单说一下. 这篇文章相当于GLFW的api介绍</p><span id="more"></span><p>SDL与SFML都是跨平台的多媒体库,除了创建窗口之外,还包括音视频库甚至网络库<a href="https://www.sfml-dev.org/tutorials/2.6/network-socket.php">sfml</a>和<a href="https://github.com/libsdl-org/SDL_net">SDL_net</a>,它们在不同平台上使用对应的库.比如SDL在windows上使用win32,使用direct3D作为图形库,在Linux上使用x11作为窗口系统,openGL作为图形库.</p><p><img data-src="https://s2.loli.net/2024/10/06/okrmt3QxWUaOfA2.png" alt="image-20241006121306560"></p><p>SFML可以将其视为面向对象的SDL,在windows上使用了gdi32+opengl32.而GLFW相对来说更纯粹,官网直接说是OpenGL的上下文管理器. </p><p><img data-src="https://s2.loli.net/2024/10/06/WK8hxcI5O9VMLzC.png" alt="image-20241006121554268"></p><p>类似SFML的还有<a href="https://www.fltk.org/">Fast Light Toolkit - Fast Light Toolkit (FLTK)</a></p><p>此外也有更上层imgui和raylib库,相当于同时包含了窗口管理和绘图等功能,raylib在windows和Linux上均使用GLFW和OpenGL<a href="https://github.com/raysan5/raylib/wiki/raylib-platforms-and-graphics">raylib platforms and graphics · raysan5/raylib Wiki (github.com)</a>,而imgui可以相对更自由地选择搭配.</p><p>此外我也看到有人提到<a href="https://skia.org/">Skia</a>,<a href="https://blend2d.com/">Blend2D</a>以及<a href="https://www.cairographics.org/">cairographics.org</a>,<a href="https://libyue.com/">Yue (libyue.com)</a>等等,它们是2D绘图库,相对使用opengl,d3d等更节省资源,本身可以创建上下文,也能与窗口结合</p><h2 id="GLFW"><a href="#GLFW" class="headerlink" title="GLFW"></a>GLFW</h2><p>GLFW目前GLFW版本到了3.0,下面是一个GLFW的经典流程</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><GLFW/glfw3.h></span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">(<span class="keyword">void</span>)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> GLFWwindow* window;</span><br><span class="line"></span><br><span class="line"> <span class="comment">/* Initialize the library */</span></span><br><span class="line"> <span class="keyword">if</span> (!<span class="built_in">glfwInit</span>())</span><br><span class="line"> <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">/* Create a windowed mode window and its OpenGL context */</span></span><br><span class="line"> window = <span class="built_in">glfwCreateWindow</span>(<span class="number">640</span>, <span class="number">480</span>, <span class="string">"Hello World"</span>, <span class="literal">NULL</span>, <span class="literal">NULL</span>);</span><br><span class="line"> <span class="keyword">if</span> (!window)</span><br><span class="line"> {</span><br><span class="line"> <span class="built_in">glfwTerminate</span>();</span><br><span class="line"> <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/* Make the window's context current */</span></span><br><span class="line"> <span class="built_in">glfwMakeContextCurrent</span>(window);</span><br><span class="line"></span><br><span class="line"> <span class="comment">/* Loop until the user closes the window */</span></span><br><span class="line"> <span class="keyword">while</span> (!<span class="built_in">glfwWindowShouldClose</span>(window))</span><br><span class="line"> {</span><br><span class="line"> <span class="comment">/* Render here */</span></span><br><span class="line"> <span class="built_in">glClear</span>(GL_COLOR_BUFFER_BIT);</span><br><span class="line"></span><br><span class="line"> <span class="comment">/* Swap front and back buffers */</span></span><br><span class="line"> <span class="built_in">glfwSwapBuffers</span>(window);</span><br><span class="line"></span><br><span class="line"> <span class="comment">/* Poll for and process events */</span></span><br><span class="line"> <span class="built_in">glfwPollEvents</span>();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="built_in">glfwTerminate</span>();</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="包含头文件"><a href="#包含头文件" class="headerlink" title="包含头文件"></a>包含头文件</h3><p>默认GLFW的头文件包括OpenGL头文件,但是版本可能比较老,一般使用一个loader library获取版本,比如glad. 如果包括了glad可以检测到从而不使用开发环境中的gl头文件.</p><blockquote><p>为了确保没有头冲突,您可以在GLFW头之前定义GLFW_INCLUDE_NONE,以显式禁用开发环境头的包含。这也允许以任何顺序包含两个头</p></blockquote><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">define</span> GLFW_INCLUDE_NONE</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><GLFW/glfw3.h></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><glad/gl.h></span></span></span><br></pre></td></tr></table></figure><h3 id="初始化和停止GLFW"><a href="#初始化和停止GLFW" class="headerlink" title="初始化和停止GLFW"></a>初始化和停止GLFW</h3><p>在使用大多数GLFW函数之前,必须初始化库。初始化成功时,返回GLFW_TRUE。如果发生错误,则返回GLFW_FALSE</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (!glfwInit())</span><br><span class="line">{</span><br><span class="line"> <span class="comment">// Initialization failed</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>当使用完GLFW后,通常在应用程序退出之前,需要终止GLFW</p><p>这将终止所有剩余的窗口并释放由GLFW分配的任何其他资源。在此调用之后,在使用任何需要它的GLFW函数之前,必须再次初始化GLFW</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">glfwTerminate();</span><br></pre></td></tr></table></figure><h3 id="设置错误处理"><a href="#设置错误处理" class="headerlink" title="设置错误处理"></a>设置错误处理</h3><p>大多数事件都是通过回调的,无论是按下的键、移动的GLFW窗口还是发生的错误。回调是由GLFW调用的带有描述事件的参数的C函数(或c++静态方法)</p><p>如果GLFW函数失败,则会向GLFW错误回调函数报告一个错误。您可以通过错误回调接收这些报告。此函数必须具有下面的签名,但可以执行其他回调中允许的任何操作</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">error_callback</span><span class="params">(<span class="keyword">int</span> error, <span class="keyword">const</span> <span class="keyword">char</span>* description)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="built_in">fprintf</span>(<span class="built_in">stderr</span>, <span class="string">"Error: %s\n"</span>, description);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>必须设置回调函数,这样GLFW才知道调用它们。设置错误回调的函数是少数几个可以在初始化之前调用的GLFW函数之一,它可以在初始化期间和之后通知错误</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">glfwSetErrorCallback(error_callback);</span><br></pre></td></tr></table></figure><h3 id="创建与销毁窗口"><a href="#创建与销毁窗口" class="headerlink" title="创建与销毁窗口"></a>创建与销毁窗口</h3><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">GLFWwindow* window = <span class="built_in">glfwCreateWindow</span>(<span class="number">640</span>, <span class="number">480</span>, <span class="string">"My Title"</span>, <span class="literal">NULL</span>, <span class="literal">NULL</span>);</span><br><span class="line"><span class="keyword">if</span> (!window)</span><br><span class="line">{</span><br><span class="line"> <span class="comment">// Window or OpenGL context creation failed</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这将创建一个带有OpenGL上下文的640 * 480窗口模式窗口。如果窗口或OpenGL上下文创建失败,将返回NULL。您应该始终检查返回值。虽然窗口创建很少失败,但上下文创建取决于正确安装的驱动程序,甚至在具有必要硬件的机器上也可能失败。</p><p>默认情况下,GLFW创建的OpenGL上下文可以有任何版本。可以通过在创建之前设置GLFW_CONTEXT_VERSION_MAJOR和GLFW_CONTEXT_VERSION_MINOR提示来要求最小OpenGL版本。如果机器上不支持所需的最低版本,则上下文(和窗口)创建失败</p><p>通过设置GLFW_OPENGL_PROFILE提示,可以选择OpenGL profile.</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, <span class="number">3</span>);</span><br><span class="line">glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, <span class="number">3</span>);</span><br><span class="line">glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);</span><br><span class="line">GLFWwindow* window = glfwCreateWindow(<span class="number">640</span>, <span class="number">480</span>, <span class="string">"My Title"</span>, <span class="literal">NULL</span>, <span class="literal">NULL</span>);</span><br><span class="line"><span class="keyword">if</span> (!window)</span><br><span class="line">{</span><br><span class="line"> <span class="comment">// Window or context creation failed</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>我们经常提到OpenGL上下文, <code>glfwCreateWindow</code>会返回窗口,这个窗口就相当于一个OpenGL上下文</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">glfwDestroyWindow(window);</span><br></pre></td></tr></table></figure><p>一旦调用这个函数,就不会再为该窗口传递事件,并且它的句柄无效</p><h3 id="设置当前OpenGL上下文"><a href="#设置当前OpenGL上下文" class="headerlink" title="设置当前OpenGL上下文"></a>设置当前OpenGL上下文</h3><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">glfwMakeContextCurrent(window);</span><br></pre></td></tr></table></figure><p>该上下文将保持当前状态,直到将另一个上下文设置为当前状态,或者直到拥有当前上下文的窗口被销毁。</p><h3 id="检查窗口是否关闭"><a href="#检查窗口是否关闭" class="headerlink" title="检查窗口是否关闭"></a>检查窗口是否关闭</h3><p>每个窗口都有一个标志,指示该窗口是否应该关闭。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">while</span> (!glfwWindowShouldClose(window))</span><br><span class="line">{</span><br><span class="line"> <span class="comment">// Keep running</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>当用户试图关闭窗口时,通过按标题栏中的关闭小部件或使用像Alt+F4这样的组合键,该标志被设置为1。注意该窗口实际上并没有关闭,因此您应该监视该标志,并销毁该窗口或向用户提供某种反馈</p><p>当用户试图关闭窗口时,你可以通过使用glfwSetWindowCloseCallback设置一个关闭回调来得到通知。在关闭标志被设置后,回调函数将被立即调用。</p><h3 id="接受用户输入"><a href="#接受用户输入" class="headerlink" title="接受用户输入"></a>接受用户输入</h3><p>每个窗口都有大量的回调函数,可以将其设置为接收所有不同类型的事件。要接收按键按下和释放事件,需要创建按键回调函数</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">void</span> <span class="title">key_callback</span><span class="params">(GLFWwindow* window, <span class="keyword">int</span> key, <span class="keyword">int</span> scancode, <span class="keyword">int</span> action, <span class="keyword">int</span> mods)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="keyword">if</span> (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)</span><br><span class="line"> glfwSetWindowShouldClose(window, GLFW_TRUE);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>设置键盘回调</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">glfwSetKeyCallback(window, key_callback);</span><br></pre></td></tr></table></figure><h3 id="使用OpenGL渲染"><a href="#使用OpenGL渲染" class="headerlink" title="使用OpenGL渲染"></a>使用OpenGL渲染</h3><p>当有一个当前的OpenGL上下文,可以正常使用OpenGL</p><p>使用<code>glfwGetFramebufferSize</code>后去帧缓冲区大小,并为glViewport设置</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">int</span> width, height;</span><br><span class="line">glfwGetFramebufferSize(window, &width, &height);</span><br><span class="line">glViewport(<span class="number">0</span>, <span class="number">0</span>, width, height);</span><br></pre></td></tr></table></figure><h3 id="获得timer"><a href="#获得timer" class="headerlink" title="获得timer"></a>获得timer</h3><p>为了创建流畅的动画,需要一个时间源。GLFW提供了一个计时器,返回自初始化以来的秒数。所使用的时间源在每个平台上都是最精确的,通常具有微秒或纳秒分辨率</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">double</span> time = glfwGetTime();</span><br></pre></td></tr></table></figure><h3 id="交换缓冲区"><a href="#交换缓冲区" class="headerlink" title="交换缓冲区"></a>交换缓冲区</h3><p>默认情况下,GLFW窗口使用双缓冲。这意味着每个窗口都有两个渲染缓冲区;一个前缓冲,一个后缓冲。<strong>前缓冲区是要显示的缓冲区,后缓冲区是要渲染的缓冲区。</strong></p><p><strong>当整个帧被渲染后,缓冲区需要相互交换,所以后缓冲区变成前缓冲区</strong>,反之亦然</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">glfwSwapBuffers(window);</span><br></pre></td></tr></table></figure><p>交换间隔表示在交换缓冲区之前需要等待多少帧,通常称为vsync。默认情况下,交换间隔为零,这意味着缓冲区交换将立即发生。在快速的机器上,许多这些帧永远不会被看到,因为屏幕通常每秒只更新60-75次,所以这浪费了大量的CPU和GPU周期</p><blockquote><p>屏幕撕裂是视频显示中的视觉伪影,显示设备在单个屏幕绘制中显示来自多个帧的信息</p></blockquote><p>由于这些原因,应用程序通常希望将交换间隔设置为1。它可以设置为更高的值,但通常不建议这样做,因为它会导致输入延迟</p><h3 id="处理事件"><a href="#处理事件" class="headerlink" title="处理事件"></a>处理事件</h3><p>GLFW需要定期与窗口系统通信,以便接收事件并显示应用程序尚未锁定。事件处理必须在有可见窗口时定期执行,通常在缓冲区交换后的每一帧执行。</p><p>处理挂起事件有两种方法:<strong>轮询和等待</strong>。这个例子使用事件轮询,它只处理那些已经接收到的事件,然后立即返回。</p><figure class="highlight abnf"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">glfwPollEvents()<span class="comment">;</span></span><br></pre></td></tr></table></figure><h3 id="使用GLAD"><a href="#使用GLAD" class="headerlink" title="使用GLAD"></a>使用GLAD</h3><p>如果不用glad,opengl版本就与装的动态库与使用的gl头文件相关,如果使用glad,就能根据版本选择对应版本的库</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">gladLoadGL(glfwGetProcAddress);</span><br></pre></td></tr></table></figure><blockquote><p>一些老代码使用glfwGetProcAddress((GLADloadproc)glfwGetProcAddress)</p></blockquote><p>可以使用<code>gladLoadGL</code>,首先加载dll库,获取gl动态库中的<code>wglGetProcAddress</code>,得到这个函数方便获的gl的地址,调用<code>gladLoadGLLoader</code>传递<code>get_proc</code>,<code>get_proc</code>中调用类似<code>gladGetProcAddressPtr</code>,<code>GetProcAddress</code>操作获取对应的函数指针</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">gladLoadGL</span><span class="params">(<span class="keyword">void</span>)</span> </span>{</span><br><span class="line"> <span class="keyword">int</span> status = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span>(open_gl()) {</span><br><span class="line"> status = gladLoadGLLoader(&get_proc);</span><br><span class="line"> close_gl();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> status;</span><br><span class="line">}</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">open_gl</span><span class="params">(<span class="keyword">void</span>)</span> </span>{</span><br><span class="line"><span class="meta">#<span class="meta-keyword">ifndef</span> IS_UWP</span></span><br><span class="line"> libGL = LoadLibraryW(<span class="string">L"opengl32.dll"</span>);</span><br><span class="line"> <span class="keyword">if</span>(libGL != <span class="literal">NULL</span>) {</span><br><span class="line"> <span class="keyword">void</span> (* tmp)(<span class="keyword">void</span>);</span><br><span class="line"> tmp = (<span class="keyword">void</span>(*)(<span class="keyword">void</span>)) GetProcAddress(libGL, <span class="string">"wglGetProcAddress"</span>);</span><br><span class="line"> gladGetProcAddressPtr = (PFNWGLGETPROCADDRESSPROC_PRIVATE) tmp;</span><br><span class="line"> <span class="keyword">return</span> gladGetProcAddressPtr != <span class="literal">NULL</span>;</span><br><span class="line"> }</span><br><span class="line"><span class="meta">#<span class="meta-keyword">endif</span></span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span>* <span class="title">get_proc</span><span class="params">(<span class="keyword">const</span> <span class="keyword">char</span> *namez)</span> </span>{</span><br><span class="line"> <span class="keyword">void</span>* result = <span class="literal">NULL</span>;</span><br><span class="line"> <span class="keyword">if</span>(libGL == <span class="literal">NULL</span>) <span class="keyword">return</span> <span class="literal">NULL</span>;</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">if</span> !defined(__APPLE__) && !defined(__HAIKU__)</span></span><br><span class="line"> <span class="keyword">if</span>(gladGetProcAddressPtr != <span class="literal">NULL</span>) {</span><br><span class="line"> result = gladGetProcAddressPtr(namez);</span><br><span class="line"> }</span><br><span class="line"><span class="meta">#<span class="meta-keyword">endif</span></span></span><br><span class="line"> <span class="keyword">if</span>(result == <span class="literal">NULL</span>) {</span><br><span class="line"><span class="meta">#<span class="meta-keyword">if</span> defined(_WIN32) || defined(__CYGWIN__)</span></span><br><span class="line"> result = (<span class="keyword">void</span>*)GetProcAddress((HMODULE) libGL, namez);</span><br><span class="line"><span class="meta">#<span class="meta-keyword">else</span></span></span><br><span class="line"> result = dlsym(libGL, namez);</span><br><span class="line"><span class="meta">#<span class="meta-keyword">endif</span></span></span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> result;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>在<code>gladLoadGLLoader</code>中将利用刚才的<code>get_proc</code>函数</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">gladLoadGLLoader</span><span class="params">(GLADloadproc load)</span> </span>{</span><br><span class="line">GLVersion.major = <span class="number">0</span>; GLVersion.minor = <span class="number">0</span>;</span><br><span class="line">glGetString = (PFNGLGETSTRINGPROC)load(<span class="string">"glGetString"</span>);</span><br><span class="line"><span class="keyword">if</span>(glGetString == <span class="literal">NULL</span>) <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line"><span class="keyword">if</span>(glGetString(GL_VERSION) == <span class="literal">NULL</span>) <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">find_coreGL();</span><br><span class="line">load_GL_VERSION_1_0(load);</span><br><span class="line">load_GL_VERSION_1_1(load);</span><br><span class="line">load_GL_VERSION_1_2(load);</span><br><span class="line">load_GL_VERSION_1_3(load);</span><br><span class="line">load_GL_VERSION_1_4(load);</span><br><span class="line">load_GL_VERSION_1_5(load);</span><br><span class="line">load_GL_VERSION_2_0(load);</span><br><span class="line">load_GL_VERSION_2_1(load);</span><br><span class="line">load_GL_VERSION_3_0(load);</span><br><span class="line">load_GL_VERSION_3_1(load);</span><br><span class="line">load_GL_VERSION_3_2(load);</span><br><span class="line">load_GL_VERSION_3_3(load);</span><br><span class="line">load_GL_VERSION_4_0(load);</span><br><span class="line">load_GL_VERSION_4_1(load);</span><br><span class="line">load_GL_VERSION_4_2(load);</span><br><span class="line">load_GL_VERSION_4_3(load);</span><br><span class="line">load_GL_VERSION_4_4(load);</span><br><span class="line">load_GL_VERSION_4_5(load);</span><br><span class="line">load_GL_VERSION_4_6(load);</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (!find_extensionsGL()) <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line"><span class="keyword">return</span> GLVersion.major != <span class="number">0</span> || GLVersion.minor != <span class="number">0</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">PFNGLGETSTRINGPROC glad_glGetString = <span class="literal">NULL</span>;</span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> glGetString glad_glGetString</span></span><br></pre></td></tr></table></figure><p>首先获取gl获取版本的函数,然后根据版本加载</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">void</span> <span class="title">find_coreGL</span><span class="params">(<span class="keyword">void</span>)</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="comment">/* Thank you @elmindreda</span></span><br><span class="line"><span class="comment"> * https://github.com/elmindreda/greg/blob/master/templates/greg.c.in#L176</span></span><br><span class="line"><span class="comment"> * https://github.com/glfw/glfw/blob/master/src/context.c#L36</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">int</span> i, major, minor;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">const</span> <span class="keyword">char</span>* version;</span><br><span class="line"> <span class="keyword">const</span> <span class="keyword">char</span>* prefixes[] = {</span><br><span class="line"> <span class="string">"OpenGL ES-CM "</span>,</span><br><span class="line"> <span class="string">"OpenGL ES-CL "</span>,</span><br><span class="line"> <span class="string">"OpenGL ES "</span>,</span><br><span class="line"> <span class="literal">NULL</span></span><br><span class="line"> };</span><br><span class="line"></span><br><span class="line"> version = (<span class="keyword">const</span> <span class="keyword">char</span>*) glGetString(GL_VERSION);</span><br><span class="line"> <span class="keyword">if</span> (!version) <span class="keyword">return</span>;</span><br><span class="line"><span class="comment">// 根据获取到的版本,匹配对应的前缀,再加上</span></span><br><span class="line"> <span class="keyword">for</span> (i = <span class="number">0</span>; prefixes[i]; i++) {</span><br><span class="line"> <span class="keyword">const</span> <span class="keyword">size_t</span> length = <span class="built_in">strlen</span>(prefixes[i]);</span><br><span class="line"> <span class="keyword">if</span> (<span class="built_in">strncmp</span>(version, prefixes[i], length) == <span class="number">0</span>) {</span><br><span class="line"> version += length;</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"><span class="comment">/* PR #18 */</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">ifdef</span> _MSC_VER</span></span><br><span class="line"> sscanf_s(version, <span class="string">"%d.%d"</span>, &major, &minor);</span><br><span class="line"><span class="meta">#<span class="meta-keyword">else</span></span></span><br><span class="line"> <span class="built_in">sscanf</span>(version, <span class="string">"%d.%d"</span>, &major, &minor);</span><br><span class="line"><span class="meta">#<span class="meta-keyword">endif</span></span></span><br><span class="line"></span><br><span class="line"> GLVersion.major = major; GLVersion.minor = minor;</span><br><span class="line"> max_loaded_major = major; max_loaded_minor = minor;</span><br><span class="line">GLAD_GL_VERSION_1_0 = (major == <span class="number">1</span> && minor >= <span class="number">0</span>) || major > <span class="number">1</span>;</span><br><span class="line">GLAD_GL_VERSION_1_1 = (major == <span class="number">1</span> && minor >= <span class="number">1</span>) || major > <span class="number">1</span>;</span><br><span class="line">GLAD_GL_VERSION_1_2 = (major == <span class="number">1</span> && minor >= <span class="number">2</span>) || major > <span class="number">1</span>;</span><br><span class="line">GLAD_GL_VERSION_1_3 = (major == <span class="number">1</span> && minor >= <span class="number">3</span>) || major > <span class="number">1</span>;</span><br><span class="line">GLAD_GL_VERSION_1_4 = (major == <span class="number">1</span> && minor >= <span class="number">4</span>) || major > <span class="number">1</span>;</span><br><span class="line">GLAD_GL_VERSION_1_5 = (major == <span class="number">1</span> && minor >= <span class="number">5</span>) || major > <span class="number">1</span>;</span><br><span class="line">GLAD_GL_VERSION_2_0 = (major == <span class="number">2</span> && minor >= <span class="number">0</span>) || major > <span class="number">2</span>;</span><br><span class="line">GLAD_GL_VERSION_2_1 = (major == <span class="number">2</span> && minor >= <span class="number">1</span>) || major > <span class="number">2</span>;</span><br><span class="line">GLAD_GL_VERSION_3_0 = (major == <span class="number">3</span> && minor >= <span class="number">0</span>) || major > <span class="number">3</span>;</span><br><span class="line">GLAD_GL_VERSION_3_1 = (major == <span class="number">3</span> && minor >= <span class="number">1</span>) || major > <span class="number">3</span>;</span><br><span class="line">GLAD_GL_VERSION_3_2 = (major == <span class="number">3</span> && minor >= <span class="number">2</span>) || major > <span class="number">3</span>;</span><br><span class="line">GLAD_GL_VERSION_3_3 = (major == <span class="number">3</span> && minor >= <span class="number">3</span>) || major > <span class="number">3</span>;</span><br><span class="line">GLAD_GL_VERSION_4_0 = (major == <span class="number">4</span> && minor >= <span class="number">0</span>) || major > <span class="number">4</span>;</span><br><span class="line">GLAD_GL_VERSION_4_1 = (major == <span class="number">4</span> && minor >= <span class="number">1</span>) || major > <span class="number">4</span>;</span><br><span class="line">GLAD_GL_VERSION_4_2 = (major == <span class="number">4</span> && minor >= <span class="number">2</span>) || major > <span class="number">4</span>;</span><br><span class="line">GLAD_GL_VERSION_4_3 = (major == <span class="number">4</span> && minor >= <span class="number">3</span>) || major > <span class="number">4</span>;</span><br><span class="line">GLAD_GL_VERSION_4_4 = (major == <span class="number">4</span> && minor >= <span class="number">4</span>) || major > <span class="number">4</span>;</span><br><span class="line">GLAD_GL_VERSION_4_5 = (major == <span class="number">4</span> && minor >= <span class="number">5</span>) || major > <span class="number">4</span>;</span><br><span class="line">GLAD_GL_VERSION_4_6 = (major == <span class="number">4</span> && minor >= <span class="number">6</span>) || major > <span class="number">4</span>;</span><br><span class="line"><span class="keyword">if</span> (GLVersion.major > <span class="number">4</span> || (GLVersion.major >= <span class="number">4</span> && GLVersion.minor >= <span class="number">6</span>)) {</span><br><span class="line">max_loaded_major = <span class="number">4</span>;</span><br><span class="line">max_loaded_minor = <span class="number">6</span>;</span><br><span class="line">}</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>可以看到如果版本大于某个大版本,会加载对应的函数,也就是说1.0加载的东西,在1.1的函数中就不会有了,文件中定义了许多函数指针</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">typedef</span> <span class="title">void</span> <span class="params">(APIENTRYP PFNGLGENSAMPLERSPROC)</span><span class="params">(GLsizei count, GLuint *samplers)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">typedef</span> <span class="title">void</span> <span class="params">(APIENTRYP PFNGLGENTRANSFORMFEEDBACKSPROC)</span><span class="params">(GLsizei n, GLuint *ids)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">typedef</span> <span class="title">void</span> <span class="params">(APIENTRYP PFNGLGENVERTEXARRAYSPROC)</span><span class="params">(GLsizei n, GLuint *arrays)</span></span>;</span><br><span class="line">PFNGLGENTEXTURESPROC glad_glGenTextures = <span class="literal">NULL</span>;</span><br><span class="line">PFNGLGENTRANSFORMFEEDBACKSPROC glad_glGenTransformFeedbacks = <span class="literal">NULL</span>;</span><br><span class="line">PFNGLGENVERTEXARRAYSPROC glad_glGenVertexArrays = <span class="literal">NULL</span>;</span><br><span class="line">PFNGLGENERATEMIPMAPPROC glad_glGenerateMipmap = <span class="literal">NULL</span>;</span><br><span class="line">PFNGLGENERATETEXTUREMIPMAPPROC glad_glGenerateTextureMipmap = <span class="literal">NULL</span>;</span><br><span class="line">PFNGLGETACTIVEATOMICCOUNTERBUFFERIVPROC glad_glGetActiveAtomicCounterBufferiv = <span class="literal">NULL</span>;</span><br><span class="line">PFNGLGETACTIVEATTRIBPROC glad_glGetActiveAttrib = <span class="literal">NULL</span>;</span><br><span class="line">PFNGLGETACTIVESUBROUTINENAMEPROC glad_glGetActiveSubroutineName = <span class="literal">NULL</span>;</span><br></pre></td></tr></table></figure><p>当加载完毕后即可直接使用glad_xx调用glxx库.</p><blockquote><p>glad相当于在知道使用的gl.dll版本之后加载相应版本所有的头文件,加载后即可使用gladxx,而原本的gl头文件实际上就不需要了(除非你还需要在代码中直接使用gl头文件)</p></blockquote><p><img data-src="https://s2.loli.net/2024/10/05/1KpBUDEAqzQlGYn.png" alt="image-20241005230313466"></p><p>在<code>glad.h</code>中如果之前定义了<code>__gl_h_</code>宏,那就会报错,否则自己会定义一个<code>__gl_h</code></p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// glad.h</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">ifndef</span> __glad_h_</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> __glad_h_</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">ifdef</span> __gl_h_</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">error</span> OpenGL header already included, remove this <span class="meta-keyword">include</span>, glad already provides it</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">endif</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> __gl_h_</span></span><br></pre></td></tr></table></figure><p>而在<code>gl.h</code>中,定义了这个宏,表明使用glad并不需要引入gl头文件</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// gl.h</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">ifndef</span> __gl_h_</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">ifndef</span> __GL_H__</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> __gl_h_</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> __GL_H__</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><winapifamily.h></span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">ifdef</span> __cplusplus</span></span><br><span class="line"><span class="keyword">extern</span> <span class="string">"C"</span> {</span><br><span class="line"><span class="meta">#<span class="meta-keyword">endif</span></span></span><br></pre></td></tr></table></figure><p>而在<code>glfw3.h</code>中也说明了如果定义了<code>__gl_h_</code>,就不需要再引入头文件了</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">elif</span> !defined(GLFW_INCLUDE_NONE) && \</span></span><br><span class="line"><span class="meta"> !defined(__gl_h_) && \</span></span><br><span class="line"><span class="meta"> !defined(__gles1_gl_h_) && \</span></span><br><span class="line"><span class="meta"> !defined(__gles2_gl2_h_) && \</span></span><br><span class="line"><span class="meta"> !defined(__gles2_gl3_h_) && \</span></span><br><span class="line"><span class="meta"> !defined(__gles2_gl31_h_) && \</span></span><br><span class="line"><span class="meta"> !defined(__gles2_gl32_h_) && \</span></span><br><span class="line"><span class="meta"> !defined(__gl_glcorearb_h_) && \</span></span><br><span class="line"><span class="meta"> !defined(__gl2_h_) <span class="comment">/*legacy*/</span> && \</span></span><br><span class="line"><span class="meta"> !defined(__gl3_h_) <span class="comment">/*legacy*/</span> && \</span></span><br><span class="line"><span class="meta"> !defined(__gl31_h_) <span class="comment">/*legacy*/</span> && \</span></span><br><span class="line"><span class="meta"> !defined(__gl32_h_) <span class="comment">/*legacy*/</span> && \</span></span><br><span class="line"><span class="meta"> !defined(__glcorearb_h_) <span class="comment">/*legacy*/</span> && \</span></span><br><span class="line"><span class="meta"> !defined(__GL_H__) <span class="comment">/*non-standard*/</span> && \</span></span><br><span class="line"><span class="meta"> !defined(__gltypes_h_) <span class="comment">/*non-standard*/</span> && \</span></span><br><span class="line"><span class="meta"> !defined(__glee_h_) <span class="comment">/*non-standard*/</span></span></span><br><span class="line"></span><br><span class="line"> <span class="meta">#<span class="meta-keyword">if</span> defined(__APPLE__)</span></span><br><span class="line"></span><br><span class="line"> <span class="meta">#<span class="meta-keyword">if</span> !defined(GLFW_INCLUDE_GLEXT)</span></span><br><span class="line"> <span class="meta">#<span class="meta-keyword">define</span> GL_GLEXT_LEGACY</span></span><br><span class="line"> <span class="meta">#<span class="meta-keyword">endif</span></span></span><br><span class="line"> <span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><OpenGL/gl.h></span></span></span><br><span class="line"></span><br><span class="line"> <span class="meta">#<span class="meta-keyword">else</span> <span class="comment">/*__APPLE__*/</span></span></span><br><span class="line"></span><br><span class="line"> <span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><GL/gl.h></span></span></span><br><span class="line"> <span class="meta">#<span class="meta-keyword">if</span> defined(GLFW_INCLUDE_GLEXT)</span></span><br><span class="line"> <span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><GL/glext.h></span></span></span><br><span class="line"> <span class="meta">#<span class="meta-keyword">endif</span></span></span><br><span class="line"></span><br><span class="line"> <span class="meta">#<span class="meta-keyword">endif</span> <span class="comment">/*__APPLE__*/</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">endif</span> <span class="comment">/* OpenGL and OpenGL ES headers */</span></span></span><br></pre></td></tr></table></figure><h2 id="初始化"><a href="#初始化" class="headerlink" title="初始化"></a>初始化</h2><p>在调用大多数GLFW函数之前,必须初始化库。这个初始化<strong>检查机器上可用的特性、显示器</strong>、<strong>初始化计时器</strong>并执行任何所需的<strong>特定于平台的初始化</strong>。</p><p>在调用<code>glfwInit</code>之前可以调用一些函数,比如获取版本,平台支持,设置错误处理回调等.</p><ul><li><a href="https://www.glfw.org/docs/latest/group__init.html#ga9f8ffaacf3c269cc48eafbf8b9b71197">glfwGetVersion</a></li><li><a href="https://www.glfw.org/docs/latest/group__init.html#ga026abd003c8e6501981ab1662062f1c0">glfwGetVersionString</a></li><li><a href="https://www.glfw.org/docs/latest/group__init.html#ga8785d2b6b36632368d803e78079d38ed">glfwPlatformSupported</a></li><li><a href="https://www.glfw.org/docs/latest/group__init.html#ga944986b4ec0b928d488141f92982aa18">glfwGetError</a></li><li><a href="https://www.glfw.org/docs/latest/group__init.html#gaff45816610d53f0b83656092a4034f40">glfwSetErrorCallback</a></li><li><a href="https://www.glfw.org/docs/latest/group__init.html#ga110fd1d3f0412822b4f1908c026f724a">glfwInitHint</a></li><li><a href="https://www.glfw.org/docs/latest/group__init.html#ga9dde93e9891fa7dd17e4194c9f3ae7c6">glfwInitAllocator</a></li><li><a href="https://www.glfw.org/docs/latest/group__init.html#ga76af552d0307bb5f7791f245417d4752">glfwInitVulkanLoader</a></li><li><a href="https://www.glfw.org/docs/latest/group__init.html#ga317aac130a235ab08c6db0834907d85e">glfwInit</a></li><li><a href="https://www.glfw.org/docs/latest/group__init.html#gaaae48c0a18607ea4a4ba951d939f0901">glfwTerminate</a></li></ul><h3 id="hints"><a href="#hints" class="headerlink" title="hints"></a>hints</h3><p>hints用于设置GLFW,包括共享/通用的hints以及platform-specific的hints.</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">glfwInitHint(GLFW_JOYSTICK_HAT_BUTTONS, GLFW_FALSE);</span><br><span class="line">glfwInitHint(GLFW_PLATFORM, GLFW_PLATFORM_X11);</span><br></pre></td></tr></table></figure><h3 id="自定义内存分配器"><a href="#自定义内存分配器" class="headerlink" title="自定义内存分配器"></a>自定义内存分配器</h3><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">GLFWallocator allocator;</span><br><span class="line">allocator.allocate = my_malloc;</span><br><span class="line">allocator.reallocate = my_realloc;</span><br><span class="line">allocator.deallocate = my_free;</span><br><span class="line">allocator.user = <span class="literal">NULL</span>;</span><br><span class="line"> </span><br><span class="line">glfwInitAllocator(&allocator);</span><br></pre></td></tr></table></figure><h3 id="设置错误处理-1"><a href="#设置错误处理-1" class="headerlink" title="设置错误处理"></a>设置错误处理</h3><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">int</span> code = glfwGetError(<span class="literal">NULL</span>);</span><br><span class="line"> </span><br><span class="line"><span class="keyword">if</span> (code != GLFW_NO_ERROR)</span><br><span class="line"> handle_error(code);</span><br></pre></td></tr></table></figure><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">glfwSetErrorCallback(error_callback);</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">error_callback</span><span class="params">(<span class="keyword">int</span> code, <span class="keyword">const</span> <span class="keyword">char</span>* description)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> display_error_message(code, description);</span><br><span class="line">} </span><br></pre></td></tr></table></figure><p>只要GLFW成功初始化,无论发生多少错误,它都将保持初始化并处于安全状态,直到终止。如果在初始化过程中发生错误导致glfwInit失败,则初始化库的任何部分都将被安全终止</p><h3 id="坐标系统"><a href="#坐标系统" class="headerlink" title="坐标系统"></a>坐标系统</h3><p>GLFW有两个主要的坐标系统,虚拟屏幕和窗口内容区域.</p><p><img data-src="https://s2.loli.net/2024/10/08/RwYs2HjEdPLDOiu.png" alt="image-20241008191903556"></p><p> 虚拟屏幕和内容区域坐标系统的x轴指向右,y轴指向下.</p><p> 窗口和显示器的位置指定为其内容区域的左上角相对于虚拟屏幕的位置,而光标的位置指定为相对于窗口的内容区域的位置</p><p> 由于窗口的内容区域坐标系统的原点也是指定窗口位置的点,因此可以通过添加窗口位置将内容区域坐标转换为虚拟屏幕。当窗口frame出现时,它从内容区域向外延伸,但不影响窗口位置</p><p> GLFW中几乎所有的位置和大小都是以相对于上述两个原点之一的屏幕坐标来测量的。这包括光标位置、窗口位置和大小、窗口frame大小、显示器位置和视频分辨率.</p><p> 显示器的物理大小以毫米为单位和帧缓冲区大小(以像素为单位)。</p><p> 像素和屏幕坐标在一些机器上可能是1:1的映射,但在其他机器上就不一定了,比如在带有Retina显示屏的Mac上dpr是2:1。屏幕坐标和像素之间的比率也可能在运行时改变,这取决于窗口当前被认为在哪个显示器上</p><h2 id="窗口"><a href="#窗口" class="headerlink" title="窗口"></a>窗口</h2><p>窗口对象封装了顶级窗口和OpenGL或OpenGL ES上下文。它是用glfwCreateWindow创建的,用glfwDestroyWindow或glfwTerminate销毁</p><p>由于窗口和上下文是不可分割地联系在一起的,窗口对象也充当上下文句柄。</p><h3 id="设置窗口的一些属性"><a href="#设置窗口的一些属性" class="headerlink" title="设置窗口的一些属性"></a>设置窗口的一些属性</h3><p>想像一下,一个窗口能有哪些东西? 标题,显示器,位置,大小,是否透明,最小化,最大化,焦点,图标. 这些属性有些可以使用hints设置,有些有单独的函数设置,还可以使用<code>glfwSetWindowAttrib</code>设置.</p><p>需要注意的是Framebuffer size和window size,window size是虚拟屏幕坐标,而framebuffer size是pixel,适合使用glviewport</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">int</span> width, height;</span><br><span class="line">glfwGetFramebufferSize(window, &width, &height);</span><br><span class="line">glViewport(<span class="number">0</span>, <span class="number">0</span>, width, height);</span><br></pre></td></tr></table></figure><h3 id="窗口属性"><a href="#窗口属性" class="headerlink" title="窗口属性"></a>窗口属性</h3><p>Windows有许多属性可以使用glfwGetWindowAttrib返回。一些反映了可能由于用户交互而改变的状态(例如是否有输入焦点),而另一些反映了窗口的固有属性(例如它有什么样的边界)。一些与窗口相关,另一些与OpenGL或OpenGL ES上下文相关</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (glfwGetWindowAttrib(window, GLFW_FOCUSED))</span><br><span class="line">{</span><br><span class="line"> <span class="comment">// window has input focus</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">glfwSetWindowAttrib(window, GLFW_RESIZABLE, GLFW_FALSE);</span><br></pre></td></tr></table></figure><h3 id="buffer交换"><a href="#buffer交换" class="headerlink" title="buffer交换"></a>buffer交换</h3><p>默认情况下,GLFW窗口是双缓冲的。这意味着有两个渲染缓冲区;一个前缓冲,一个后缓冲。前缓冲区是要显示的缓冲区,后缓冲区是要渲染的缓冲区</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">glfwSwapBuffers(window);</span><br></pre></td></tr></table></figure><p>选择何时进行缓冲区交换是很有用的。使用函数glfwSwapInterval,可以选择驱动程序在交换缓冲区之前从调用glfwSwapBuffers开始应该等待的监视器刷新的最小次数</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">glfwSwapInterval(<span class="number">1</span>);</span><br></pre></td></tr></table></figure><p>如果间隔为零,则在调用glfwSwapBuffers时立即进行交换,而无需等待刷新。否则,每次缓冲区交换之间至少会传递间隔回溯。当不希望测量等待垂直回溯所需的时间时,使用零交换间隔对基准测试很有用。但是,交换间隔为1可以避免撕裂</p><h2 id="上下文"><a href="#上下文" class="headerlink" title="上下文"></a>上下文</h2><p>当使用glfwCreateWindow创建一个窗口和它的OpenGL或OpenGL ES上下文时,可以指定另一个窗口,它的上下文应该与新窗口共享它的对象(纹理,顶点和元素缓冲区等)</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">GLFWwindow* second_window = glfwCreateWindow(<span class="number">640</span>, <span class="number">480</span>, <span class="string">"Second Window"</span>, <span class="literal">NULL</span>, first_window);</span><br></pre></td></tr></table></figure><p>在你进行OpenGL或OpenGL ES调用之前,需要有一个正确类型的当前上下文。一个上下文一次只能对一个线程是当前的,而一个线程一次只能有一个当前的上下文</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">glfwMakeContextCurrent(window);</span><br><span class="line">GLFWwindow* window = glfwGetCurrentContext();</span><br></pre></td></tr></table></figure><h3 id="显示器"><a href="#显示器" class="headerlink" title="显示器"></a>显示器</h3><p>显示器对象表示当前连接的显示器,并表示为指向不透明类型GLFWmonitor的指针。显示器对象不能由应用程序创建或销毁,并保留其地址,直到它们所代表的显示器断开连接或直到库终止</p><p>每个显示器都有一个当前视频模式,一个支持的视频模式列表,一个虚拟位置,一个人类可读的名称,一个估计的物理尺寸和一个gamma ramp。其中一个监控器是主监控器.显示器的虚拟位置以屏幕坐标表示,并与当前视频模式一起描述了连接的显示器提供给跨越它们的虚拟桌面的视口</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">GLFWmonitor* primary = glfwPrimaryMonitor();</span><br><span class="line"><span class="keyword">int</span> count;</span><br><span class="line">GLFWmonitor** monitors = glfwGetMonitors(&count);</span><br></pre></td></tr></table></figure><h4 id="显示器配置改变"><a href="#显示器配置改变" class="headerlink" title="显示器配置改变"></a>显示器配置改变</h4><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">glfwSetMonitorCallback(monitor_callback);</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">monitor_callback</span><span class="params">(GLFWmonitor* monitor, <span class="keyword">int</span> event)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="keyword">if</span> (event == GLFW_CONNECTED)</span><br><span class="line"> {</span><br><span class="line"> <span class="comment">// The monitor was connected</span></span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span> <span class="keyword">if</span> (event == GLFW_DISCONNECTED)</span><br><span class="line"> {</span><br><span class="line"> <span class="comment">// The monitor was disconnected</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="显示器属性"><a href="#显示器属性" class="headerlink" title="显示器属性"></a>显示器属性</h4><p>当创建一个全屏窗口时,改变它的视频模式或使窗口成为一个全屏,GLFW通常会很好地选择一个合适的视频模式,但有时确切地知道支持哪些视频模式是有用的。</p><p>视频模式表示为GLFWvidmode结构。您可以使用glfwGetVideoModes获得监视器支持的视频模式数组。有关返回数组的生命周期</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">int</span> count;</span><br><span class="line">GLFWvidmode* modes = glfwGetVideoModes(monitor, &count);</span><br><span class="line"><span class="keyword">const</span> GLFWvidmode* mode = glfwGetVideoMode(monitor);</span><br></pre></td></tr></table></figure><p>监视器的物理尺寸(以毫米为单位)或其估计值可以使用glfwGetMonitorPhysicalSize来检索。这与当前分辨率无关</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">int</span> width_mm, height_mm;</span><br><span class="line">glfwGetMonitorPhysicalSize(monitor, &width_mm, &height_mm);</span><br></pre></td></tr></table></figure><p>可以使用glfwGetMonitorPos获取监视器在虚拟桌面上的位置(以屏幕坐标表示)。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">int</span> xpos, ypos;</span><br><span class="line">glfwGetMonitorPos(monitor, &xpos, &ypos);</span><br></pre></td></tr></table></figure><p>未被全局任务栏或菜单栏占用的监视器区域是工作区域。这是在屏幕坐标中指定的,可以使用glfwGetMonitorWorkarea进行检索</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">int</span> xpos, ypos, width, height;</span><br><span class="line">glfwGetMonitorWorkarea(monitor, &xpos, &ypos, &width, &height);</span><br></pre></td></tr></table></figure><h3 id="输入"><a href="#输入" class="headerlink" title="输入"></a>输入</h3><p>GLFW提供多种输入。虽然有些只能轮询,如时间,或只能通过回调接收,如滚动,但许多同时提供回调和轮询。回调要比轮询做更多的工作,但CPU密集程度较低,并保证不会错过状态更改</p><p>所有输入回调都接收一个窗口句柄。通过使用窗口用户指针,您可以从回调中访问非全局结构或对象。</p><h4 id="事件处理"><a href="#事件处理" class="headerlink" title="事件处理"></a>事件处理</h4><p>GLFW需要轮询窗口系统的事件,以便向应用程序提供输入,并向窗口系统证明应用程序没有锁定。事件处理通常在缓冲区交换后的每一帧完成。即使没有窗口,也需要进行事件轮询,以便接收监视器和操纵杆连接事件</p><p>有三个函数用于处理挂起事件</p><p>glfwPollEvents,只处理那些已经接收到的事件,然后立即返回。</p><p>如果只需要在接收新输入时更新窗口的内容,那么glfwWaitEvents是更好的选择</p><p>它将线程置于睡眠状态,直到至少接收到一个事件,然后处理所有接收到的事件。这节省了大量的CPU周期,并且对于编辑工具等非常有用。</p><p>如果你想等待事件,但有UI元素或其他需要定期更新的任务,glfwWaitEventsTimeout允许你指定一个超时。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">glfwWaitEventsTimeout(<span class="number">0.7</span>);</span><br></pre></td></tr></table></figure><p>如果主线程在glfwWaitEvents中休眠,可以通过使用glfwPostEmptyEvent向事件队列发送一个空事件来从另一个线程唤醒它</p><p>不要假设回调只会在响应上述函数时被调用。虽然有必要以上述一种或多种方式处理事件,但需要GLFW注册其自身回调的窗口系统可以将事件传递给GLFW以响应许多窗口系统函数调用。GLFW将在返回之前将这些事件传递给应用程序回调</p><h4 id="键盘输入"><a href="#键盘输入" class="headerlink" title="键盘输入"></a>键盘输入</h4><p>GLFW将键盘输入分为两类;关键事件和角色事件。键事件与实际的物理键盘键有关,而字符事件与按下其中一些键产生的文本有关</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">key_callback</span><span class="params">(GLFWwindow* window, <span class="keyword">int</span> key, <span class="keyword">int</span> scancode, <span class="keyword">int</span> action, <span class="keyword">int</span> mods)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="keyword">if</span> (key == GLFW_KEY_E && action == GLFW_PRESS)</span><br><span class="line"> activate_airship();</span><br><span class="line">}</span><br><span class="line">glfwSetKeyCallback(window, key_callback);</span><br></pre></td></tr></table></figure><p>GLFW支持由操作系统文本输入系统生成的Unicode码点流形式的文本输入。与按键输入不同,文本输入受键盘布局和修改键的影响</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">character_callback</span><span class="params">(GLFWwindow* window, <span class="keyword">unsigned</span> <span class="keyword">int</span> codepoint)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line">}</span><br><span class="line">glfwSetCharCallback(window, character_callback);</span><br></pre></td></tr></table></figure><h4 id="鼠标输入"><a href="#鼠标输入" class="headerlink" title="鼠标输入"></a>鼠标输入</h4><p>鼠标输入有多种形式,包括鼠标移动、按钮按压和滚动偏移。还可以更改光标的外观,将其更改为自定义图像或来自系统主题的标准光标形状</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">double</span> xpos, ypos;</span><br><span class="line">glfwGetCursorPos(window, &xpos, &ypos);</span><br><span class="line">glfwSetCursorPosCallback(window, cursor_position_callback);</span><br></pre></td></tr></table></figure><p>回调函数接收光标位置,以屏幕坐标测量,但相对于窗口内容区域的左上角。。</p><p>如果希望实现基于鼠标运动的相机控制或其他需要无限制鼠标移动的输入方案,请将光标模式设置为<code>GLFW_CURSOR_DISABLED</code>.</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);</span><br></pre></td></tr></table></figure><p>这将隐藏光标并将其锁定到指定的窗口。然后,GLFW将处理光标重新居中和偏移计算的所有细节,并为应用程序提供虚拟光标位置。这个虚拟位置通常通过回调和轮询提供。原始鼠标运动更接近鼠标在表面上的实际运动。它不受应用于桌面光标运动的缩放和加速的影响。这种处理适合于光标,而原始运动更适合于控制,例如3D相机。因此,仅在禁用光标时才提供原始鼠标运动</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (glfwRawMouseMotionSupported())</span><br><span class="line"> glfwSetInputMode(window, GLFW_RAW_MOUSE_MOTION, GLFW_TRUE);</span><br></pre></td></tr></table></figure><p>使用glfwCreateCursor创建自定义鼠标,它返回创建的鼠标对象的句柄</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">unsigned</span> <span class="keyword">char</span> pixels[<span class="number">16</span> * <span class="number">16</span> * <span class="number">4</span>];</span><br><span class="line"><span class="built_in">memset</span>(pixels, <span class="number">0xff</span>, <span class="keyword">sizeof</span>(pixels));</span><br><span class="line"> </span><br><span class="line">GLFWimage image;</span><br><span class="line">image.width = <span class="number">16</span>;</span><br><span class="line">image.height = <span class="number">16</span>;</span><br><span class="line">image.pixels = pixels;</span><br><span class="line"> </span><br><span class="line">GLFWcursor* cursor = glfwCreateCursor(&image, <span class="number">0</span>, <span class="number">0</span>);</span><br></pre></td></tr></table></figure><p>可以使用glfwCreateStandardCursor创建当前系统游标主题中具有标准形状的游标</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">GLFWcursor* url_cursor = glfwCreateStandardCursor(GLFW_POINTING_HAND_CURSOR);</span><br></pre></td></tr></table></figure><p>如果希望在光标进入或离开窗口的内容区域时得到通知设置光标进入/离开回调</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">cursor_enter_callback</span><span class="params">(GLFWwindow* window, <span class="keyword">int</span> entered)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="keyword">if</span> (entered)</span><br><span class="line"> {</span><br><span class="line"> <span class="comment">// The cursor entered the content area of the window</span></span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span></span><br><span class="line"> {</span><br><span class="line"> <span class="comment">// The cursor left the content area of the window</span></span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line">glfwSetCursorEnterCallback(window, cursor_enter_callback);</span><br></pre></td></tr></table></figure><p>可以查询鼠标当前是否在具有glfw_hoved窗口属性的窗口的内容区域内</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (glfwGetWindowAttrib(window, GLFW_HOVERED))</span><br><span class="line">{</span><br><span class="line"> highlight_interface();</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="鼠标按钮输入"><a href="#鼠标按钮输入" class="headerlink" title="鼠标按钮输入"></a>鼠标按钮输入</h4><p>如果希望在鼠标按钮被按下或释放时收到通知设置鼠标按钮回调</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">glfwSetMouseButtonCallback(window, mouse_button_callback);</span><br></pre></td></tr></table></figure><p>回调函数接收鼠标按钮、按钮动作和修饰符位</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">mouse_button_callback</span><span class="params">(GLFWwindow* window, <span class="keyword">int</span> button, <span class="keyword">int</span> action, <span class="keyword">int</span> mods)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="keyword">if</span> (button == GLFW_MOUSE_BUTTON_RIGHT && action == GLFW_PRESS)</span><br><span class="line"> popup_menu();</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>每个受支持的鼠标按钮的最后状态也保存在每个窗口状态数组中,可以使用glfwGetMouseButton轮询。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">int</span> state = glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_LEFT);</span><br><span class="line"><span class="keyword">if</span> (state == GLFW_PRESS)</span><br><span class="line">{</span><br><span class="line"> upgrade_cow();</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="滚动"><a href="#滚动" class="headerlink" title="滚动"></a>滚动</h4><figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">glfw<span class="constructor">SetScrollCallback(<span class="params">window</span>, <span class="params">scroll_callback</span>)</span>;</span><br><span class="line">void scroll<span class="constructor">_callback(GLFWwindow<span class="operator">*</span> <span class="params">window</span>, <span class="params">double</span> <span class="params">xoffset</span>, <span class="params">double</span> <span class="params">yoffset</span>)</span></span><br><span class="line">{</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>普通的鼠标滚轮是垂直的,它提供沿y轴的偏移量</p><h2 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h2><p>1.如何在windows上去掉启动时出现的控制台(使用vs或cmake)<a href="https://stackoverflow.com/questions/5995433/removing-console-window-for-glut-freeglut-glfw">c++ - Removing console window for Glut/FreeGlut/GLFW? - Stack Overflow</a><a href="https://stackoverflow.com/questions/78704285/how-do-i-remove-the-console-window-in-a-c-application-in-visual-studio">How do I remove the console window in a C++ application in Visual Studio? - Stack Overflow</a> 但是似乎只对cl.exe也就是MSVC管用,无法跨平台了,只能针对不同平台分别编译,gcc/clang(但测试了貌似不管用)可以使用<code>-mwindows</code><a href="https://discourse.glfw.org/t/eliminate-shell-window/354/2">Eliminate shell window? - support - GLFW</a></p><p>在vs上在配置属性,链接器,系统上修改subsystem以及高级中的entry point</p><p>设置应用为窗口应用,由于它默认需要wmain函数而不是main函数,还需要修改入口函数为main<a href="https://learn.microsoft.com/zh-cn/cpp/build/reference/entry-entry-point-symbol?view=msvc-170">/ENTRY(入口点符号) | Microsoft Learn</a>.</p><p><img data-src="https://s2.loli.net/2024/10/06/l5MFVcaY4thObmx.png" alt="image-20241006151255023"></p><p><img data-src="https://s2.loli.net/2024/10/06/8jMH3BaLvzRJwEl.png" alt="image-20241006151328843"></p><p>在cmake上类似,但需要使用<code>clang-cl.exe</code>(而不是clang.exe),<code>msvc</code>作为generator(不能是其他的).</p><figure class="highlight cmake"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span>(CMAKE_HOST_SYSTEM_NAME <span class="keyword">STREQUAL</span> <span class="string">"Windows"</span> <span class="keyword">AND</span> CMAKE_CXX_COMPILER_ID <span class="keyword">STREQUAL</span> <span class="string">"MSVC"</span>)</span><br><span class="line"> <span class="keyword">target_link_options</span>(learn_gl PRIVATE /SUBSYSTEM:WINDOWS /ENTRY:mainCRTStartup)</span><br><span class="line"><span class="keyword">endif</span>()</span><br><span class="line"><span class="comment"># 或者使用set_target_properties(exe_name PROPERTIES </span></span><br><span class="line"> LINK_FLAGS <span class="string">"/ENTRY:mainCRTStartup /SUBSYSTEM:WINDOWS"</span>)</span><br></pre></td></tr></table></figure><p>或者在源文件中添加如下<a href="https://stackoverflow.com/questions/11785157/replacing-winmain-with-main-function-in-win32-programs/11785733#11785733">c++ - Replacing WinMain() with main() function in Win32 programs - Stack Overflow</a></p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">ifdef</span> _MSC_VER</span></span><br><span class="line"><span class="meta"># <span class="meta-keyword">pragma</span> comment(linker, <span class="meta-string">"/subsystem:windows /ENTRY:mainCRTStartup"</span>)</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">endif</span></span></span><br></pre></td></tr></table></figure><p>一个问题是使用了vs作为generator,目前无法生成clangd的compile_commands.json了<a href="https://cmake.org/cmake/help/latest/variable/CMAKE_EXPORT_COMPILE_COMMANDS.html">CMAKE_EXPORT_COMPILE_COMMANDS — CMake 3.30.4 Documentation</a>,那就不使用cland使用微软的c++工具用于代码搜索、跳转.</p><p><a href="https://zhuanlan.zhihu.com/p/635410959">C++轻量级跨平台桌面GUI库FLTK的简单使用 - 知乎 (zhihu.com)</a>介绍了一些现有的跨平台桌面库</p><link rel="stylesheet" href="/css/spoiler.css" type="text/css"><script src="/js/spoiler.js" type="text/javascript" async></script>]]></content>
<summary type="html"><p>使用OpenGL或者Vulkan的图形库时经常使用一些窗口工具库搭配,常见的就是<a href="https://www.glfw.org/docs/latest/index.html">GLFW: Introduction</a>了,类似的有SDL,SFML和win32库,这里也会简单说一下. 这篇文章相当于GLFW的api介绍</p></summary>
<category term="window library" scheme="https://www.sekyoro.top/tags/window-library/"/>
</entry>
<entry>
<title>链接动态库在不同操作系统上的行为</title>
<link href="https://www.sekyoro.top/2024/10/04/%E9%93%BE%E6%8E%A5%E5%8A%A8%E6%80%81%E5%BA%93%E5%9C%A8%E4%B8%8D%E5%90%8C%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F%E4%B8%8A%E7%9A%84%E8%A1%8C%E4%B8%BA/"/>
<id>https://www.sekyoro.top/2024/10/04/%E9%93%BE%E6%8E%A5%E5%8A%A8%E6%80%81%E5%BA%93%E5%9C%A8%E4%B8%8D%E5%90%8C%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F%E4%B8%8A%E7%9A%84%E8%A1%8C%E4%B8%BA/</id>
<published>2024-10-04T03:23:29.000Z</published>
<updated>2024-10-04T09:22:57.518Z</updated>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="\assets\js\Meting.min.js"></script><p>想必很多人已经了解了动态库与静态库,在实际开发中也经常使用. 但是,有必要了解在windows和Linux上开发c++程序生成和链接动态库的不同行为,因为经常混淆或者自以为找到了动态库,这里简单学习并澄清一下.其中许多内容来自官方文档<br><span id="more"></span></p><p>在linux上静态库常常以.a结尾,动态库以.so结尾,而windows上分别以.lib与.dll结尾. 于是很多人就把.dll等同于.so使用了,但其实并不一样. </p><h2 id="编译与链接静态库"><a href="#编译与链接静态库" class="headerlink" title="编译与链接静态库"></a>编译与链接静态库</h2><p><strong>生成动态库</strong></p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">gcc -shared -fPIC -o libfoo.so foo.c</span><br></pre></td></tr></table></figure><p><strong>生成静态库</strong></p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">gcc -o libfoo.o foo.c -I include </span><br><span class="line">ar cr libfoo.a libfoo.o</span><br></pre></td></tr></table></figure><div class="table-container"><table><thead><tr><th>选项</th><th>作用</th></tr></thead><tbody><tr><td><strong>-ggdb</strong></td><td>此选项将尽可能的生成 gdb 的可以使用的调试信息.</td></tr><tr><td>-l [lib]</td><td>(这里是小写的L,命令无中括号,下同)指定程序要链接的库,[lib]为库文件名称.如果gcc编译选项中加入了“-static”表示寻找静态库文件</td></tr><tr><td>-L [dir]</td><td>指定-l(小写-L)所使用到的库文件所在路径(链接时而非动态查找),不然编译器将只在标准库的目录找</td></tr><tr><td>-I [dir]</td><td>(这里是大写的I)增加 include 头文件路径</td></tr><tr><td>-static</td><td>链接静态库生成目标文件,禁止使用动态库(在支持动态链接的系统上) 所以编译出来的东西一般都很大,也不需要什么动态连接库就可以运行.</td></tr><tr><td>-shared</td><td>生成共享文件,可以与其它文件链接生成可执行文件</td></tr><tr><td>-fpic</td><td>生成适用于共享库的<strong>与地址无关的代码</strong>(PIC)(如果机器支持的话)</td></tr><tr><td>-fPIC</td><td>生成<strong>与位置无关的的代码</strong>,适用于使用动态库,与“-fpic”的区别在于去除去全局偏移表的任何限制(如果机器支持的话)</td></tr></tbody></table></div><p>链接动态库和链接静态库差别不大,但是需要设置一些路径方便linux查找.</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">gcc -o main main.c -I/home/alice/foo -lfoo</span><br></pre></td></tr></table></figure><blockquote><p>如果静态库和动态库在同一目录并且前缀相同,e.g. libxx.so和libxx.a,使用<code>g++ -o main main.cpp -L build/lib -l xx -I lib/include</code>会默认链接动态库,添加<code>-static</code>可解决(不过一般也不会同时把动态库和静态库同名放一个目录吧😅)</p></blockquote><p>gcc <code>-L</code> <code>-l</code>含义是什么,不管是动态库还是链接库,如果使用了库,都需要使用<code>-L</code>和<code>-l</code>进行编译时链接,如果是静态库,<code>-l</code>往往就够了,但如果是动态库,<code>-l</code>作用是在编译时让编译器直到用到了库中的某些东西存在,但是运行时还需要另外设置,如果链接动态库不使用<code>-l</code>也会报错</p><p><img data-src="https://s2.loli.net/2024/10/04/w37nS5PECGmVyjc.png" alt="image-20241004132437629"></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">g++ -o main main.cpp ./build/lib/libdy_lib.so -I lib/include</span><br></pre></td></tr></table></figure><p>或者直接使用<code>.so</code>与<code>.cpp</code>编译链接,注意这在windows上行不通,根本原因是动态库的路径搜索方式不同</p><p>文件一多,项目一大肯定需要使用构建系统的,包括make,Ninja,MSBuilg等等,而cmake就是生成这些构建系统的,当使用cmake时就没有太大必要考虑编译器细节了.</p><figure class="highlight cmake"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 生成</span></span><br><span class="line"><span class="keyword">add_library</span>(dy_lib STATIC <span class="keyword">test</span>.cpp)</span><br><span class="line"><span class="comment"># add_library(dy_lib SHARED test.cpp)</span></span><br><span class="line"><span class="comment"># 链接</span></span><br><span class="line"><span class="keyword">target_link_libraries</span>(cpp_test PRIVATE dy_lib)</span><br></pre></td></tr></table></figure><h3 id="链接动态库在cmake中的行为"><a href="#链接动态库在cmake中的行为" class="headerlink" title="链接动态库在cmake中的行为"></a>链接动态库在cmake中的行为</h3><p>RPATH在开发过程中很有用,因为可以将构建树中的库链接到可执行文件中.CMake提供了相当多的选项来优化构建树链接和安装链接期间的行为</p><p>我们知道要使用动态库,光是<code>-l</code>是不行的,在windows上需要看链接方式(下面详细介绍),在linux上也要设置动态库搜索路径. 使用cmake时链接动态库,cmake会默认设置buil_rpath,但安装时使用install_rpath<a href="https://cmake.org/cmake/help/latest/prop_tgt/BUILD_RPATH.html">BUILD_RPATH — CMake 3.30.4 Documentation</a></p><p><strong>BUILD_PATH</strong></p><p>一个分号分隔的列表,指定要添加到构建树中链接的二进制文件中的运行时路径(RPATH)条目(对于支持它的平台).默认情况下,CMake在构建树中设置二进制文件的运行时路径,以包含它知道需要查找它们链接的共享库的搜索路径.项目可以设置BUILD_RPATH来指定额外的搜索路径.</p><ul><li>The <a href="https://cmake.org/cmake/help/latest/variable/CMAKE_SKIP_RPATH.html#variable:CMAKE_SKIP_RPATH"><code>CMAKE_SKIP_RPATH</code></a> variable completely disables runtime paths in both the build tree and install tree.</li><li>The <a href="https://cmake.org/cmake/help/latest/prop_tgt/SKIP_BUILD_RPATH.html#prop_tgt:SKIP_BUILD_RPATH"><code>SKIP_BUILD_RPATH</code></a> target property disables setting any runtime path in the build tree.</li><li>The <a href="https://cmake.org/cmake/help/latest/prop_tgt/BUILD_RPATH_USE_ORIGIN.html#prop_tgt:BUILD_RPATH_USE_ORIGIN"><code>BUILD_RPATH_USE_ORIGIN</code></a> target property causes the automatically-generated runtime path to use entries relative to <code>$ORIGIN</code>.</li><li>The <a href="https://cmake.org/cmake/help/latest/prop_tgt/BUILD_WITH_INSTALL_RPATH.html#prop_tgt:BUILD_WITH_INSTALL_RPATH"><code>BUILD_WITH_INSTALL_RPATH</code></a> target property causes binaries in the build tree to be built with the install-tree runtime path.</li></ul><p>下面是默认设置.默认情况下,如果不更改任何 RPATH 相关设置,CMake 将以完整的 RPATH 连接可执行文件和共享库,并将其连接到联编树中所有使用过的库.安装时,它会清除这些目标的 RPATH,因此它们在安装时的 RPATH 为空</p><figure class="highlight cmake"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># use, i.e. don't skip the full RPATH for the build tree</span></span><br><span class="line"><span class="keyword">set</span>(CMAKE_SKIP_BUILD_RPATH <span class="keyword">FALSE</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># when building, don't use the install RPATH already</span></span><br><span class="line"><span class="comment"># (but later on when installing)</span></span><br><span class="line"><span class="keyword">set</span>(CMAKE_BUILD_WITH_INSTALL_RPATH <span class="keyword">FALSE</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># the RPATH to be used when installing</span></span><br><span class="line"><span class="keyword">set</span>(CMAKE_INSTALL_RPATH <span class="string">""</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># don't add the automatically determined parts of the RPATH</span></span><br><span class="line"><span class="comment"># which point to directories outside the build tree to the install RPATH</span></span><br><span class="line"><span class="keyword">set</span>(CMAKE_INSTALL_RPATH_USE_LINK_PATH <span class="keyword">FALSE</span>)</span><br></pre></td></tr></table></figure><p>也就是说cmake在build时默认添加在build目录下使用的动态库路径,在安装库时rpath默认为空</p><h2 id="Linux"><a href="#Linux" class="headerlink" title="Linux"></a>Linux</h2><p>在Linux系统中,动态链接器(如ld-linux.so)负责在应用程序启动时解析其依赖的共享库.动态链接器根据一定的搜索顺序来查找这些共享库,这个顺序通常包括:</p><ol><li><strong>RPATH</strong>:如果可执行文件中指定了RPATH,动态链接器会首先在这个路径下搜索共享库.</li><li><strong>LD_LIBRARY_PATH</strong>:如果未找到所需的库,动态链接器会继续在由环境变量LD_LIBRARY_PATH指定的目录中搜索.</li><li>配置文件/etc/ld.so.conf</li><li><strong>系统默认路径</strong>:如果仍未找到,动态链接器会在系统默认的库路径(如<code>/lib</code>和<code>/usr/lib</code>)中搜索.</li></ol><p>理解这个搜索机制有助于我们更好地掌握如何通过调整RPATH来控制应用程序的动态链接行为</p><figure class="highlight livecodeserver"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">Unless loading object has RUNPATH:</span><br><span class="line"> RPATH <span class="keyword">of</span> <span class="keyword">the</span> loading object,</span><br><span class="line"> <span class="keyword">then</span> <span class="keyword">the</span> RPATH <span class="keyword">of</span> its loader (unless <span class="keyword">it</span> has <span class="keyword">a</span> RUNPATH), ...,</span><br><span class="line"> <span class="keyword">until</span> <span class="keyword">the</span> <span class="function"><span class="keyword">end</span> <span class="title">of</span> <span class="title">the</span> <span class="title">chain</span>, <span class="title">which</span> <span class="title">is</span> <span class="title">either</span> <span class="title">the</span> <span class="title">executable</span></span></span><br><span class="line"> <span class="keyword">or</span> <span class="keyword">an</span> object loaded <span class="keyword">by</span> dlopen</span><br><span class="line"> Unless executable has RUNPATH:</span><br><span class="line"> RPATH <span class="keyword">of</span> <span class="keyword">the</span> executable</span><br><span class="line">LD_LIBRARY_PATH</span><br><span class="line">RUNPATH <span class="keyword">of</span> <span class="keyword">the</span> loading object</span><br><span class="line">ld.so.cache</span><br><span class="line">default dirs</span><br></pre></td></tr></table></figure><h3 id="RPATH"><a href="#RPATH" class="headerlink" title="RPATH"></a>RPATH</h3><p>rpath优先级最高,会优先让执行档去寻找相应的动态库(如果设置了RUNPATH就会忽略RPATH<a href="https://stackoverflow.com/questions/7967848/use-rpath-but-not-runpath">c - use RPATH but not RUNPATH? - Stack Overflow</a>,简单来说,如果设置了RUN_PATH,那么RPATH会被忽略,但是RUNPATH优先级又低于<code>LD_LIBRAY_PATH</code></p><p>作者给的建议是<code>当您发布二进制文件时,要么使用RPATH而不是RUNPATH,要么确保在运行它们之前设置了LD_LIBRARY_PATH</code>,当然也有推荐只使用<code>LIBRARY_PATH</code>的.</p><p>注意,runpath和rpath也许操作系统支持有关,新版本的os<strong>应该</strong>默认使用runpath了,也就是使用<code>gcc -rpath</code>时默认设置<code>runpath</code></p><p>设置rpath,告诉新系统使用老行为</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">gcc ... -Wl --disable-new-dtags -rpath=<span class="string">""</span></span><br></pre></td></tr></table></figure><p>设置runpath,告诉旧系统使用新行为</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">gcc ... -Wl --enable-new-dtags -rpath=<span class="string">""</span></span><br></pre></td></tr></table></figure><p>查看一个elf文件的PATH,可以看到目前默认是runpath,rpath是depreacated了,这两者最大差异就是优先级</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">readelf --dynamic obj | grep PATH</span><br></pre></td></tr></table></figure><p><img data-src="https://s2.loli.net/2024/10/04/wKgcNut2XI1T6VD.png" alt="image-20241004140806996"></p><p>使用cmake开发时,默认就是这样使用动态库的</p><h3 id="LIBRAY-PATH"><a href="#LIBRAY-PATH" class="headerlink" title="LIBRAY_PATH"></a>LIBRAY_PATH</h3><p>LIBRAY_PATH不是运行时搜索动态库,其效果类似于<code>gcc -L</code>,设置编译时查找路径</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">export</span> LIBRARY_PATH=/home/foo</span><br><span class="line">gcc -o main main.c -I/home/foo -lfoo</span><br><span class="line">ls</span><br><span class="line">main main.c</span><br></pre></td></tr></table></figure><p>推荐使用<code>gcc -L</code>即可</p><blockquote><p>事实上实践中直接使用cmake</p></blockquote><figure class="highlight cmake"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">link_directories</span>()</span><br><span class="line"><span class="keyword">target_link_libraries</span>()</span><br></pre></td></tr></table></figure><h3 id="LD-LIBRAY-PATH"><a href="#LD-LIBRAY-PATH" class="headerlink" title="LD_LIBRAY_PATH"></a>LD_LIBRAY_PATH</h3><p>你会发现在链接动态库后执行程序也无法成功,因为linux搜索动态库的路径并没有包括动态库的路径,道理同<code>rpath</code></p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">export</span> LD_LIBRARY_PATH=<span class="variable">$LD_LIBRARY_PATH</span>:/home/work</span><br><span class="line">./main</span><br></pre></td></tr></table></figure><h3 id="etc-ld-so-conf"><a href="#etc-ld-so-conf" class="headerlink" title="/etc/ld.so.conf"></a>/etc/ld.so.conf</h3><blockquote><p>将非标准路经加入 /etc/ld.so.conf,然后运行 ldconfig 生成 /etc/ld.so.cache. ld.so 加载共享库的时候,会从 ld.so.cache 查找</p></blockquote><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">vim /etc/ld.so.conf</span><br><span class="line"><span class="comment"># 在文件中添加库路径 e.g. /project/build/libdy_lib.so</span></span><br><span class="line">sudo ldconfig</span><br></pre></td></tr></table></figure><p>原理是ldconfig这个程序,程序运行时会通过这个程序查找库.</p><h3 id="默认搜索路径"><a href="#默认搜索路径" class="headerlink" title="默认搜索路径"></a>默认搜索路径</h3><p>可执行程序动态库默认搜索路径包括/usr/lib和/lib</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">cp libdy_lib.so /lib</span><br><span class="line"><span class="comment"># 以下命令均可</span></span><br><span class="line">cp libdy_lib.so /usr/lib</span><br><span class="line">ln -s libdy_lib.so /usr/lib</span><br></pre></td></tr></table></figure><h2 id="Windows"><a href="#Windows" class="headerlink" title="Windows"></a>Windows</h2><p>windows并没有类似linux的rpath机制<a href="https://stackoverflow.com/questions/107888/is-there-a-windows-msvc-equivalent-to-the-rpath-linker-flag">dll - Is there a Windows/MSVC equivalent to the -rpath linker flag? - Stack Overflow</a></p><blockquote><p>当你使用visual studio开发使用了动态库时,也许你在vs上执行并没有问题,但直接点击可执行程序执行就报错了(即使你给可执行程序添加了相关引用). 主要原因是vs在编译链接时会去引用生成的目录找相关.dll和.lib(即使是动态库,vs也会生成DLL导入库,这类似一个查找表,方便获取DLL中的函数、变量等)</p><p>而在运行时,动态库的查找机制就不一样了</p><p><img data-src="https://s2.loli.net/2024/10/04/kYxoIivzWmrOuGd.png" alt="链接时加载了.dll库,成功执行"></p></blockquote><h3 id="链接方法"><a href="#链接方法" class="headerlink" title="链接方法"></a>链接方法</h3><p>可执行文件通过以下两种方式之一链接到(或加载)DLL:</p><ul><li>隐式链接,其中操作系统会与使用 DLL 的可执行文件同时加载它. 客户端<strong>可执行文件调用 DLL 的导出函数的方式与函数进行静态链接并包含在可执行文件中时的方式相同</strong>. 隐式链接有时称为静态加载或加载时动态链接.</li><li>显式链接,其中操作系统会在运行时按需加载 DLL. 通过显式链接使用 DLL 的可执行文件必须显式加载和卸载 DLL. 它还必须设置函数指针,用于访问它从 DLL 使用的每个函数. 与静态链接的库或隐式链接 DLL 中的函数调用不同,客户端可执行文件必须通过函数指针调用显式链接 DLL 中的导出函数. 显式链接有时称为动态加载或运行时动态链接.</li></ul><h4 id="隐式链接"><a href="#隐式链接" class="headerlink" title="隐式链接"></a>隐式链接</h4><p>当应用程序的代码调用导出 DLL 函数时,会进行隐式链接. 当编译或汇编调用可执行文件的源代码时,DLL 函数调用会在对象代码中生成外部函数引用.</p><p> <strong>若要解析此外部引用,应用程序必须与 DLL 创建者提供的导入库(.lib 文件)链接</strong>.</p><p>导入库包含的代码仅用于加载 DLL 和实现对 DLL 中函数的调用. 在导入库中查找外部函数会告知链接器该函数的代码处于 DLL 中. 若要解析对 DLL 的外部引用,链接器只需将信息添加到可执行文件,告知系统在进程启动时查找 DLL 代码的位置.</p><p>当系统启动包含动态链接引用的程序时,它将使用该程序可执行文件中的信息查找所需 DLL. 如果找不到 DLL,则系统将终止进程,并显示报告错误的对话框. 否则,系统会将 DLL 模块映射到进程地址空间中.</p><p>所以我们需要一个.lib文件方便静态加载,也就是程序在编译时就知道了动态库的位置(通过.lib),这样方便查找,而不是像上面提到的linux再通过rpath等路径去看. 那这样做需要什么呢? 那就是经典的<code>__declspec(dllexport)</code>了<a href="https://learn.microsoft.com/zh-cn/cpp/cpp/dllexport-dllimport?view=msvc-170">dllexport、dllimport | Microsoft Learn</a></p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">define</span> DllImport __declspec( dllimport )</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> DllExport __declspec( dllexport )</span></span><br><span class="line"></span><br><span class="line"><span class="function">DllExport <span class="keyword">void</span> <span class="title">func</span><span class="params">()</span></span>;</span><br><span class="line">DllExport <span class="keyword">int</span> i = <span class="number">10</span>;</span><br><span class="line">DllImport <span class="keyword">int</span> j;</span><br><span class="line">DllExport <span class="keyword">int</span> n;</span><br></pre></td></tr></table></figure><p>使用 <strong><code>dllexport</code></strong> 意味着定义,而使用 <strong><code>dllimport</code></strong> 则意味着声明. 必须使用带 <strong><code>extern</code></strong> 的 <strong><code>dllexport</code></strong> 关键字来强制进行声明;否则,会进行隐式定义.</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">static</span> __declspec( dllimport ) <span class="keyword">int</span> l; <span class="comment">// Error; not declared extern.</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">func</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">static</span> __declspec( dllimport ) <span class="keyword">int</span> s; <span class="comment">// Error; not declared</span></span><br><span class="line"> <span class="comment">// extern.</span></span><br><span class="line"> __declspec( dllimport ) <span class="keyword">int</span> m; <span class="comment">// Okay; this is a</span></span><br><span class="line"> <span class="comment">// declaration.</span></span><br><span class="line"> __declspec( dllexport ) <span class="keyword">int</span> n; <span class="comment">// Error; implies external</span></span><br><span class="line"> <span class="comment">// definition in local scope.</span></span><br><span class="line"> <span class="keyword">extern</span> __declspec( dllimport ) <span class="keyword">int</span> i; <span class="comment">// Okay; this is a</span></span><br><span class="line"> <span class="comment">// declaration.</span></span><br><span class="line"> <span class="keyword">extern</span> __declspec( dllexport ) <span class="keyword">int</span> k; <span class="comment">// Okay; extern implies</span></span><br><span class="line"> <span class="comment">// declaration.</span></span><br><span class="line"> __declspec( dllexport ) <span class="keyword">int</span> x = <span class="number">5</span>; <span class="comment">// Error; implies external</span></span><br><span class="line"> <span class="comment">// definition in local scope.</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>当声明 <strong><code>dllexport</code></strong> 类时,它的所有成员函数和静态数据成员都会导出. 必须在同一程序中提供所有此类成员的定义. 否则,将生成链接器错误. 此规则有一个例外情况,即对于纯虚函数,无需为其提供显式定义. 但是,由于基类的析构函数始终在调用继承类的析构函数,因此纯虚析构函数必须始终提供定义</p><p>当声明 <strong><code>dllimport</code></strong> 类时,它的所有成员函数和静态数据成员都会导入. 与非类类型上的 <strong><code>dllimport</code></strong> 和 <strong><code>dllexport</code></strong> 的行为不同,静态数据成员无法在定义 <strong><code>dllimport</code></strong> 类的同一程序中指定定义. 如果整个类都已导入或导出,则禁止将成员函数和数据显式声明为 <strong><code>dllimport</code></strong> 或 <strong><code>dllexport</code></strong></p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">define</span> DllExport __declspec( dllexport )</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">DllExport</span> <span class="title">C</span> {</span></span><br><span class="line"> <span class="keyword">int</span> i;</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="keyword">int</span> <span class="title">func</span><span class="params">( <span class="keyword">void</span> )</span> </span>{ <span class="keyword">return</span> <span class="number">1</span>; }</span><br><span class="line">};</span><br></pre></td></tr></table></figure><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// lib_link_input_2.cpp</span></span><br><span class="line"><span class="comment">// compile by using: cl /EHsc lib_link_input_1.lib lib_link_input_2.cpp</span></span><br><span class="line">__declspec(dllimport) <span class="function"><span class="keyword">int</span> <span class="title">Test</span><span class="params">()</span></span>;</span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><iostream></span></span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span> </span>{</span><br><span class="line"> std::cout << <span class="built_in">Test</span>() << std::endl;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>在windows上,这几乎成了常用的方式,不管你使用的什么编译器,即便是mingw,clang</p><p>如果使用vc++,那会生成xx.dll与libxx.lib,后者用于找动态库,而使用mingw,clang会生成xx.dll和xx.dll.a,效果一样.</p><p>若要通过隐式链接使用 DLL,客户端可执行文件必须从 DLL 的提供程序获取以下文件:</p><ul><li>一个或多个头文件(.h 文件),其中包含 DLL 中的导出数据、函数和 C++ 类的声明. DLL 导出的类、函数和数据全都必须在头文件中标记为 <code>__declspec(dllimport)</code></li><li>要链接到可执行文件中的导入库. 生成 DLL 时,链接器会创建导入库</li><li>实际 DLL 文件.</li></ul><p>我们在windows上默认都是使用的隐式链接,如果你要使用动态库,还挺麻烦的.</p><h4 id="显式链接"><a href="#显式链接" class="headerlink" title="显式链接"></a>显式链接</h4><p>有时需要显式链接. 下面是使用显式链接的一些常见原因:</p><ul><li>应用程序直到运行时才知道它所加载的 DLL 的名称. 例如,应用程序可能会在启动时从配置文件获取 DLL 的名称和导出函数.</li><li>如果在使用隐式链接的进程启动时找不到 DLL,则操作系统会终止进程. 使用显式链接的进程在这种情况下不会终止,可以尝试从错误中恢复. 例如,进程可以向用户通知错误,并让用户指定 DLL 的其他路径.</li><li>如果使用隐式链接的进程所链接到的任何 DLL 的 <code>DllMain</code> 函数失败,则进程也会终止. 使用显式链接的进程在这种情况下不会终止.</li><li>隐式链接到许多 DLL 的应用程序可能会速度较慢,因为 Windows 会在应用程序加载时加载所有 DLL. 若要提高启动性能,应用程序可以只对在加载之后立即需要的 DLL 使用隐式链接. 它可以仅在需要时才使用显式链接加载其他 DLL.</li><li>显式链接无需使用导入库链接应用程序. 如果 DLL 中的更改导致导出序号发生更改,则在使用函数名称而不是序号值调用 <code>GetProcAddress</code> 时,应用程序无需重新链接. 使用隐式链接的应用程序仍必须重新链接到更改的导入库.</li></ul><p>若要通过显式链接使用 DLL,应用程序必须在运行时进行函数调用以显式加载 DLL. 若要显式链接到 DLL,应用程序必须:</p><ul><li>调用LoadLibraryEx或类似函数以加载 DLL 并获取模块句柄.</li><li>调用 GetProcAddress以获取应用程序调用的每个导出函数的函数指针. 由于应用程序通过指针调用 DLL 函数,因此编译器不生成外部引用,从而不需要与导入库链接. 不过必须有 <strong><code>typedef</code></strong> 或 <strong><code>using</code></strong> 语句,此语句定义调用的已导出函数的调用签名.</li><li>处理完 DLL 时,调用 FreeLibrary</li></ul><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"windows.h"</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">typedef</span> <span class="title">HRESULT</span> <span class="params">(CALLBACK* LPFNDLLFUNC1)</span><span class="params">(DWORD,UINT*)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="function">HRESULT <span class="title">LoadAndCallSomeFunction</span><span class="params">(DWORD dwParam1, UINT * puParam2)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> HINSTANCE hDLL; <span class="comment">// Handle to DLL</span></span><br><span class="line"> LPFNDLLFUNC1 lpfnDllFunc1; <span class="comment">// Function pointer</span></span><br><span class="line"> HRESULT hrReturnVal;</span><br><span class="line"></span><br><span class="line"> hDLL = <span class="built_in">LoadLibrary</span>(<span class="string">"MyDLL"</span>);</span><br><span class="line"> <span class="keyword">if</span> (<span class="literal">NULL</span> != hDLL)</span><br><span class="line"> {</span><br><span class="line"> lpfnDllFunc1 = (LPFNDLLFUNC1)<span class="built_in">GetProcAddress</span>(hDLL, <span class="string">"DLLFunc1"</span>);</span><br><span class="line"> <span class="keyword">if</span> (<span class="literal">NULL</span> != lpfnDllFunc1)</span><br><span class="line"> {</span><br><span class="line"> <span class="comment">// call the function</span></span><br><span class="line"> hrReturnVal = <span class="built_in">lpfnDllFunc1</span>(dwParam1, puParam2);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span></span><br><span class="line"> {</span><br><span class="line"> <span class="comment">// report the error</span></span><br><span class="line"> hrReturnVal = ERROR_DELAY_LOAD_FAILED;</span><br><span class="line"> }</span><br><span class="line"> <span class="built_in">FreeLibrary</span>(hDLL);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span></span><br><span class="line"> {</span><br><span class="line"> hrReturnVal = ERROR_DELAY_LOAD_FAILED;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> hrReturnVal;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="动态库查找路径"><a href="#动态库查找路径" class="headerlink" title="动态库查找路径"></a>动态库查找路径</h3><blockquote><p>查找路径不仅针对显式链接,隐式链接也能用. 比较方便的就是可执行程序文件、或环境变量PATH</p></blockquote><p>当应用程序调用 LoadLibrary或 LoadLibraryEx函数时,系统会尝试查找 DLL . 如果搜索成功,系统会将 DLL 模块映射到进程的虚拟地址空间,并递增引用计数.</p><p><a href="https://learn.microsoft.com/zh-cn/windows/win32/dlls/dynamic-link-library-search-order">Dynamic-link library search order - Win32 apps | Microsoft Learn</a></p><p>windows搜索dll路径顺序比较麻烦,需要看是否是打包应用(loadPackagedLibrary ),是否开启了安全DLL搜索模式(默认开启)</p><blockquote><p>若要禁用安全 DLL 搜索模式,将<code>HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\SafeDllSearchMode</code> 创建注册表值并将其设置为 0</p></blockquote><p>如果是未打包并且是安全搜索模式,那么搜索顺序如下,前六个感觉不用看</p><ol><li>DLL 重定向.</li><li>API sets.</li><li>SxS manifest redirection.</li><li>Loaded-module list.</li><li>Known DLLs.</li><li><strong>Windows 11,版本 21H2 (10.0;内部版本 22000) 及更高版本</strong>. The package dependency graph of the process. This is the application’s package plus any dependencies specified as <code><PackageDependency></code> in the <code><Dependencies></code> section of the application’s package manifest. Dependencies are searched in the order they appear in the manifest.</li><li>从中加载应用程序的文件夹.</li><li>系统文件夹. 使用GetSystemDirectory函数检索此文件夹的路径.</li><li>16 位系统文件夹. 没有获取此文件夹路径的函数,但会对其进行搜索.</li><li>Windows 文件夹. 使用GetWindowsDirectory函数获取此文件夹的路径.</li><li>当前文件夹.</li><li>环境变量中列出的 <code>PATH</code> 目录. 这不包括由应用路径注册表项指定的<strong>App Paths</strong> . 计算 DLL 搜索路径时,不使用 <strong>App Paths</strong> 变量</li></ol><p>如果禁用安全DLL 搜索模式,则搜索顺序基本相同,只是位置11和8交换顺序</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>总结一下,在linux使用cmake开发c/c++程序链接动态库时使用rpath添加搜索目录,使用windows开发开发动态库实在麻烦,一般默认隐式链接然后使用<code>__declspec( dllexport)</code>导出(因为默认不导出),如果使用现成的xx.dll和libxx.lib就不需要声明<code>__declspec(__dllimport)</code>宏了,因为链接了DLL导入库(也就是libxx.lib) <a href="https://www.youtube.com/watch?v=pLy69V2F_8M&t=481s&ab_channel=TheCherno">Using Dynamic Libraries in C++ (youtube.com)</a></p><p><img data-src="https://learn.microsoft.com/zh-cn/cpp/build/media/mathclient-additional-dependencies-property.png?view=msvc-170" alt="Screenshot of the Property Pages dialog showing the Edit command in the Linker > Input > Additional Dependencies property drop-down."></p><p>至于生成的DLL放哪,连微软自己都说放在可执行文件同一目录中,在vs<code>可将“后期生成事件”添加到项目中,以此添加一条命令,将 DLL 复制到生成输出目录.</code></p><figure class="highlight awk"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">xcopy <span class="regexp">/y /</span>d <span class="string">"..\..\MathLibrary\$(IntDir)MathLibrary.dll"</span> <span class="string">"$(OutDir)"</span></span><br></pre></td></tr></table></figure><p><img data-src="https://learn.microsoft.com/zh-cn/cpp/build/media/mathclient-post-build-command-line.png?view=msvc-170" alt="Screenshot of the Property Pages dialog showing the post build event command line property."></p><p>我的配置如下</p><p><img data-src="https://s2.loli.net/2024/10/04/yH5w6uk8spCKgnW.png" alt="image-20241004164950315"></p><figure class="highlight nsis"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">xcopy /y /d <span class="string">"<span class="variable">$(OutDir)</span><span class="variable">$(TargetFileName)</span>"</span> <span class="string">"<span class="variable">$(SolutionDir)</span>bin\<span class="variable">$(Platform)</span>\<span class="variable">$(Configuration)</span>\"</span></span><br></pre></td></tr></table></figure><p>在cmake中添加自定义command,道理相同,使用了<code>cmake -E</code></p><figure class="highlight cmake"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">add_custom_command</span>(<span class="keyword">TARGET</span> MyTest POST_BUILD </span><br><span class="line"> <span class="keyword">COMMAND</span> <span class="variable">${CMAKE_COMMAND}</span> -E copy_if_different </span><br><span class="line"> <span class="string">"${PROJECT_SOURCE_DIR}/libs/test.dll"</span> </span><br><span class="line"> $<TARGET_FILE_DIR:MyTest>) </span><br></pre></td></tr></table></figure><p>或者类似的使用更好的生成器表达式<code>$<TARGET_RUNTIME_DLLS:MyTest></code></p><figure class="highlight cmake"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">find_package</span>(foo CONFIG REQUIRED) <span class="comment"># package generated by install(EXPORT)</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">add_executable</span>(exe main.c)</span><br><span class="line"><span class="keyword">target_link_libraries</span>(exe PRIVATE foo::foo foo::bar)</span><br><span class="line"><span class="keyword">add_custom_command</span>(<span class="keyword">TARGET</span> exe POST_BUILD</span><br><span class="line"> <span class="keyword">COMMAND</span> <span class="variable">${CMAKE_COMMAND}</span> -E copy -t $<TARGET_FILE_DIR:exe> $<TARGET_RUNTIME_DLLS:exe></span><br><span class="line"> COMMAND_EXPAND_LISTS</span><br><span class="line">)</span><br></pre></td></tr></table></figure><p><code>cmake -E copy_if_different</code></p><figure class="highlight vim"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">copy</span> <span class="symbol"><file></span>... destination - <span class="keyword">copy</span> <span class="keyword">files</span> <span class="keyword">to</span> destination (either <span class="keyword">file</span> <span class="built_in">or</span> directory)</span><br><span class="line">copy_directory <span class="symbol"><dir></span>... destination - <span class="keyword">copy</span> content of <span class="symbol"><dir></span>... directories <span class="keyword">to</span> <span class="string">'destination'</span> directory</span><br><span class="line">copy_directory_if_different <span class="symbol"><dir></span>... destination - <span class="keyword">copy</span> changed content of <span class="symbol"><dir></span>... directories <span class="keyword">to</span> <span class="string">'destination'</span> directory</span><br><span class="line">copy_if_different <span class="symbol"><file></span>... destination - <span class="keyword">copy</span> <span class="keyword">files</span> <span class="keyword">if</span> it <span class="built_in">has</span> changed</span><br></pre></td></tr></table></figure><h2 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h2><ol><li><a href="https://learn.microsoft.com/zh-cn/cpp/build/linking-an-executable-to-a-dll?view=msvc-170">将可执行文件链接到 DLL | Microsoft Learn</a></li><li><a href="https://learn.microsoft.com/zh-cn/cpp/build/walkthrough-creating-and-using-a-dynamic-link-library-cpp?view=msvc-170">演练:创建和使用自己的动态链接库 (C++) | Microsoft Learn</a></li><li><a href="https://blog.csdn.net/feikudai8460/article/details/121823029">gcc/g++ 动态库和静态库,编译与链接(含示例)_g++ 链接静态库-CSDN博客</a></li><li><a href="https://www.baeldung.com/linux/library_path-vs-ld_library_path">LIBRARY_PATH vs LD_LIBRARY_PATH | Baeldung on Linux</a></li><li><a href="https://developer.aliyun.com/article/1469309">【Linux 应用开发 】Linux环境下动态链接库路径(RPATH)的调整策略-阿里云开发者社区 (aliyun.com)</a></li><li><a href="https://www.cnblogs.com/AndyJee/p/3835092.html">Linux动态库(.so)搜索路径 - AndyJee - 博客园 (cnblogs.com)</a></li><li><a href="https://web.archive.org/web/20120418232524/http://labs.qt.nokia.com/2011/10/28/rpath-and-runpath/">RPATH and RUNPATH (archive.org)</a></li><li><a href="https://blog.tremily.us/posts/rpath/">RPATH, RUNPATH, and dynamic linking (tremily.us)</a></li><li><a href="https://gitlab.kitware.com/cmake/community/-/wikis/doc/cmake/RPATH-handling">RPATH handling · Wiki · CMake / Community · GitLab (kitware.com)</a></li><li><a href="https://stackoverflow.com/questions/10671916/how-to-copy-dll-files-into-the-same-folder-as-the-executable-using-cmake">How to copy DLL files into the same folder as the executable using CMake? - Stack Overflow</a></li><li><a href="https://cmake.org/cmake/help/latest/manual/cmake-generator-expressions.7.html#genex:TARGET_RUNTIME_DLLS">cmake-generator-expressions(7) — CMake 3.30.4 Documentation</a></li></ol><link rel="stylesheet" href="/css/spoiler.css" type="text/css"><script src="/js/spoiler.js" type="text/javascript" async></script>]]></content>
<summary type="html"><p>想必很多人已经了解了动态库与静态库,在实际开发中也经常使用. 但是,有必要了解在windows和Linux上开发c++程序生成和链接动态库的不同行为,因为经常混淆或者自以为找到了动态库,这里简单学习并澄清一下.其中许多内容来自官方文档<br></summary>
</entry>
<entry>
<title>窗口系统与图形绘制接口</title>
<link href="https://www.sekyoro.top/2024/10/02/%E7%AA%97%E5%8F%A3%E7%B3%BB%E7%BB%9F%E4%B8%8E%E5%9B%BE%E5%BD%A2%E7%BB%98%E5%88%B6%E6%8E%A5%E5%8F%A3/"/>
<id>https://www.sekyoro.top/2024/10/02/%E7%AA%97%E5%8F%A3%E7%B3%BB%E7%BB%9F%E4%B8%8E%E5%9B%BE%E5%BD%A2%E7%BB%98%E5%88%B6%E6%8E%A5%E5%8F%A3/</id>
<published>2024-10-02T03:18:22.000Z</published>
<updated>2024-10-15T02:20:08.025Z</updated>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="\assets\js\Meting.min.js"></script><p>当我们想要进行底层图形应用(GUI)开发时,往往需要用到窗口系统和图形库,这里简单介绍一下<br><span id="more"></span></p><h2 id="视窗系统-window-system-与通信协议"><a href="#视窗系统-window-system-与通信协议" class="headerlink" title="视窗系统(window system)与通信协议"></a>视窗系统(window system)与通信协议</h2><p>下面内容主要针对Unix-like操作系统 </p><p><strong>视窗系统</strong>是以使用视窗作为主要特征之一的图形用户接口的构成组件.更为明确地说,它是桌面环境的构成组件.视窗系统支撑著窗口管理器的实现(implementation);视窗系统为“图像硬件(graphics hardware)、指向设备(pointing devices)提供基本支持.绘制鼠标光标,一般也与视窗系统相关.</p><h3 id="X-Window-System-X11"><a href="#X-Window-System-X11" class="headerlink" title="X Window System(X11)"></a>X Window System(X11)</h3><p>X窗口系统是一种以位图方式显示的软件窗口系统,X窗口系统通过软件工具及架构协议来建立操作系统所用的<a href="https://zh.wikipedia.org/wiki/图形用户界面">图形用户界面</a>,此后则逐渐扩展适用到各形各色的其他操作系统上.</p><blockquote><p>在X11的设计中,应用程序和显示器不必在同一台计算机上,这一点并不明显.在开发X时,X server运行在工作站上,而用户在具有更强处理能力的远程计算机上运行应用程序是很常见的.</p></blockquote><h4 id="X-Window核心协议"><a href="#X-Window核心协议" class="headerlink" title="X Window核心协议"></a>X Window核心协议</h4><p><strong>X Window 核心协议</strong>是X窗口系统的基础协议,它是一个以位图显示的网络化视窗系统,用来在Unix、类Unix和其它操作系统上建立用户图形界面.X Window 系统基于主从式模型:单一服务器控管硬件的输出入,如屏幕、键盘和鼠标;所有的应用程序都被视作客户端,<strong>用户之间透过服务器来交互</strong>.</p><p>交互部分由X Window核心协议来管理.还有其它与X窗口系统有关的协议,有的建立在X Window核心协议之上的,有的是独立的协议.</p><p><img data-src="https://upload.wikimedia.org/wikipedia/commons/thumb/0/03/X_client_server_example.svg/220px-X_client_server_example.svg.png" alt="img"></p><p>X server接受来自键盘,鼠标,显示器的输入,并将这些请求发送给client.</p><p><img data-src="https://jichu4n.com/content/images/2018/10/so1jXbe2d2Vvx917pbA5Cjw.png" alt="so1jXbe2d2Vvx917pbA5Cjw"></p><h4 id="X-server"><a href="#X-server" class="headerlink" title="X server"></a>X server</h4><p>与大多数早期的显示协议不同,X是专门设计用于网络连接,而不是用于集成或附加的显示设备.X具有网络透明性,这意味着在网络上某处的计算机(例如Internet)上运行的X程序可以在网络上其他计算机上运行的X服务器上显示其用户界面.</p><p>X服务器通常为X客户机提供图形资源和键盘/鼠标事件,这意味着X服务器通常在人类用户面前的计算机上运行,而X客户机应用程序在网络上的任何地方运行,并与用户的计算机通信,请求图形内容的呈现,并从包括键盘和鼠标在内的输入设备接收事件</p><h4 id="Xlib与其他的客户端程序"><a href="#Xlib与其他的客户端程序" class="headerlink" title="Xlib与其他的客户端程序"></a>Xlib与其他的客户端程序</h4><blockquote><p>大部分的客户端程序借由 Xlib 客户端程序库与服务器交流.特别是客户端大多使用 Xaw、Motif、GTK+、Qt 之类使用到 Xlib 的程序库,方便和服务器交互.</p></blockquote><p>XLib是<strong>X Window System的核心库</strong>,它提供了与窗口系统交互的基本功能,如创建窗口、处理事件和绘制图形</p><blockquote><p><strong>Xlib</strong>是一种X Window System协议的客户端,以C语言撰写.其功能是与X server沟通.这样的功能可以让程序人员撰写程序时,,毋须了解其协议的细节。但甚少应用程序会直接使用Xlib;通常是透过其他的函数库来调用Xlib用以提供部件工具箱</p></blockquote><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><X11/Xlib.h></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><unistd.h></span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> Display* MainDisplay = XOpenDisplay(<span class="number">0</span>);</span><br><span class="line"> Window RootWindow = XDefaultRootWindow(MainDisplay);</span><br><span class="line"> </span><br><span class="line"> Window MainWindow = XCreateSimpleWindow(MainDisplay, RootWindow, <span class="number">0</span>, <span class="number">0</span>, <span class="number">800</span>, <span class="number">600</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0x00aade87</span>);</span><br><span class="line"> XMapWindow(MainDisplay, MainWindow);</span><br><span class="line"> XFlush(MainDisplay);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">for</span>(;;) { sleep(<span class="number">1</span>); }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><X11/Xlib.h></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><X11/Xutil.h></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><stdio.h></span></span></span><br><span class="line"></span><br><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> {</span></span><br><span class="line"> <span class="keyword">int</span> X;</span><br><span class="line"> <span class="keyword">int</span> Y;</span><br><span class="line"> <span class="keyword">int</span> Width;</span><br><span class="line"> <span class="keyword">int</span> Height;</span><br><span class="line">} entity;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> Display* MainDisplay = XOpenDisplay(<span class="number">0</span>);</span><br><span class="line"> Window RootWindow = XDefaultRootWindow(MainDisplay);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">int</span> DefaultScreen = DefaultScreen(MainDisplay);</span><br><span class="line"> GC Context = XDefaultGC(MainDisplay, DefaultScreen);</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">int</span> WindowX = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">int</span> WindowY = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">int</span> WindowWidth = <span class="number">800</span>;</span><br><span class="line"> <span class="keyword">int</span> WindowHeight = <span class="number">600</span>;</span><br><span class="line"> <span class="keyword">int</span> BorderWidth = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">int</span> WindowDepth = CopyFromParent;</span><br><span class="line"> <span class="keyword">int</span> WindowClass = CopyFromParent;</span><br><span class="line"> Visual* WindowVisual = CopyFromParent;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">int</span> AttributeValueMask = CWBackPixel | CWEventMask;</span><br><span class="line"> XSetWindowAttributes WindowAttributes = {};</span><br><span class="line"> WindowAttributes.background_pixel = <span class="number">0xffffccaa</span>;</span><br><span class="line"> WindowAttributes.event_mask = StructureNotifyMask | KeyPressMask | KeyReleaseMask | ExposureMask;</span><br><span class="line"></span><br><span class="line"> Window MainWindow = XCreateWindow(MainDisplay, RootWindow, </span><br><span class="line"> WindowX, WindowY, WindowWidth, WindowHeight,</span><br><span class="line"> BorderWidth, WindowDepth, WindowClass, WindowVisual,</span><br><span class="line"> AttributeValueMask, &WindowAttributes);</span><br><span class="line"></span><br><span class="line"> XMapWindow(MainDisplay, MainWindow);</span><br><span class="line"></span><br><span class="line"> XStoreName(MainDisplay, MainWindow, <span class="string">"Moving rectangle. Use arrow keys to move."</span>);</span><br><span class="line"></span><br><span class="line"> Atom WM_DELETE_WINDOW = XInternAtom(MainDisplay, <span class="string">"WM_DELETE_WINDOW"</span>, False);</span><br><span class="line"> <span class="keyword">if</span>(!XSetWMProtocols(MainDisplay, MainWindow, &WM_DELETE_WINDOW, <span class="number">1</span>)) {</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"Couldn't register WM_DELETE_WINDOW property \n"</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"> entity Box = {};</span><br><span class="line"> Box.Width = <span class="number">50</span>;</span><br><span class="line"> Box.Height = <span class="number">80</span>;</span><br><span class="line"> Box.X = WindowWidth/<span class="number">2</span> - Box.Width/<span class="number">2</span>;</span><br><span class="line"> Box.Y = WindowHeight/<span class="number">2</span> - Box.Height/<span class="number">2</span>;</span><br><span class="line"> <span class="keyword">int</span> StepSize = <span class="number">5</span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">int</span> IsWindowOpen = <span class="number">1</span>;</span><br><span class="line"> <span class="keyword">while</span>(IsWindowOpen) {</span><br><span class="line"> XEvent GeneralEvent = {};</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"> XNextEvent(MainDisplay, &GeneralEvent);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">switch</span>(GeneralEvent.type) {</span><br><span class="line"> <span class="keyword">case</span> KeyPress:</span><br><span class="line"> <span class="keyword">case</span> KeyRelease:</span><br><span class="line"> {</span><br><span class="line"> XKeyPressedEvent *Event = (XKeyPressedEvent *)&GeneralEvent;</span><br><span class="line"> <span class="keyword">if</span>(Event->keycode == XKeysymToKeycode(MainDisplay, XK_Escape))</span><br><span class="line"> {</span><br><span class="line"> IsWindowOpen = <span class="number">0</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span>(Event->keycode == XKeysymToKeycode(MainDisplay, XK_Down))</span><br><span class="line"> {</span><br><span class="line"> Box.Y += StepSize;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span> <span class="keyword">if</span>(Event->keycode == XKeysymToKeycode(MainDisplay, XK_Up))</span><br><span class="line"> {</span><br><span class="line"> Box.Y -= StepSize;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span> <span class="keyword">if</span>(Event->keycode == XKeysymToKeycode(MainDisplay, XK_Right))</span><br><span class="line"> {</span><br><span class="line"> Box.X += StepSize;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span> <span class="keyword">if</span>(Event->keycode == XKeysymToKeycode(MainDisplay, XK_Left))</span><br><span class="line"> {</span><br><span class="line"> Box.X -= StepSize;</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">break</span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">case</span> ClientMessage:</span><br><span class="line"> {</span><br><span class="line"> XClientMessageEvent *Event = (XClientMessageEvent *) &GeneralEvent;</span><br><span class="line"> <span class="keyword">if</span>((Atom)Event->data.l[<span class="number">0</span>] == WM_DELETE_WINDOW) {</span><br><span class="line"> XDestroyWindow(MainDisplay, MainWindow);</span><br><span class="line"> IsWindowOpen = <span class="number">0</span>;</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">break</span>;</span><br><span class="line"></span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> XClearWindow(MainDisplay, MainWindow);</span><br><span class="line"> XFillRectangle(MainDisplay, MainWindow, Context, Box.X, Box.Y, Box.Width, Box.Height);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="窗口管理器"><a href="#窗口管理器" class="headerlink" title="窗口管理器"></a>窗口管理器</h4><blockquote><p><strong>窗口管理器</strong>(Window manager)是在图形用户界面中,控制窗口位置与外观的软件. 许多窗口管理器是为了桌面环境编写,与桌面环境一同发布的,例如被GNOME使用的Mutter.同时也存在不少独立的窗口管理器,如Openbox、Awesome等.</p></blockquote><p>linux的窗口管理器(dwm,i3wm)以及桌面环境(比如Gnome,KDE等)往往不会直接使用xlib编写界面,而是使用其他库调用xlib,比如GTK,Qt以及<a href="https://www.cairographics.org/">cairographics.org</a>等.</p><p>窗口管理器是一个常规的X客户机.它没有任何超级用户权限;它是X服务器允许调用一组特殊api的普通用户进程</p><p>X通过拒绝客户端对这些api的访问(如果另一个客户端当前具有访问权限),确保在任何给定点上运行的窗口管理器不超过一个.第一个尝试访问这些api的客户端总是成功的.</p><p><img data-src="https://jichu4n.com/content/images/2018/10/so1jXbe2d2Vvx917pbA5Cjw.png" alt="so1jXbe2d2Vvx917pbA5Cjw"></p><p>窗口管理器通过两个X机制与它所管理的窗口进行通信:属性和事件。通信是通过X服务器进行的,而不是直接在窗口管理器和其他应用程序之间进行的。</p><h4 id="substructure-redirection"><a href="#substructure-redirection" class="headerlink" title="substructure redirection"></a>substructure redirection</h4><p>在没有窗口管理器的情况下,当一个应用程序想要对一个窗口做一些事情——移动它、调整它的大小、显示/隐藏它等等——它的请求直接由X服务器处理。但是,窗口管理器需要拦截这些请求。例如,窗口管理器可能需要知道一个新的顶层窗口已经创建并显示,以便在其周围绘制窗口装饰(例如最小化/最大化/关闭按钮)。它可能还需要知道现有的顶层窗口已被调整大小,以便重新绘制窗口装饰以重新填充.允许窗口管理器拦截此类请求的机制称为substructure redirection</p><p>假设我们有一个窗口W。如果程序M在W上注册substructure redirection,<strong>则X服务器不会执行修改W的任何直接子窗口的匹配请求。相反,X服务器将此请求重定向到程序M</strong>,程序M可以对请求执行任何操作,包括直接拒绝请求或通过、修改请求。</p><blockquote><p>窗口管理器能为一个root window(是应用的top-level window的父窗口)注册substructure redirection,使得这个顶层窗口的任何直接子窗口(也就是应用的top-level窗口)发送的请求经过x server不经过修改转发到window manager,windows manager可以进行处理</p></blockquote><p><img data-src="https://jichu4n.com/content/images/2018/10/sTifV_OHk9dsZi6cwbLS7qQ.png" alt="sTifV_OHk9dsZi6cwbLS7qQ"></p><h4 id="reparenting"><a href="#reparenting" class="headerlink" title="reparenting"></a>reparenting</h4><p>如果在没有窗口管理器的情况下运行X应用程序,则应用程序的顶层窗口将是根窗口的直接子窗口.但是,在运行窗口管理器时,应用程序的顶层窗口可能被窗口管理器重新定义;它成为由窗口管理器创建的框架窗口的子窗口,并且它本身是根窗口的直接子窗口。窗口管理器可以将其他UI元素添加到这个框架窗口中,并与应用程序的顶层窗口一起使用</p><p>Reparenting允许不同的窗口管理器绘制不同的窗口装饰,从而实现跨窗口的一致外观。然而,也有一些窗口管理器根本不reparent:这些窗口管理器称为非reparent窗口管理器。窗口管理器不希望重命名有两个原因:</p><ol><li><p>如果窗口管理器不在顶层窗口周围绘制窗口装饰,那么它显然不需要重新表示它们。例如:xmonad、dwm。</p></li><li><p>合成窗口管理器并不总是需要reparent窗口</p></li></ol><blockquote><p>简单来说,reparent赋予了窗口管理器对其他应用程序的top-level也就是顶层窗口绘制的能力</p><p>让window manager管理了应用程序的top-level window</p></blockquote><h4 id="compositing"><a href="#compositing" class="headerlink" title="compositing"></a>compositing</h4><p>从窗口管理器的角度来看,top-level窗口是黑盒;它们各自管理自己的后代窗口(UI元素),可能通过GTK+或Qt这样的框架,窗口管理器无权干涉那里。</p><p>但现在,随着图形硬件计算能力增强,window manager能做的更多了</p><p><img data-src="https://jichu4n.com/content/images/2018/10/sawR1epQiKxE5bX3Cplms2A.png" alt="sawR1epQiKxE5bX3Cplms2A"></p><p>绘制应用top-level窗口下的子元素往往如果直接由应用程序管理,那么其子元素的绘制和事件处理均通过应用发送请求到X server(中间通过window manager可能做出一些修改)</p><p>让我们花点时间思考一下如何实现像上面的Shift Switcher这样的接口。当用户触发这个接口时,我们需要:</p><ol><li><p>将每个顶层窗口及其所有后代窗口(UI元素)呈现到off-screen的内存缓冲区中,而不是直接呈现给硬件。</p></li><li><p>根据设计变换(旋转,扭曲等)每个缓冲区。</p></li><li><p>将转换后的buffer与背景和我们需要显示的任何其他浮动UI元素一起合成为最终buffer</p></li><li><p>创建一个覆盖窗口,覆盖整个屏幕,并隐藏所有其他窗口</p></li><li><p>渲染最终的buffer到覆盖的窗口</p></li></ol><p> Composite扩展提供了一种机制,请求X服务器<strong>不要将特定的窗口及其后代直接呈现给硬件,而是呈现给X服务器维护的一个特殊缓冲区,这样做不需要进行常规的裁剪和重叠计算</strong>。然后,发出请求的客户端(也就是composition window manager)可以读取和使用该缓冲区</p><p> 由于合成窗口管理器已经知道所有顶层窗口的大小和位置,因此在使用图形操作(例如OpenGL)合成到覆盖窗口时,只需绘制窗口装饰就很容易了,而无需创建实际的X框架窗口和reparent</p><p>另一方面,窗口管理器可能需要同时支持合成和非合成模式,以兼容较旧或不受支持的图形硬件。在这种情况下,它需要为非合成模式实现修复和框架窗口,因此使用图形操作额外实现绘制窗口装饰变得多余。这就是为什么许多其他合成窗口管理器仍然选择重命名的原因</p><h3 id="XCB"><a href="#XCB" class="headerlink" title="XCB"></a>XCB</h3><p><a href="https://zh.wikipedia.org/wiki/XCB">XCB - 维基百科,自由的百科全书 (wikipedia.org)</a></p><p>XCB是目标在于取代Xlib,XCB 主要目标是:</p><ul><li>减轻函数库的大小与复杂度;</li><li>可直接访问 X Window核心协议.</li></ul><h3 id="Wayland"><a href="#Wayland" class="headerlink" title="Wayland"></a>Wayland</h3><blockquote><p>Wayland 是在同一个主机上的 GUI 通信协议:新、简单、快速,不需要 setuid root 二进制</p></blockquote><p><strong>Wayland</strong>是一个通信协议,规定了显示服务器与其客户机之间的通信方式,而使用这个协议的显示服务器称为Wayland Compositor.它由Kristian Høgsberg于2008年发起,目标是用更简单的现代化视窗系统取代X Window System.Wayland协议的参考实现称为Weston,由Wayland项目组使用C语言开发</p><p>Wayland与X Window System的最大不同在于<strong>它规定由客户机自身负责窗口边框和装饰的绘制(应用程序直接修改显存?),并且客户机能够通过EGL以及一些Wayland特定的EGL扩展组件直接在显示内存中算绘自己的缓冲器</strong>.</p><p>窗口管理器简化成显示管理服务,专门负责算绘那些屏幕上的程序.这比X Window System中的窗口管理器要更简单、高效</p><p>以“鼠标点击按钮引发按钮更新动作”为例来说明一下Wayland和X server的区别</p><p><strong>在X中</strong>:</p><ol><li><p>内核捕获鼠标点击事件并发送给X server.</p></li><li><p>X server会计算该把这一事件发送给哪个窗口(事实上,窗口位置是由Compositor控制的,X server并不能够正确的计算Compositor做过特效变化之后的按钮的正确位置).</p></li><li><p>应用程序对此事件进行处理(将引发按钮更新动作).但是,在此之前它得向X server发送绘制请求.</p></li><li><p>X server接收到这条绘制请求,然后把它发给视频驱动来渲染.X还计算了更新区域,并且这条“垃圾信息”发送给了Compositor.</p></li><li><p>这时,Compositor知道它必须要重新合成屏幕上的一块区域.当然,这还是要向X server发送绘制请求的.</p></li><li><p>开始绘制.但是X server还会去做一些不必要的本职工作(窗口重叠计算、窗口剪裁计算等)</p><p><img data-src="https://upload.wikimedia.org/wikipedia/commons/thumb/0/06/X-architecture.png/220px-X-architecture.png" alt="img"></p></li></ol><p><strong>在Wayland中</strong>:</p><ol><li>内核捕获鼠标点击事件并发送给Wayland Compositor.</li><li>由于是直接发给Wayland Compositor的,所以Wayland Compositor会正确地计算出按钮的位置.同时它会把这一事件发送给按钮所在的应用程序来处理.</li><li>应用程序直接渲染,无需向Wayland Compositor请求.只需在绘制完成之后向Wayland Compositor发送一条信息表明这块区域被更新了.</li><li>Wayland Compositor收到这条信息后,立即重新合成整个桌面</li></ol><p><img data-src="https://upload.wikimedia.org/wikipedia/commons/thumb/c/cd/Wayland-architecture.png/220px-Wayland-architecture.png" alt="img"></p><p>实现 Wayland 显示服务器协议的显示服务器也称为 Wayland 合成器,因为它们也执行合成视窗管理器的任务.</p><ul><li><p>Hyprland – 一个用C++ 编写的基于wlroots 的平铺Wayland 合成器,Hyprland 值得注意的功能包括动态平铺、选项卡式视窗、干净且可读的C++ 代码库以及提供视窗动画、圆角和Dual-Kawase模糊的自定义渲染器.</p></li><li><p>Weston – Wayland 合成器的参考实现,实现客户端装饰.</p></li><li><p>Sway – 平铺 Wayland 合成器和 X11 i3 视窗管理器的直接替代品.</p></li></ul><h2 id="在Windows上"><a href="#在Windows上" class="headerlink" title="在Windows上"></a>在Windows上</h2><h3 id="win32-api"><a href="#win32-api" class="headerlink" title="win32 api"></a>win32 api</h3><p><del>我去,这接口名把我看吐了</del></p><p>win32api提供了用户界面,图形,音视频,设备以及网络等等<a href="https://learn.microsoft.com/zh-cn/windows/win32/api/">Win32 API 的编程参考 - Win32 apps | Microsoft Learn</a></p><h3 id="winui"><a href="#winui" class="headerlink" title="winui"></a>winui</h3><p>WinUI 是适用于 Windows 桌面应用和 UWP 应用的本机用户体验 (UX) 框架</p><p>使用界面使用<code>xaml</code>,处逻辑处理使用c++</p><h2 id="图形绘制接口"><a href="#图形绘制接口" class="headerlink" title="图形绘制接口"></a>图形绘制接口</h2><p>图形绘制与窗口管理(窗口上下文)往往是一起的,比如使用OpenGL往往需要搭配类似GLFW的窗口绘制库.</p><h3 id="OpenGL"><a href="#OpenGL" class="headerlink" title="OpenGL"></a>OpenGL</h3><p><img data-src="https://s2.loli.net/2024/10/02/S3p1byofjQceLEu.png" alt="image-20241002130444685"></p><p><a href="https://learnopengl-cn.github.io/">主页 - LearnOpenGL CN (learnopengl-cn.github.io)</a></p><h3 id="DirectX"><a href="#DirectX" class="headerlink" title="DirectX"></a>DirectX</h3><p><strong>DirectX</strong>(<strong>Direct</strong> e<strong>X</strong>tension,缩写:<strong>DX</strong>)是一系列专为多媒体以及游戏开发的api.旗下包含Direct3D、Direct2D、DirectCompute等等多个不同用途的子部分,因为这一系列<a href="https://zh.wikipedia.org/wiki/API">API</a>皆以Direct字样开头,所以DirectX(只要把X字母替换为任何一个特定API的名字)就成为这一巨大的API系列的统称.目前最新版本为DirectX 12,随附于Windows 10操作系统之上. 之前还有GDI/GDI++(已经过时)<a href="https://learn.microsoft.com/en-us/windows/win32/graphics-and-multimedia?redirectedfrom=MSDN">Graphics and gaming - Win32 apps | Microsoft Learn</a></p><p><a href="https://learn.microsoft.com/zh-cn/windows/win32/direct2d/getting-started-with-direct2d?source=recommendations#step-1-include-direct2d-header">Direct2D 快速入门 - Win32 apps | Microsoft Learn</a></p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line">ID2D1Factory* pD2DFactory = <span class="literal">NULL</span>;</span><br><span class="line">HRESULT hr = D2D1CreateFactory(</span><br><span class="line"> D2D1_FACTORY_TYPE_SINGLE_THREADED,</span><br><span class="line"> &pD2DFactory</span><br><span class="line"> );</span><br><span class="line"> <span class="comment">// Obtain the size of the drawing area.</span></span><br><span class="line">RECT rc;</span><br><span class="line">GetClientRect(hwnd, &rc);</span><br><span class="line"></span><br><span class="line"><span class="comment">// Create a Direct2D render target </span></span><br><span class="line">ID2D1HwndRenderTarget* pRT = <span class="literal">NULL</span>; </span><br><span class="line">HRESULT hr = pD2DFactory->CreateHwndRenderTarget(</span><br><span class="line"> D2D1::RenderTargetProperties(),</span><br><span class="line"> D2D1::HwndRenderTargetProperties(</span><br><span class="line"> hwnd,</span><br><span class="line"> D2D1::SizeU(</span><br><span class="line"> rc.right - rc.left,</span><br><span class="line"> rc.bottom - rc.top)</span><br><span class="line"> ),</span><br><span class="line"> &pRT</span><br><span class="line">); </span><br><span class="line"> ID2D1SolidColorBrush* pBlackBrush = <span class="literal">NULL</span>;</span><br><span class="line"><span class="keyword">if</span> (SUCCEEDED(hr))</span><br><span class="line">{</span><br><span class="line"> </span><br><span class="line"> pRT->CreateSolidColorBrush(</span><br><span class="line"> D2D1::ColorF(D2D1::ColorF::Black),</span><br><span class="line"> &pBlackBrush</span><br><span class="line"> ); </span><br><span class="line">} </span><br><span class="line"> pRT->BeginDraw();</span><br><span class="line"></span><br><span class="line">pRT->DrawRectangle(</span><br><span class="line"> D2D1::RectF(</span><br><span class="line"> rc.left + <span class="number">100.0</span>f,</span><br><span class="line"> rc.top + <span class="number">100.0</span>f,</span><br><span class="line"> rc.right - <span class="number">100.0</span>f,</span><br><span class="line"> rc.bottom - <span class="number">100.0</span>f),</span><br><span class="line"> pBlackBrush);</span><br><span class="line"></span><br><span class="line">HRESULT hr = pRT->EndDraw(); </span><br><span class="line"> </span><br><span class="line">SafeRelease(pRT);</span><br><span class="line">SafeRelease(pBlackBrush);</span><br></pre></td></tr></table></figure><h3 id="Vulkan"><a href="#Vulkan" class="headerlink" title="Vulkan"></a>Vulkan</h3><p>跨平台图形与计算应用程序接口,号称下一代OpenGL</p><p><a href="https://easyvulkan.github.io/index.html">首页 — EasyVulkan</a></p><p><a href="https://www.vulkan.org/">Home | Vulkan | Cross platform 3D Graphics</a></p><p><a href="https://github.com/PacktPublishing/Learning-Vulkan">PacktPublishing/Learning-Vulkan: Code repository for Learning Vulkan, published by Packt (github.com)</a></p><p><a href="https://vulkan-tutorial.com/">https://vulkan-tutorial.com/</a></p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">initVulkan</span><span class="params">()</span> </span>{</span><br><span class="line"> createInstance();</span><br><span class="line"> setupDebugMessenger();</span><br><span class="line"> createSurface();</span><br><span class="line"> pickPhysicalDevice();</span><br><span class="line"> createLogicalDevice();</span><br><span class="line"> createSwapChain();</span><br><span class="line"> createImageViews();</span><br><span class="line"> createGraphicsPipeline();</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">...</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">createGraphicsPipeline</span><span class="params">()</span> </span>{</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="Metal"><a href="#Metal" class="headerlink" title="Metal"></a>Metal</h3><p>苹果上的类似OpenGL,Direct3D,兼顾图形与计算,面向底层应用程序接口.</p><h2 id="相关应用库"><a href="#相关应用库" class="headerlink" title="相关应用库"></a>相关应用库</h2><ol><li>SFML</li><li>SDL</li><li>FLTK</li><li>Qt</li><li>GLFW(针对OpenGL图形库)</li><li>raylib</li><li>imgui</li><li>nanoGui</li><li>easyx</li><li><a href="https://oxygine.org/">Oxygine - 2D C++ game framework</a></li></ol><h2 id="相关资料"><a href="#相关资料" class="headerlink" title="相关资料"></a>相关资料</h2><ol><li><a href="https://jichu4n.com/posts/how-x-window-managers-work-and-how-to-write-one-part-i/">How X Window Managers Work, And How To Write One (Part I) (jichu4n.com)</a></li><li><a href="https://hereket.com/posts/linux_creating_x11_windows/">Xlib 01: Creating windows from scratch with Xlib on Linux | Hereket</a></li><li><a href="https://xcb.freedesktop.org/tutorial/">tutorial (xcb.freedesktop.org)</a></li><li><a href="https://www.cairographics.org/">cairographics.org</a></li><li><a href="https://zh.wikipedia.org/wiki/Metal_(API">Metal (API) - 维基百科,自由的百科全书 (wikipedia.org)</a>)</li><li><a href="https://zh.wikipedia.org/wiki/Vulkan">Vulkan - 维基百科,自由的百科全书 (wikipedia.org)</a></li></ol><link rel="stylesheet" href="/css/spoiler.css" type="text/css"><script src="/js/spoiler.js" type="text/javascript" async></script>]]></content>
<summary type="html"><p>当我们想要进行底层图形应用(GUI)开发时,往往需要用到窗口系统和图形库,这里简单介绍一下<br></summary>
<category term="window systems" scheme="https://www.sekyoro.top/tags/window-systems/"/>
<category term="graphics library" scheme="https://www.sekyoro.top/tags/graphics-library/"/>
</entry>
<entry>
<title>打实基础:cs61c学习</title>
<link href="https://www.sekyoro.top/2024/10/01/%E6%89%93%E5%AE%9E%E5%9F%BA%E7%A1%80-cs61c%E5%AD%A6%E4%B9%A0/"/>
<id>https://www.sekyoro.top/2024/10/01/%E6%89%93%E5%AE%9E%E5%9F%BA%E7%A1%80-cs61c%E5%AD%A6%E4%B9%A0/</id>
<published>2024-10-01T06:18:42.000Z</published>
<updated>2024-10-03T06:36:03.157Z</updated>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="\assets\js\Meting.min.js"></script><p>很早之间就想学学传说中的cs61a/b/c了,这里花两三天时间过一遍cs61c(2020).</p><span id="more"></span><p>整个课程做起来最大的问题是,过去的课程目前不开放给校外人员了,现有的资料没有保存图片,所以lab没有图片..</p><h2 id="Labs"><a href="#Labs" class="headerlink" title="Labs"></a>Labs</h2><h3 id="00"><a href="#00" class="headerlink" title="00"></a>00</h3><p>学习常用指令,我个人常用(由于我目前使用windows,许多指令都没有)find,grep,ps,kill等指令</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">ps aux | grep <span class="string">"xxx"</span></span><br><span class="line">find ./ -<span class="built_in">type</span> f -<span class="built_in">exec</span> grep <span class="string">"xx"</span> {} +</span><br></pre></td></tr></table></figure><p>现在Linux上有许多重写的更好用的工具,比如<code>ripgrep</code>,<code>btop</code>等等</p><h3 id="01"><a href="#01" class="headerlink" title="01"></a>01</h3><p>使用gdb和valgrind</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">gcc -g -o hello hello.c</span><br><span class="line">gdb hello</span><br><span class="line">b <span class="comment"># set breakpoint</span></span><br><span class="line">bt <span class="comment"># backtrace </span></span><br><span class="line">p expr <span class="comment"># display value</span></span><br><span class="line">c <span class="comment"># continue running</span></span><br><span class="line">n <span class="comment"># next line,stepping over function calls</span></span><br><span class="line">s <span class="comment"># next line,stepping into function calls</span></span><br></pre></td></tr></table></figure><p>详情查看manual<a href="https://www.learncs.site/assets/files/gdb5-refcard-89fdebb2475f348ded03071dd13271df.pdf">gdb5-refcard-89fdebb2475f348ded03071dd13271df.pdf (learncs.site)</a></p><p><img data-src="https://s2.loli.net/2024/10/01/vy67mZfx5C9RIok.png" alt="image-20241001160602317"></p><p>运行时内存错误可以使用valgrind. Valgrind是一个程序,模拟你的CPU和跟踪你的内存访问</p><p>此外写一个检查链表是否有循环的c代码</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"ll_cycle.h"</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><stddef.h></span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">ll_has_cycle</span><span class="params">(node *head)</span> </span>{</span><br><span class="line"> <span class="comment">/* your code here */</span></span><br><span class="line"> <span class="keyword">if</span> (head == <span class="literal">NULL</span> || head->next == <span class="literal">NULL</span>) {</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line"> }</span><br><span class="line"> node *tortoise = head;</span><br><span class="line"> node *hare = tortoise->next->next;</span><br><span class="line"> <span class="keyword">while</span> (hare != <span class="literal">NULL</span>) {</span><br><span class="line"> tortoise = tortoise->next;</span><br><span class="line"> <span class="keyword">if</span> (hare == tortoise) {</span><br><span class="line"> <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (hare->next == <span class="literal">NULL</span>) {</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line"> }</span><br><span class="line"> hare = hare->next->next;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="02"><a href="#02" class="headerlink" title="02"></a>02</h3><p>学习使用Makefile(虽然我现在基本使用CMake或者直接用visual studio(当然vs仙子啊也支持了cmake))</p><blockquote><p>makefile是代码目录中的一个文本文件(字面上标记为“makefile”),其中包含一组规则,每个规则都有为其编译C程序的命令。每个makefile可以包含多个规则,每个规则编译一个或多个目标(例如可执行文件)或执行不同的目标。要编译一个目标,程序员只需要在他们的命令终端输入“make</p></blockquote><p>使用位操作分别获取,设置,翻转一个值的某位.</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">unsigned</span> <span class="title">get_bit</span><span class="params">(<span class="keyword">unsigned</span> x, <span class="keyword">unsigned</span> n)</span> </span>{</span><br><span class="line"> <span class="comment">// YOUR CODE HERE</span></span><br><span class="line"> <span class="comment">// Returning -1 is a placeholder (it makes</span></span><br><span class="line"> <span class="comment">// no sense, because get_bit only returns</span></span><br><span class="line"> <span class="comment">// 0 or 1)</span></span><br><span class="line"> <span class="keyword">int</span> mask = <span class="number">1</span> << n; <span class="comment">// e.g. n = 5, 000100000</span></span><br><span class="line"> <span class="keyword">return</span> (x & mask) >> n;</span><br><span class="line">}</span><br><span class="line"><span class="comment">// Set the nth bit of the value of x to v.</span></span><br><span class="line"><span class="comment">// Assume 0 <= n <= 31, and v is 0 or 1</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">set_bit</span><span class="params">(<span class="keyword">unsigned</span> *x, <span class="keyword">unsigned</span> n, <span class="keyword">unsigned</span> v)</span> </span>{</span><br><span class="line"> <span class="comment">// YOUR CODE HERE</span></span><br><span class="line"> <span class="keyword">int</span> mask = <span class="number">1</span> << n;</span><br><span class="line"> <span class="keyword">if</span> (v == <span class="number">1</span>) {</span><br><span class="line"> *x = *x | mask;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> *x = *x & ~mask;</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"><span class="comment">// Flip the nth bit of the value of x.</span></span><br><span class="line"><span class="comment">// Assume 0 <= n <= 31</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">flip_bit</span><span class="params">(<span class="keyword">unsigned</span> *x, <span class="keyword">unsigned</span> n)</span> </span>{</span><br><span class="line"> <span class="comment">// YOUR CODE HERE</span></span><br><span class="line"> <span class="keyword">int</span> mask = <span class="number">1</span> << n;</span><br><span class="line"> *x = *x ^ mask;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>实现线性反馈移位寄存器</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">lfsr_calculate</span><span class="params">(<span class="keyword">uint16_t</span> *reg)</span> </span>{</span><br><span class="line"> <span class="comment">/* YOUR CODE HERE */</span></span><br><span class="line"> <span class="keyword">int</span> bit0 = *reg & <span class="number">1</span>;</span><br><span class="line"> <span class="keyword">int</span> bit2 = (*reg >> <span class="number">2</span>) & <span class="number">1</span>;</span><br><span class="line"> <span class="keyword">int</span> bit3 = (*reg >> <span class="number">3</span>) & <span class="number">1</span>;</span><br><span class="line"> <span class="keyword">int</span> bit5 = (*reg >> <span class="number">5</span>) & <span class="number">1</span>;</span><br><span class="line"> <span class="keyword">int</span> new_bit = bit0 ^ bit2 ^ bit3 ^ bit5;</span><br><span class="line"> *reg = *reg >> <span class="number">1</span>;</span><br><span class="line"> <span class="keyword">int</span> mask = new_bit << <span class="number">15</span>;</span><br><span class="line"> *reg = *reg | mask;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>然后实现一个动态长度array,其实就是利用<code>malloc</code>,<code>free</code>分配内存,这些内存往往在堆上.</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> {</span></span><br><span class="line"> usize capacity;</span><br><span class="line"> uszie size;</span><br><span class="line"> <span class="keyword">int</span>* data;</span><br><span class="line">} DArray;</span><br></pre></td></tr></table></figure><p>初始化和free时主要需要分别malloc整个结构和存储的data数据,</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">vector_t</span> *<span class="title">vector_new</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="comment">/* Declare what this function will return */</span></span><br><span class="line"> <span class="keyword">vector_t</span> *retval;</span><br><span class="line"></span><br><span class="line"> <span class="comment">/* First, we need to allocate memory on the heap for the struct */</span></span><br><span class="line"> retval = (<span class="keyword">vector_t</span> *)<span class="built_in">malloc</span>(<span class="keyword">sizeof</span>(<span class="keyword">vector_t</span>)); <span class="comment">/* YOUR CODE HERE */</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">/* Check our return value to make sure we got memory */</span></span><br><span class="line"> <span class="keyword">if</span> (retval == <span class="literal">NULL</span>) {</span><br><span class="line"> allocation_failed();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/* Now we need to initialize our data.</span></span><br><span class="line"><span class="comment"> Since retval->data should be able to dynamically grow,</span></span><br><span class="line"><span class="comment"> what do you need to do? */</span></span><br><span class="line"> retval->size = <span class="number">1</span> <span class="comment">/* YOUR CODE HERE */</span>;</span><br><span class="line"> retval->data = (<span class="keyword">int</span> *)<span class="built_in">malloc</span>(retval->size * <span class="keyword">sizeof</span>(<span class="keyword">int</span>)) <span class="comment">/* YOUR CODE HERE */</span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">/* Check the data attribute of our vector to make sure we got memory */</span></span><br><span class="line"> <span class="keyword">if</span> (retval->data == <span class="literal">NULL</span>) {</span><br><span class="line"> <span class="built_in">free</span>(retval); <span class="comment">// Why is this line necessary?</span></span><br><span class="line"> allocation_failed();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/* Complete the initialization by setting the single component to zero */</span></span><br><span class="line"> retval->data[<span class="number">0</span>] = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">/* and return... */</span></span><br><span class="line"> <span class="keyword">return</span> retval;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="03"><a href="#03" class="headerlink" title="03"></a>03</h3><p>RISCV汇编,</p><p>.data段落包含初始化的全局和静态变量, .word段用于分配和初始化内存(通常4字节)</p><p>.text段落包括程序的可执行指令,x0寄存器中值始终为0</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">.text</span><br><span class="line">main:</span><br><span class="line"> add t0, x0, x0 # curr_fib = 0</span><br><span class="line"> addi t1, x0, 1 # next_fib = 1</span><br><span class="line">la t3, n # load the address of the label n</span><br><span class="line">lw t3, 0(t3) # get the value that is stored at the adddress denoted by the label n</span><br><span class="line">fib:</span><br><span class="line"> beq t3, x0, finish # exit loop once we have completed n iterations</span><br><span class="line"> add t2, t1, t0 # new_fib = curr_fib + next_fib;</span><br><span class="line"> mv t0, t1 # curr_fib = next_fib;</span><br><span class="line"> mv t1, t2 # next_fib = new_fib;</span><br><span class="line"> addi t3, t3, -1 # decrement counter</span><br><span class="line"> j fib # loop</span><br><span class="line">finish:</span><br><span class="line"> addi a0, x0, 1 # argument to ecall to execute print integer</span><br><span class="line"> addi a1, t0, 0 # argument to ecall, the value to be printed</span><br><span class="line"> ecall # print integer ecall</span><br></pre></td></tr></table></figure><p><code>ecall</code>是系统调用,要传递参数,需要将其放入参数寄存器(a0-a7)。当函数执行时,它将在这些寄存器中查找参数。第一个参数应该放在a0中,第二个参数应该放在a1中,以此类推。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">int</span> source[] = {<span class="number">3</span>, <span class="number">1</span>, <span class="number">4</span>, <span class="number">1</span>, <span class="number">5</span>, <span class="number">9</span>, <span class="number">0</span>};</span><br><span class="line"><span class="keyword">int</span> dest[<span class="number">10</span>];</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">fun</span><span class="params">(<span class="keyword">int</span> x)</span> </span>{</span><br><span class="line"><span class="keyword">return</span> -x * (x + <span class="number">1</span>);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">int</span> k;</span><br><span class="line"> <span class="keyword">int</span> sum = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">for</span> (k = <span class="number">0</span>; source[k] != <span class="number">0</span>; k++) {</span><br><span class="line"> dest[k] = fun(source[k]);</span><br><span class="line"> sum += dest[k];</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> sum;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>上面c代码转为汇编,全局变量使用<code>.data</code>和<code>.word</code>分配,只写t和a寄存器(临时寄存器和参数寄存器),s寄存器在调用函数前注意保存</p><p><img data-src="https://s2.loli.net/2024/10/01/iKzPvVZAfa1pCnG.png" alt="image-20241001195355718"></p><ul><li><p><strong><code>li</code> 指令</strong>:将立即数加载到寄存器中。</p></li><li><p><strong><code>la</code> 指令</strong>:加载变量的地址到寄存器中。</p></li><li><p><strong><code>lw</code> 指令</strong>:从内存中加载一个 32 位字到寄存器中。</p></li><li><p><strong><code>sw</code> 指令</strong>:将寄存器中的 32 位字存储到内存中。</p></li><li><p><strong><code>sw</code> 指令</strong>:将寄存器中的 32 位字存储到内存中。</p></li><li><p><strong><code>sh</code> 指令</strong>:将寄存器中的 16 位半字存储到内存中。</p></li><li><p><strong><code>sb</code> 指令</strong>:将寄存器中的 8 位字节存储到内存中</p><p>内存地址主要就是寄存器offset寻址</p></li></ul><h4 id="RISC-V-调用约定"><a href="#RISC-V-调用约定" class="headerlink" title="RISC-V 调用约定"></a>RISC-V 调用约定</h4><p>RISC-V 的调用约定规定了哪些寄存器在函数调用时需要保存,哪些寄存器可以被修改。以下是一些主要的约定:</p><ol><li><strong>临时寄存器(Temporary Registers)</strong>:<ul><li><strong><code>t0</code> 至 <code>t6</code></strong>:这些寄存器可以在函数调用时被修改,调用者不需要保存这些寄存器的值。</li><li><strong><code>a0</code> 至 <code>a7</code></strong>:这些寄存器用于传递函数参数,也可以在函数调用时被修改,调用者不需要保存这些寄存器的值。</li></ul></li><li><strong>保存寄存器(Saved Registers)</strong>:<ul><li><strong><code>s0</code> 至 <code>s11</code></strong>:这些寄存器在函数调用时需要保存,被调用者必须在返回前恢复这些寄存器的值。</li><li><strong><code>ra</code></strong>:返回地址寄存器,被调用者必须在返回前恢复 <code>ra</code> 的值。</li></ul></li></ol><p><strong>保存和恢复寄存器</strong></p><p>为了确保函数调用的正确性,通常需要在函数入口处保存这些寄存器的值,并在函数返回前恢复这些值。这通常通过使用堆栈来实现(利用<code>sp</code>栈寄存器). 通常内存上的值首先加到寄存器上然后再进行计算.</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">.data</span><br><span class="line">n: .word 8</span><br><span class="line">lw t0, 0(sp) ; t0 = [sp]</span><br><span class="line">la t1, n</span><br><span class="line">lw t1, 0(t1) ; t1 = [t1]</span><br><span class="line">addi t1,t1,2</span><br><span class="line">sw t0, 0(t1) ; [t1] = t0</span><br></pre></td></tr></table></figure><p>使用RISCV写一段factorial代码,注意要么写为recursion要么写更简单的iteration</p><figure class="highlight stylus"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="title">factorial</span><span class="params">(n)</span></span> = n!</span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">factorial:</span><br><span class="line"> addi sp,sp,-4</span><br><span class="line"> sw s0,0(sp) # sp[0] = s0</span><br><span class="line"> addi s0,x0,1 # sum = 1</span><br><span class="line"> addi t0,x0,1 # i = 1</span><br><span class="line">loop:</span><br><span class="line"> blt a0,t0,exit # if n < i, goto exit</span><br><span class="line"> mul s0,s0,t0# sum = sum * i</span><br><span class="line"> addi t0,t0,1 # i++</span><br><span class="line"> jal x0,loop</span><br><span class="line">exit:</span><br><span class="line"> add a0,x0,s0 # a0 = sum</span><br><span class="line"> lw s0,0(sp) # s0 = sp[0]</span><br><span class="line"> addi sp,sp,4</span><br><span class="line"> jr ra</span><br></pre></td></tr></table></figure><blockquote><p>所有覆盖按约定保留的寄存器的函数都必须有一个序言(prologue)和一个尾声(epilogue),序言将这些寄存器值保存到函数开始时的堆栈中;epilogue将为函数的调用者恢复这些值。 由于调用约定违反而导致的bug很难发现,不要直接修改这些寄存器</p><p>也就是对于保存寄存器的处理,包括s开头的寄存器和ra寄存器(受jal影响)</p></blockquote><p>后面有一个专门的测试,在每个函数前后写上将s寄存器值保存到内存的代码,此外如果一个函数栈中调用了另一个函数,如果那个函数修改了t寄存器,那么父函数栈也需要保存相应的寄存器. 对于ra寄存器同理,函数<code>jrl</code>调用另一个函数,修改了ra寄存器值,也需要在调用另一个函数之前保存ra,否则这个函数无法通过<code>jr</code>跳转回去了(当然可以使用j label调转到某个位置).</p><p>此外还使用RISC-V实现了链表分配和map操作.</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line">create_default_list:</span><br><span class="line"> addi sp, sp, -12</span><br><span class="line"> sw ra, 0(sp)</span><br><span class="line"> sw s0, 4(sp)</span><br><span class="line"> sw s1, 8(sp)</span><br><span class="line"> li s0, 0 # pointer to the last node we handled</span><br><span class="line"> li s1, 0 # number of nodes handled</span><br><span class="line">loop: #do...</span><br><span class="line"> li a0, 8</span><br><span class="line"> jal ra, malloc # get memory for the next node</span><br><span class="line"> sw s1, 0(a0) # node->value = i</span><br><span class="line"> sw s0, 4(a0) # node->next = last</span><br><span class="line"> add s0, a0, x0 # last = node</span><br><span class="line"> addi s1, s1, 1 # i++</span><br><span class="line"> addi t0, x0, 10</span><br><span class="line"> bne s1, t0, loop # ... while i!= 10</span><br><span class="line"> lw ra, 0(sp)</span><br><span class="line"> lw s0, 4(sp)</span><br><span class="line"> lw s1, 8(sp)</span><br><span class="line"> addi sp, sp, 12</span><br><span class="line"> jr ra</span><br><span class="line">malloc:</span><br><span class="line"> addi a1, a0, 0</span><br><span class="line"> addi a0, x0 9</span><br><span class="line"> ecall</span><br><span class="line"> jr r</span><br></pre></td></tr></table></figure><p>上面malloc中调用了<code>ecall</code>系统调用.</p><p>实现高阶函数<code>map</code>操作</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br></pre></td><td class="code"><pre><span class="line">map:</span><br><span class="line"> # Prologue: Make space on the stack and back-up registers</span><br><span class="line"> ### YOUR CODE HERE ###</span><br><span class="line"> addi sp,sp,-12</span><br><span class="line"> sw ra, 0(sp)</span><br><span class="line"> sw s0, 4(sp)</span><br><span class="line"> sw s1, 8(sp)</span><br><span class="line"></span><br><span class="line"> beq a0, x0, done # If we were given a null pointer (address 0), we're done.</span><br><span class="line"></span><br><span class="line"> add s0, a0, x0 # Save address of this node in s0</span><br><span class="line"> add s1, a1, x0 # Save address of function in s1</span><br><span class="line"></span><br><span class="line"> # Remember that each node is 8 bytes long: 4 for the value followed by 4 for the pointer to next.</span><br><span class="line"> # What does this tell you about how you access the value and how you access the pointer to next?</span><br><span class="line"></span><br><span class="line"> # load the value of the current node into a0</span><br><span class="line"> # THINK: why a0? prepare to call map</span><br><span class="line"> ### YOUR CODE HERE ###</span><br><span class="line"> lw a0,0(s0) # a0 = [s0]</span><br><span class="line"></span><br><span class="line"> # Call the function in question on that value. DO NOT use a label (be prepared to answer why).</span><br><span class="line"> # What function? Recall the parameters of "map"</span><br><span class="line"> ### YOUR CODE HERE ###</span><br><span class="line"> # jal is used to jump to label</span><br><span class="line"> jalr ra, s1 ,0 # call square</span><br><span class="line"> # Where can you assume the returned value is?</span><br><span class="line"> ### YOUR CODE HERE ###</span><br><span class="line"> sw a0, 0(s0) # [s0] = a0</span><br><span class="line"></span><br><span class="line"> # Load the address of the next node into a0</span><br><span class="line"> # The Address of the next node is an attribute of the current node.</span><br><span class="line"> # Think about how structs are organized in memory.</span><br><span class="line"> ### YOUR CODE HERE ###</span><br><span class="line"> lw a0, 4(s0) # a0 = [s0+4]</span><br><span class="line"></span><br><span class="line"> # Put the address of the function back into a1 to prepare for the recursion</span><br><span class="line"> # THINK: why a1? What about a0?</span><br><span class="line"> ### YOUR CODE HERE ###</span><br><span class="line"> mv a1, s1 # not really necessary, cause a1 is not changed in square functino</span><br><span class="line"></span><br><span class="line"> # recurse</span><br><span class="line"> ### YOUR CODE HERE ###</span><br><span class="line"> jal ra, map</span><br><span class="line"></span><br><span class="line">done:</span><br><span class="line"> # Epilogue: Restore register values and free space from the stack</span><br><span class="line"> ### YOUR CODE HERE ###</span><br><span class="line"> lw ra, 0(sp)</span><br><span class="line"> lw s0, 4(sp)</span><br><span class="line"> lw s1, 8(sp)</span><br><span class="line"> addi sp,sp,12</span><br><span class="line"> jr ra # Return to caller</span><br><span class="line"></span><br><span class="line">square:</span><br><span class="line"> mul a0 ,a0, a0</span><br><span class="line"> jr ra</span><br></pre></td></tr></table></figure><h3 id="04"><a href="#04" class="headerlink" title="04"></a>04</h3><p>修改一个汇编代码中的问题,我卡了很久还是看了其他人的解答.</p><p>在main中获取数组第一个值地址和大小,在mainLoop中不断遍历,修改数组值</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br></pre></td><td class="code"><pre><span class="line">map:</span><br><span class="line"> addi sp, sp, -12</span><br><span class="line"> sw ra, 0(sp)</span><br><span class="line"> sw s1, 4(sp)</span><br><span class="line"> sw s0, 8(sp)</span><br><span class="line"></span><br><span class="line"> beq a0, x0, done # if we were given a null pointer, we're done.</span><br><span class="line"></span><br><span class="line"> add s0, a0, x0 # save address of this node in s0</span><br><span class="line"> add s1, a1, x0 # save address of function in s1</span><br><span class="line"> add t0, x0, x0 # t0 is a counter</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"> lw t1, 0(s0) # load the address of the array of current node into t1</span><br><span class="line"> lw t2, 4(s0) # load the size of the node's array into t2</span><br><span class="line"> addi t1, t1, -4</span><br><span class="line"> # remember that each node is 12 bytes long:</span><br><span class="line"> # - 4 for the array pointer</span><br><span class="line"> # - 4 for the size of the array</span><br><span class="line"> # - 4 more for the pointer to the next node</span><br><span class="line"></span><br><span class="line"> # also keep in mind that we should not make ANY assumption on which registers</span><br><span class="line"> # are modified by the callees, even when we know the content inside the functions </span><br><span class="line"> # we call. this is to enforce the abstraction barrier of calling convention.</span><br><span class="line">mapLoop:</span><br><span class="line"> # add t1, s0, x0 # load the address of the array of current node into t1</span><br><span class="line"> addi t1, t1, 4 # offset the array address by the count</span><br><span class="line"> lw a0, 0(t1) # load the value at that address into a0</span><br><span class="line"> addi sp,sp,-12</span><br><span class="line"> sw t0, 0(sp)</span><br><span class="line"> sw t1, 4(sp)</span><br><span class="line"> sw t2, 8(sp)</span><br><span class="line"> jalr ra,s1,0 # call the function on that value.</span><br><span class="line"> lw t0, 0(sp)</span><br><span class="line"> lw t1, 4(sp)</span><br><span class="line"> lw t2, 8(sp)</span><br><span class="line"> addi sp,sp,12</span><br><span class="line"></span><br><span class="line"> sw a0, 0(t1) # store the returned value back into the array</span><br><span class="line"> addi t0, t0, 1 # increment the count</span><br><span class="line"> bne t0, t2, mapLoop # repeat if we haven't reached the array size yet</span><br><span class="line"></span><br><span class="line"> lw a0, 8(s0) # load the address of the next node into a0</span><br><span class="line"> mv a1,s1</span><br><span class="line"></span><br><span class="line"> jal map # recurse</span><br><span class="line">done:</span><br><span class="line"> lw s0, 8(sp)</span><br><span class="line"> lw s1, 4(sp)</span><br><span class="line"> lw ra, 0(sp)</span><br><span class="line"> addi sp, sp, 12</span><br><span class="line"> jr ra</span><br></pre></td></tr></table></figure><p>然后写一个映射</p><figure class="highlight stylus"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="title">f</span><span class="params">(-<span class="number">3</span>)</span></span> = <span class="number">6</span></span><br><span class="line"><span class="function"><span class="title">f</span><span class="params">(-<span class="number">2</span>)</span></span> = <span class="number">61</span></span><br><span class="line"><span class="function"><span class="title">f</span><span class="params">(-<span class="number">1</span>)</span></span> = <span class="number">17</span></span><br><span class="line"><span class="function"><span class="title">f</span><span class="params">(<span class="number">0</span>)</span></span> = -<span class="number">38</span></span><br><span class="line"><span class="function"><span class="title">f</span><span class="params">(<span class="number">1</span>)</span></span> = <span class="number">19</span></span><br><span class="line"><span class="function"><span class="title">f</span><span class="params">(<span class="number">2</span>)</span></span> = <span class="number">42</span></span><br><span class="line"><span class="function"><span class="title">f</span><span class="params">(<span class="number">3</span>)</span></span> = <span class="number">5</span></span><br></pre></td></tr></table></figure><p>其中a0寄存器是输入值,a1是输出列表</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">f:</span><br><span class="line"> addi a0,a0,3</span><br><span class="line"> # li t0, 4</span><br><span class="line"> # mul a0,a0,t0</span><br><span class="line"> # add s0,a1,a0 # s0 is the address</span><br><span class="line"> slli a0,a0,2</span><br><span class="line"> add a0,a1,a0</span><br><span class="line"> lw a0, 0(a0)</span><br><span class="line"> </span><br><span class="line"> jr ra </span><br></pre></td></tr></table></figure><h3 id="05"><a href="#05" class="headerlink" title="05"></a>05</h3><p>使用logisim工具构建模拟电路,使用AND,OR,NOT基本件构建NAND,NOR,XOR以及Mux. 此外还有个寄存器</p><p><img data-src="https://s2.loli.net/2024/10/02/2QKgjTAZU48rXcP.png" alt="image-20241002193745279"></p><p>FSM代表有限状态机。FSM跟踪给定的输入,根据这些输入在状态之间移动,并在每次输入时输出一些东西。</p><p>我们使用寄存器来存储当前所在FSM的状态,并使用组合逻辑将FSM的输入和当前寄存器状态映射到FSM的输出和下一个寄存器状态。说实话,这个FSM的作用我没太看懂,貌似是连续两次一样的输入,输出就固定了. 需要连现在状态到下一次状态的电路线,我按照truth table直接推的.</p><p>此外还需要画出rotr电路,我一开始画成下面这样,使用Mux-16针对每个0-15值选择对应偏移…</p><p><img data-src="https://s2.loli.net/2024/10/02/VFjKlRYLwc826qb.png" alt="image-20241002224915855"></p><p>其实可以根据每一位使用mux-2,简单得多</p><p><img data-src="https://s2.loli.net/2024/10/02/MG3ecRyFif1njkb.png" alt="image-20241002231322258"></p><h3 id="06"><a href="#06" class="headerlink" title="06"></a>06</h3><p>关于电路流水线时间的问题,通过编排优化流水线</p><p><img data-src="https://s2.loli.net/2024/10/02/q8GXlWucIeimaCz.png" alt="image-20241002231814951"></p><p>这个电路所做的就是接收两个输入,将它们相乘,然后将结果与当前状态值相加。对于这个电路,设<strong>加法器块的传播延迟为45ns,乘法块的传播延迟为60ns。寄存器的CLK-to-Q延迟为10ns,设置时间为10ns,保持时间为5ns</strong>。计算该电路可以工作的最大时钟速率。假设两个输入都来自从外部源接收数据的时钟寄存器。</p><p>为了确保电路能够正确工作,时钟周期必须满足以下条件:</p><ol><li><strong>数据路径延迟</strong> + <strong>CLK-to-Q 延迟</strong> + <strong>建立时间</strong> ≤ 时钟周期</li><li><strong>保持时间</strong> < 时钟周期</li></ol><p>首先,计算第一个条件: 时钟周期≥数据路径延迟+CLK-to-Q 延迟+建立时间时钟周期≥数据路径延迟+CLK-to-Q 延迟+建立时间 时钟周期≥105 ns+10 ns+10 ns时钟周期≥105ns+10ns+10ns 时钟周期≥125 ns时钟周期≥125ns</p><p>接下来,检查保持时间的要求: 时钟周期>5 ns时钟周期>5ns</p><p>显然,125ns 已经大于 5ns,所以保持时间的要求自然满足。</p><p><img data-src="https://s2.loli.net/2024/10/02/HAY1F6ZsJbhjix7.png" alt="image-20241002232639288"></p><p>如果一个计算依赖于前一个计算的输出,那么很难对它们进行管道处理,我们通常需要插入一个(或几个)管道“bubbles”,以确保第一个计算的输出准备好成为第二个计算的输入。提醒一下,bubbles是在管道中故意延迟指令的过程。理解为什么这种“bubbles”对于这种特殊的电路是不必要的是很重要的。</p><p>下面这种pipeline方案在乘法和加法之间增加了一个register保存中间值,输出会增加一个前导0</p><p><img data-src="https://s2.loli.net/2024/10/02/DZFX7x5wgIiQHCK.png" alt="image-20241002234052193"></p><p>在non_pipeline版本中,一个tick会同时更新输出值和寄存器的值</p><p>假设我们在乘法器和加法器之间插入一个寄存器,将电路分成两个阶段:</p><ol><li><strong>第一阶段</strong>:乘法器</li><li><strong>第二阶段</strong>:加法器</li></ol><p>每个阶段的延迟如下:</p><ul><li>第一阶段(乘法器)延迟:60ns</li><li>第二阶段(加法器)延迟:45ns</li></ul><p>寄存器的时序要求</p><ul><li><strong>CLK-to-Q 延迟</strong>:10ns</li><li><strong>建立时间</strong>:10ns</li><li><strong>保持时间</strong>:5ns</li></ul><p><strong>计算每个阶段的最大时钟周期</strong></p><p>为了确保每个阶段的时序要求都得到满足,我们需要计算每个阶段的最大时钟周期。</p><p><strong>第一阶段</strong></p><ul><li><strong>数据路径延迟</strong>:60ns</li><li><strong>CLK-to-Q 延迟</strong>:10ns</li><li><strong>建立时间</strong>:10ns</li></ul><p>第一阶段的时钟周期要求: 时钟周期≥60 ns+10 ns+10 ns时钟周期≥60ns+10ns+10ns 时钟周期≥80 ns时钟周期≥80ns</p><p><strong>第二阶段</strong></p><ul><li><strong>数据路径延迟</strong>:45ns</li><li><strong>CLK-to-Q 延迟</strong>:10ns</li><li><strong>建立时间</strong>:10ns</li></ul><p>第二阶段的时钟周期要求: 时钟周期≥45 ns+10 ns+10 ns时钟周期≥45ns+10ns+10ns 时钟周期≥65 ns时钟周期≥65ns</p><p>为了确保整个流水线电路能够正确工作,时钟周期必须满足所有阶段中最严格的要求。因此,最终的时钟周期应取这两个阶段的最大值:</p><p>时钟周期=max(80 ns,65 ns)=80 ns时钟周期=max(80ns,65ns)=80ns</p><p>由于乘法延时比加法长,这两个阶段相对独立.</p><p>在流水线设计中,“气泡”(bubbles)通常指的是空闲的时钟周期,用于处理流水线中的依赖关系或避免冲突。在某些情况下,插入气泡是为了确保数据的正确性和避免数据冒险(data hazards)。然而,在这个电路中,插入一个寄存器将乘法和加法操作分成两个独立的阶段,这样可以避免大多数常见的数据冒险,因此不需要插入气泡。</p><p>为什么这个电路不需要气泡?</p><ol><li><strong>独立的流水线阶段</strong>:<ul><li>通过在乘法器和加法器之间插入一个寄存器,将整个操作分成了两个独立的阶段。</li><li>每个阶段都有自己的时钟周期,数据在每个阶段的末尾被寄存器捕获并传递到下一个阶段。</li></ul></li><li><strong>数据流的清晰分离</strong>:<ul><li>在第一阶段,乘法器完成乘法操作,并将结果存储在寄存器中。</li><li>在第二阶段,加法器从寄存器中读取乘法结果,并将其与当前状态值相加。</li><li>这种分离确保了每个阶段的数据都是在前一个阶段完成后的稳定状态下进行处理的,避免了数据冒险。</li></ul></li><li><strong>时序要求的满足</strong>:<ul><li>每个阶段的时钟周期都足够长,以满足寄存器的时序要求(CLK-to-Q 延迟、建立时间和保持时间)。</li><li>例如,第一阶段的时钟周期为 80ns,第二阶段的时钟周期为 65ns,这两个值都足以确保数据在每个阶段的末尾被正确捕获和传递</li></ul></li></ol><h3 id="07"><a href="#07" class="headerlink" title="07"></a>07</h3><p>主要介绍cache,</p><p>write-back意味着在写入命中时,数据只被写入缓存,当这个写入发生时,被写入块的脏位变为1。写入缓存的速度很快,因此回写缓存中的写入延迟通常非常小。但是,当从回写缓存中取出块时,如果脏位为1,则必须使用该块的内容更新内存,因为它包含尚未反映在内存中的更改。这使得回写缓存更难以在硬件中实现。</p><p>write-through意味着在写命中时,数据同时写入缓存和主存。写入缓存很快,但写入主存很慢;这使得透写缓存中的写延迟比回写缓存中的写延迟慢。然而,透写缓存意味着更简单的硬件,因为我们可以假设在透写缓存中,内存总是拥有最新的数据。</p><p>Write-around意味着在任何情况下,数据只被写入主存;如果缓存中有要写入的块,则块的有效位变为无效位。从本质上讲,在写绕过缓存中没有写命中这样的事情;写“命中”和写“未命中”的作用是一样的</p><p><strong>miss policies</strong></p><p>write -allocate的意思是,在写失败时,将丢失的块拉到缓存中。对于回写,写分配缓存,这意味着内存永远不会直接写入;相反,总是对缓存进行写操作,并且在删除时更新内存。</p><p><strong>过程</strong>:</p><ol><li><strong>写操作</strong>:处理器尝试写入某个内存地址。</li><li><strong>缓存查找</strong>:缓存控制器检查该地址是否在缓存中。</li><li><strong>未命中</strong>:如果该地址不在缓存中,发生未命中。</li><li><strong>加载缓存行</strong>:从主存中读取该地址所在的整个缓存行,并将其加载到缓存中。</li><li><strong>写入缓存</strong>:将写操作的数据写入缓存中的相应位置。</li><li><strong>写入主存</strong>:根据缓存的写策略(写回或写穿透),决定是否立即将数据写入主存</li></ol><p>no write-allocate意味着在写失败时,不会将错过的块拉到缓存中。只更新内存</p><p><strong>过程</strong>:</p><ol><li><strong>写操作</strong>:处理器尝试写入某个内存地址。</li><li><strong>缓存查找</strong>:缓存控制器检查该地址是否在缓存中。</li><li><strong>未命中</strong>:如果该地址不在缓存中,发生未命中。</li><li><strong>加载缓存行</strong>:从主存中读取该地址所在的整个缓存行,并将其加载到缓存中。</li><li><strong>写入缓存</strong>:将写操作的数据写入缓存中的相应位置。</li><li><strong>写入主存</strong>:根据缓存的写策略(写回或写穿透),决定是否立即将数据写入主存</li></ol><p><strong>更新策略</strong></p><p>LRU—最近最少使用当我们决定移除缓存块以腾出空间时,我们选择在所有块中使用时间最远的块</p><p>随机-当我们决定移除一个缓存块来腾出空间时,我们随机选择缓存中的一个块来移除。</p><p>MRU—最近使用的—当我们决定移除缓存块以腾出空间时,我们选择所有块中最近使用的块</p><p>针对下面代码,假设缓存是write through,write-allocate,也就是命中时同时写入缓存和内存,没有命中写入缓存与内存</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">int</span> <span class="built_in">array</span>[]; <span class="comment">//Assume sizeof(int) == 4</span></span><br><span class="line"><span class="keyword">for</span> (k = <span class="number">0</span>; k < repcount; k++) {<span class="comment">// repeat repcount times</span></span><br><span class="line"> <span class="comment">/* Step through the selected array segment with the given step size. */</span></span><br><span class="line"> <span class="keyword">for</span> (index = <span class="number">0</span>; index < arraysize; index += stepsize) {</span><br><span class="line"> <span class="keyword">if</span>(option==<span class="number">0</span>)</span><br><span class="line"> <span class="built_in">array</span>[index] = <span class="number">0</span>;<span class="comment">// Option 0: One cache access - write</span></span><br><span class="line"> <span class="keyword">else</span></span><br><span class="line"> <span class="built_in">array</span>[index] = <span class="built_in">array</span>[index] + <span class="number">1</span>;<span class="comment">// Option 1: Two cache accesses - read AND write</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><strong>缓存行的组织</strong></p><p>缓存行的组织方式通常有以下几种:</p><ol><li><strong>直接映射(Direct-Mapped)</strong>:<ul><li>每个主存块只能映射到缓存中的一个特定位置。</li><li>简单但容易发生冲突未命中。</li></ul></li><li><strong>组相联(Set-Associative)</strong>:<ul><li>主存块可以映射到缓存中的多个位置(一组)。</li><li>平衡了直接映射和全相联的优缺点。</li></ul></li><li><strong>全相联(Fully Associative)</strong>:<ul><li>主存块可以映射到缓存中的任意位置。</li><li>复杂但灵活性高</li></ul></li></ol><h4 id="Scene-1"><a href="#Scene-1" class="headerlink" title="Scene 1"></a>Scene 1</h4><ul><li><strong>Array Size (<code>a0</code>)</strong>: 128 (bytes)</li><li><strong>Step Size (<code>a1</code>)</strong>: 8</li><li><strong>Rep Count (<code>a2</code>)</strong>: 4</li><li><strong>Option (<code>a3</code>)</strong>: 0</li></ul><p><strong>Cache Parameters</strong>: (set these in the Cache tab)</p><ul><li><strong>Cache Levels</strong>: 1</li><li><strong>Block Size</strong>: 8</li><li><strong>Number of Blocks</strong>: 4</li><li><strong>Enable?</strong>: Should be green</li><li><strong>Placement Policy</strong>: Direct Mapped</li><li><strong>Associativity</strong>: 1 (Venus won’t let you change this, why?)</li><li><strong>Block Replacement Policy</strong>: LRU</li></ul><p>128/8/4=4,所以一次遍历会写入4次,遍历4次就会写入16次. 发现每次都没有命中,这是为什么呢? 我们想想,每次没命中就会把对应4字节内容放入缓存和内存(write through,write-allocate),遍历一遍之后block中</p><p><img data-src="https://s2.loli.net/2024/10/03/nkSE5IHOro7KWYV.png" alt="image-20241003134801342"></p><p>cache命中的应用就是矩阵(线性代数)运算</p><p>要正确计算矩阵乘法,循环顺序并不重要。但是<strong>选择访问矩阵元素的顺序会对性能产生很大的影响。当内存访问利用空间和时间局部性,利用已经包含在缓存中的块时,缓存性能更好</strong>(缓存命中更多,缓存丢失更少)。优化程序的内存访问模式对于从内存层次结构中获得良好的性能至关重要</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">multMat1</span><span class="params">( <span class="keyword">int</span> n, <span class="keyword">float</span> *A, <span class="keyword">float</span> *B, <span class="keyword">float</span> *C )</span> </span>{</span><br><span class="line"> <span class="keyword">int</span> i,j,k;</span><br><span class="line"> <span class="comment">/* This is ijk loop order. */</span></span><br><span class="line"> <span class="keyword">for</span>( i = <span class="number">0</span>; i < n; i++ )</span><br><span class="line"> <span class="keyword">for</span>( j = <span class="number">0</span>; j < n; j++ )</span><br><span class="line"> <span class="keyword">for</span>( k = <span class="number">0</span>; k < n; k++ )</span><br><span class="line"> C[i+j*n] += A[i+k*n]*B[k+j*n];</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="Cache-Blocksize"><a href="#Cache-Blocksize" class="headerlink" title="Cache Blocksize"></a>Cache Blocksize</h4><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">transpose_blocking</span><span class="params">(<span class="keyword">int</span> n, <span class="keyword">int</span> blocksize, <span class="keyword">int</span> *dst, <span class="keyword">int</span> *src)</span> </span>{</span><br><span class="line"> <span class="comment">// YOUR CODE HERE</span></span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < n; i += blocksize) {</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> j = <span class="number">0</span>; j < n; j += blocksize) {</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> x = <span class="number">0</span>; x < blocksize; x++) {</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> y = <span class="number">0</span>; y < blocksize; y++) {</span><br><span class="line"> <span class="keyword">if</span> (i + x < n && j + y < n) {</span><br><span class="line"> dst[j + y + (i + x) * n] = src[i + x + (j + y) * n];</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>设置blocksize,一次填写一个方形块,其中每个方形块的尺寸为blocksize × blocksize。</p><p>后面几个lab就是关于页内存,SIMD,OpenMP并行编程以及Spark等等.</p><h2 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h2><ol><li><a href="https://www.learncs.site/docs/intro">前言 | CS自学社区 (learncs.site)</a></li><li><a href="https://www.learncs.site/assets/files/gdb5-refcard-89fdebb2475f348ded03071dd13271df.pdf">gdb5-refcard-89fdebb2475f348ded03071dd13271df.pdf (learncs.site)</a></li><li><a href="https://www.learncs.site/assets/files/riscvcard-75f9fb3a791fab6eee17d3cf216f77f0.pdf">HTU2.indd (learncs.site)</a></li></ol><link rel="stylesheet" href="/css/spoiler.css" type="text/css"><script src="/js/spoiler.js" type="text/javascript" async></script>]]></content>
<summary type="html"><p>很早之间就想学学传说中的cs61a/b/c了,这里花两三天时间过一遍cs61c(2020).</p></summary>
<category term="c" scheme="https://www.sekyoro.top/tags/c/"/>
</entry>
<entry>
<title>Just For Fun: 学习c++之后可以尝试的框架/工具</title>
<link href="https://www.sekyoro.top/2024/09/30/Just-For-Fun-%E5%AD%A6%E4%B9%A0c-%E4%B9%8B%E5%90%8E%E5%8F%AF%E4%BB%A5%E5%B0%9D%E8%AF%95%E7%9A%84%E6%A1%86%E6%9E%B6-%E5%B7%A5%E5%85%B7/"/>
<id>https://www.sekyoro.top/2024/09/30/Just-For-Fun-%E5%AD%A6%E4%B9%A0c-%E4%B9%8B%E5%90%8E%E5%8F%AF%E4%BB%A5%E5%B0%9D%E8%AF%95%E7%9A%84%E6%A1%86%E6%9E%B6-%E5%B7%A5%E5%85%B7/</id>
<published>2024-09-30T09:13:50.000Z</published>
<updated>2024-10-02T15:05:51.395Z</updated>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="\assets\js\Meting.min.js"></script><p>学习c++与其他语言一个不同之处就是, 你可能不能很快地构建一些有趣的项目. </p><p>这时你可能会转向Rust作为代餐,享受安全系统编程语言带来的性能,同时也有一些高级语言所有的强大标准库.或者你也有可能转向Java,C#等更加具有高级应用生态的语言,利用自带的标准库以及强大的三方生态,快速构建性能并不算差的应用. 这里,我简单介绍一下可以使用c++的框架/库以便play for fun.<br><span id="more"></span></p><p>c++常见的开发系统/底层应用主要方向就是1)图形学 2)网络 3) 存储 4)高性能计算</p><p>这里面作为应用开发比较有趣的可能就是利用网络通信再加上图形绘制的游戏开发了. 事实上c++中游戏开发的大佬还是很多的. 作为学习者来说也有许多这方面的库/框架</p><h2 id="较为底层的图形学接口"><a href="#较为底层的图形学接口" class="headerlink" title="较为底层的图形学接口"></a>较为底层的图形学接口</h2><h3 id="OpenGL"><a href="#OpenGL" class="headerlink" title="OpenGL"></a>OpenGL</h3><p><a href="https://www.opengl.org/">OpenGL - The Industry Standard for High Performance Graphics</a></p><p><a href="https://learnopengl-cn.github.io/">主页 - LearnOpenGL CN (learnopengl-cn.github.io)</a></p><p>OpenGL是一个图形API,它是跨平台的,使用c++. 我觉得这里并不需要过多介绍</p><p>Vulkan,Dir等也类似.</p><h2 id="封装后的UI"><a href="#封装后的UI" class="headerlink" title="封装后的UI"></a>封装后的UI</h2><h3 id="Dear-ImGUI"><a href="#Dear-ImGUI" class="headerlink" title="Dear ImGUI"></a>Dear ImGUI</h3><p><a href="https://github.com/ocornut/imgui">ocornut/imgui: Dear ImGui: Bloat-free Graphical User interface for C++ with minimal dependencies (github.com)</a></p><p>Dear ImGui是一个c++图形用户界面库.它输出优化的顶点缓冲区,可以在启用3d管道的应用程序中随时渲染。它快速、可移植、与渲染器无关,并且是自包含的(没有外部依赖)</p><p>这个库需要你格外安装图形学API与窗口管理工具,比如OpenGL+GLFW.之前我写过简单的搭建教程<a href="https://www.sekyoro.top/2024/09/11/Dear-ImGUI-不太一样的GUI/#more">Dear-ImGUI:不太一样的GUI | Sekyoro的博客小屋</a>.</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"imgui.h"</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"imgui_impl_glfw.h"</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"imgui_impl_opengl3.h"</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><stdio.h></span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><GLFW/glfw3.h></span> <span class="comment">// Will drag system OpenGL headers</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">void</span> <span class="title">glfw_error_callback</span><span class="params">(<span class="keyword">int</span> error, <span class="keyword">const</span> <span class="keyword">char</span> *description)</span> </span>{</span><br><span class="line"> <span class="built_in">fprintf</span>(stderr, <span class="string">"GLFW Error %d: %s\n"</span>, error, description);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="comment">// Setup Dear ImGui context</span></span><br><span class="line"> <span class="built_in">glfwSetErrorCallback</span>(glfw_error_callback);</span><br><span class="line"> <span class="keyword">if</span> (!<span class="built_in">glfwInit</span>())</span><br><span class="line"> <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line"> <span class="keyword">const</span> <span class="keyword">char</span> *glsl_version = <span class="string">"#version 130"</span>;</span><br><span class="line"> <span class="built_in">glfwWindowHint</span>(GLFW_CONTEXT_VERSION_MAJOR, <span class="number">3</span>);</span><br><span class="line"> <span class="built_in">glfwWindowHint</span>(GLFW_CONTEXT_VERSION_MINOR, <span class="number">0</span>);</span><br><span class="line"> GLFWwindow *window = <span class="built_in">glfwCreateWindow</span>(</span><br><span class="line"> <span class="number">1280</span>, <span class="number">720</span>, <span class="string">"Dear ImGui GLFW+OpenGL3 example"</span>, <span class="literal">nullptr</span>, <span class="literal">nullptr</span>);</span><br><span class="line"> <span class="keyword">if</span> (window == <span class="literal">nullptr</span>)</span><br><span class="line"> <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line"> <span class="built_in">glfwMakeContextCurrent</span>(window);</span><br><span class="line"> <span class="built_in">glfwSwapInterval</span>(<span class="number">1</span>); <span class="comment">// Enable vsync</span></span><br><span class="line"></span><br><span class="line"> <span class="built_in">IMGUI_CHECKVERSION</span>();</span><br><span class="line"> <span class="comment">// initialize</span></span><br><span class="line"> ImGui::<span class="built_in">CreateContext</span>();</span><br><span class="line"> ImGuiIO &io = ImGui::<span class="built_in">GetIO</span>();</span><br><span class="line"> io.ConfigFlags |=</span><br><span class="line"> ImGuiConfigFlags_NavEnableKeyboard; <span class="comment">// Enable Keyboard Controls</span></span><br><span class="line"> io.ConfigFlags |=</span><br><span class="line"> ImGuiConfigFlags_NavEnableGamepad; <span class="comment">// Enable Gamepad Controls</span></span><br><span class="line"> <span class="comment">// io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; // IF using Docking</span></span><br><span class="line"> <span class="comment">// Branch</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">// Setup Platform/Renderer backends</span></span><br><span class="line"> <span class="built_in">ImGui_ImplGlfw_InitForOpenGL</span>(</span><br><span class="line"> window, <span class="literal">true</span>); <span class="comment">// Second param install_callback=true will install</span></span><br><span class="line"> <span class="comment">// GLFW callbacks and chain to existing ones.</span></span><br><span class="line"> <span class="built_in">ImGui_ImplOpenGL3_Init</span>();</span><br><span class="line"> <span class="keyword">bool</span> show_demo_window{<span class="literal">true</span>};</span><br><span class="line"> <span class="keyword">bool</span> show_another_window{<span class="literal">false</span>};</span><br><span class="line"> ImVec4 clear_color = <span class="built_in">ImVec4</span>(<span class="number">0.45f</span>, <span class="number">0.55f</span>, <span class="number">0.60f</span>, <span class="number">1.00f</span>);</span><br><span class="line"> <span class="comment">// (Your code calls glfwPollEvents())</span></span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"> <span class="comment">// Start the Dear ImGui frame</span></span><br><span class="line"> <span class="keyword">while</span> (!<span class="built_in">glfwWindowShouldClose</span>(window)) {</span><br><span class="line"> <span class="comment">// Poll and handle events (inputs, window resize, etc.)</span></span><br><span class="line"> <span class="comment">// You can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to</span></span><br><span class="line"> <span class="comment">// tell if dear imgui wants to use your inputs.</span></span><br><span class="line"> <span class="comment">// - When io.WantCaptureMouse is true, do not dispatch mouse input data to</span></span><br><span class="line"> <span class="comment">// your main application, or clear/overwrite your copy of the mouse data.</span></span><br><span class="line"> <span class="comment">// - When io.WantCaptureKeyboard is true, do not dispatch keyboard input</span></span><br><span class="line"> <span class="comment">// data to your main application, or clear/overwrite your copy of the</span></span><br><span class="line"> <span class="comment">// keyboard data. Generally you may always pass all inputs to dear imgui,</span></span><br><span class="line"> <span class="comment">// and hide them from your application based on those two flags.</span></span><br><span class="line"> <span class="built_in">glfwPollEvents</span>();</span><br><span class="line"> <span class="keyword">if</span> (<span class="built_in">glfwGetWindowAttrib</span>(window, GLFW_ICONIFIED) != <span class="number">0</span>) {</span><br><span class="line"> <span class="built_in">ImGui_ImplGlfw_Sleep</span>(<span class="number">10</span>);</span><br><span class="line"> <span class="keyword">continue</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// at the begining of the frame</span></span><br><span class="line"> <span class="built_in">ImGui_ImplOpenGL3_NewFrame</span>();</span><br><span class="line"> <span class="built_in">ImGui_ImplGlfw_NewFrame</span>();</span><br><span class="line"> ImGui::<span class="built_in">NewFrame</span>();</span><br><span class="line"> <span class="comment">// ImGui::ShowDemoWindow(); // Show demo window! :)</span></span><br><span class="line"> <span class="keyword">static</span> <span class="keyword">float</span> f = <span class="number">.0</span>f;</span><br><span class="line"> <span class="keyword">static</span> <span class="keyword">int</span> counter = <span class="number">0</span>;</span><br><span class="line"> ImGui::<span class="built_in">Begin</span>(<span class="string">"Hello, world!"</span>);</span><br><span class="line"> ImGui::<span class="built_in">Text</span>(<span class="string">"This is some useful text."</span>);</span><br><span class="line"> ImGui::<span class="built_in">Checkbox</span>(<span class="string">"Demo Window"</span>, &show_demo_window);</span><br><span class="line"> ImGui::<span class="built_in">Checkbox</span>(<span class="string">"Another Window"</span>, &show_another_window);</span><br><span class="line"> ImGui::<span class="built_in">SliderFloat</span>(<span class="string">"float"</span>, &f, <span class="number">.0</span>f, <span class="number">1.f</span>);</span><br><span class="line"> ImGui::<span class="built_in">ColorEdit3</span>(<span class="string">"clear color"</span>, (<span class="keyword">float</span> *)&clear_color);</span><br><span class="line"> <span class="keyword">if</span> (ImGui::<span class="built_in">Button</span>(<span class="string">"Button"</span>)) {</span><br><span class="line"> counter++;</span><br><span class="line"> }</span><br><span class="line"> ImGui::<span class="built_in">SameLine</span>();</span><br><span class="line"> ImGui::<span class="built_in">Text</span>(<span class="string">"counter = %d"</span>, counter);</span><br><span class="line"></span><br><span class="line"> ImGui::<span class="built_in">Text</span>(<span class="string">"Application average %.3f ms/frame (%.1f PFS)"</span>,</span><br><span class="line"> <span class="number">1000.0f</span> / io.Framerate, io.Framerate);</span><br><span class="line"> ImGui::<span class="built_in">End</span>();</span><br><span class="line"> <span class="keyword">if</span> (show_another_window) {</span><br><span class="line"> ImGui::<span class="built_in">Begin</span>(</span><br><span class="line"> <span class="string">"Another Window"</span>,</span><br><span class="line"> &show_another_window); <span class="comment">// Pass a pointer to our bool variable (the</span></span><br><span class="line"> <span class="comment">// window will have a closing button that will</span></span><br><span class="line"> <span class="comment">// clear the bool when clicked)</span></span><br><span class="line"> ImGui::<span class="built_in">Text</span>(<span class="string">"Hello from another window!"</span>);</span><br><span class="line"> <span class="keyword">if</span> (ImGui::<span class="built_in">Button</span>(<span class="string">"Close Me"</span>))</span><br><span class="line"> show_another_window = <span class="literal">false</span>;</span><br><span class="line"> ImGui::<span class="built_in">End</span>();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">//// Rendering</span></span><br><span class="line"> <span class="comment">// (Your code clears your framebuffer, renders your other stuff etc.)</span></span><br><span class="line"> ImGui::<span class="built_in">Render</span>();</span><br><span class="line"> <span class="keyword">int</span> display_w, display_h;</span><br><span class="line"> <span class="built_in">glfwGetFramebufferSize</span>(window, &display_w, &display_h);</span><br><span class="line"> <span class="built_in">glViewport</span>(<span class="number">0</span>, <span class="number">0</span>, display_w, display_h);</span><br><span class="line"> <span class="built_in">glClearColor</span>(clear_color.x, clear_color.y, clear_color.z, clear_color.w);</span><br><span class="line"> <span class="built_in">glClear</span>(GL_COLOR_BUFFER_BIT);</span><br><span class="line"> <span class="built_in">ImGui_ImplOpenGL3_RenderDrawData</span>(ImGui::<span class="built_in">GetDrawData</span>());</span><br><span class="line"> <span class="built_in">glfwSwapBuffers</span>(window);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// Cleanup</span></span><br><span class="line"> <span class="built_in">ImGui_ImplOpenGL3_Shutdown</span>();</span><br><span class="line"> <span class="built_in">ImGui_ImplGlfw_Shutdown</span>();</span><br><span class="line"> ImGui::<span class="built_in">DestroyContext</span>();</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>Rust也有类似作用的egui<a href="https://github.com/emilk/egui">emilk/egui: egui: an easy-to-use immediate mode GUI in Rust that runs on both web and native (github.com)</a></p><p><img data-src="https://s2.loli.net/2024/09/30/gymonshOAW9r1BP.png" alt="image-20240930172646741"></p><h3 id="raylib"><a href="#raylib" class="headerlink" title="raylib"></a>raylib</h3><p><a href="https://www.raylib.com/index.html">raylib | A simple and easy-to-use library to enjoy videogames programming</a></p><p>看到有人推荐没过几天我就看到很多使用它开发的小东西. 官网说主要用于vediogames的开发(跟使用imgui的目的类似). </p><p>个人使用体验比imgui更舒服,因为实际上使用imgui需要调用opengl/vulkan的接口,这些接口命名太<del>傻逼</del>了而且使用更繁琐.</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><raylib.h></span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><iostream></span></span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span> </span>{</span><br><span class="line"> std::cout << <span class="string">"Hello, World!"</span> << std::endl;</span><br><span class="line"> <span class="keyword">const</span> <span class="keyword">int</span> screenWidth = <span class="number">800</span>;</span><br><span class="line"> <span class="keyword">const</span> <span class="keyword">int</span> screenHeight = <span class="number">450</span>;</span><br><span class="line"> <span class="built_in">InitWindow</span>(screenWidth, screenHeight, <span class="string">"raylib [core] example - basic window"</span>);</span><br><span class="line"> <span class="built_in">SetTargetFPS</span>(<span class="number">60</span>);</span><br><span class="line"> <span class="keyword">while</span> (!<span class="built_in">WindowShouldClose</span>()) {</span><br><span class="line"> <span class="built_in">BeginDrawing</span>();</span><br><span class="line"></span><br><span class="line"> <span class="built_in">ClearBackground</span>(RAYWHITE);</span><br><span class="line"> <span class="built_in">DrawText</span>(<span class="string">"Congrats! You created your first window!"</span>, <span class="number">190</span>, <span class="number">200</span>, <span class="number">20</span>,</span><br><span class="line"> LIGHTGRAY);</span><br><span class="line"> <span class="built_in">EndDrawing</span>();</span><br><span class="line"> }</span><br><span class="line"> <span class="built_in">CloseWindow</span>();</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br><span class="line"></span><br></pre></td></tr></table></figure><h2 id="更加高级的跨平台应用开发"><a href="#更加高级的跨平台应用开发" class="headerlink" title="更加高级的跨平台应用开发"></a>更加高级的跨平台应用开发</h2><h3 id="SDL"><a href="#SDL" class="headerlink" title="SDL"></a>SDL</h3><p><a href="https://www.libsdl.org/">Simple DirectMedia Layer - Homepage (libsdl.org)</a></p><p><a href="https://wiki.libsdl.org/SDL2/Tutorials">SDL2/Tutorials - SDL Wiki (libsdl.org)</a></p><p>SDL本身是跨平台的多媒体库,使用c++与音视频设备交互. 目前最新是SDL3.0版本了,本身使用c编写,也经常用来写小游戏.</p><p>SDL有对应的网络库<a href="https://github.com/libsdl-org/SDL_net">libsdl-org/SDL_net: A simple, cross-platform wrapper over TCP/IP sockets. (github.com)</a></p><h3 id="SFML"><a href="#SFML" class="headerlink" title="SFML"></a>SFML</h3><p><a href="https://www.sfml-dev.org/">SFML (sfml-dev.org)</a></p><p>与SDL类似,但个人觉得API更简洁更易于使用</p><p>关于两者的讨论<a href="https://www.reddit.com/r/gamedev/comments/n8d6an/sfml_vs_sdl2_which_one_should_i_choose/">sfml vs sdl2 .. which one should i choose? : r/gamedev (reddit.com)</a></p><p>这两者都不止是简单的图形接口了,提供音频和网络模块方便使用</p><p><img data-src="https://s2.loli.net/2024/09/30/HlfgAV9Z2exv8Gb.png" alt="image-20240930174112035"></p><h2 id="功能强大的框架"><a href="#功能强大的框架" class="headerlink" title="功能强大的框架"></a>功能强大的框架</h2><h3 id="WinUI3-期待"><a href="#WinUI3-期待" class="headerlink" title="WinUI3 期待"></a>WinUI3 期待</h3><p>借助微软的力量<a href="https://learn.microsoft.com/zh-cn/windows/apps/winui/winui3/">WinUI 3 - Windows apps | Microsoft Learn</a></p><p>使用WinUI开发windows应用,缺点是不能跨平台,并且微软已经有了很多桌面开发框架了,WPF,MAUI,UWP?等等,不知道这个框架能够一直积极维持多久. </p><h3 id="Qt-不只是UI"><a href="#Qt-不只是UI" class="headerlink" title="Qt 不只是UI"></a>Qt 不只是UI</h3><p>Qt是跨平台桌面?(目前Qt也支持了安卓开发)开发工具.目前已经到了6.0+,除了使用widget之外,针对移动端的Qt quick也发展得不错. 除了UI之外,它还有网络库,图形渲染等常用的库. 甚至可以说Qt的一些库都能拿来代替c++贫瘠的标准库了</p><p>Qt不仅可以进行桌面应用开发,也可以拿来开发游戏.</p><h2 id="至尊C-游戏开发"><a href="#至尊C-游戏开发" class="headerlink" title="至尊C++游戏开发"></a>至尊C++游戏开发</h2><p><a href="https://www.unrealengine.com/zh-CN">最强大的实时3D创作工具 - Unreal Engine</a></p><h2 id="实战raylib"><a href="#实战raylib" class="headerlink" title="实战raylib"></a>实战raylib</h2><p>个人使用上感觉raylib->Qt->UE从简易到复杂,功能也越来越多. 而imgui还是功能更少. 下面就介绍一下raylib写点小东西学习学习.</p><ul><li><a href="https://www.raylib.com/cheatsheet/cheatsheet_zh.html">raylib - cheatsheet</a></li><li><a href="https://www.raylib.com/examples.html">raylib - examples</a></li></ul><p><img data-src="https://s2.loli.net/2024/09/30/9zaylEkxOSm1eG2.png" alt="image-20240930203756619"></p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"raylib.h"</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">//------------------------------------------------------------------------------------------</span></span><br><span class="line"><span class="comment">// Types and Structures Definition</span></span><br><span class="line"><span class="comment">//------------------------------------------------------------------------------------------</span></span><br><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">enum</span> <span class="title">GameScreen</span> {</span> LOGO = <span class="number">0</span>, TITLE, GAMEPLAY, ENDING } GameScreen;</span><br><span class="line"></span><br><span class="line"><span class="comment">//------------------------------------------------------------------------------------</span></span><br><span class="line"><span class="comment">// Program main entry point</span></span><br><span class="line"><span class="comment">//------------------------------------------------------------------------------------</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">(<span class="keyword">void</span>)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="comment">// Initialization</span></span><br><span class="line"> <span class="comment">//--------------------------------------------------------------------------------------</span></span><br><span class="line"> <span class="keyword">const</span> <span class="keyword">int</span> screenWidth = <span class="number">800</span>;</span><br><span class="line"> <span class="keyword">const</span> <span class="keyword">int</span> screenHeight = <span class="number">450</span>;</span><br><span class="line"></span><br><span class="line"> <span class="built_in">InitWindow</span>(screenWidth, screenHeight, <span class="string">"raylib [core] example - basic screen manager"</span>);</span><br><span class="line"></span><br><span class="line"> GameScreen currentScreen = LOGO;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// <span class="doctag">TODO:</span> Initialize all required variables and load all required data here!</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">int</span> framesCounter = <span class="number">0</span>; <span class="comment">// Useful to count frames</span></span><br><span class="line"></span><br><span class="line"> <span class="built_in">SetTargetFPS</span>(<span class="number">60</span>); <span class="comment">// Set desired framerate (frames-per-second)</span></span><br><span class="line"> <span class="comment">//--------------------------------------------------------------------------------------</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">// Main game loop</span></span><br><span class="line"> <span class="keyword">while</span> (!<span class="built_in">WindowShouldClose</span>()) <span class="comment">// Detect window close button or ESC key</span></span><br><span class="line"> {</span><br><span class="line"> <span class="comment">// Update</span></span><br><span class="line"> <span class="comment">//----------------------------------------------------------------------------------</span></span><br><span class="line"> <span class="built_in"><span class="keyword">switch</span></span>(currentScreen)</span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">case</span> LOGO:</span><br><span class="line"> {</span><br><span class="line"> <span class="comment">// <span class="doctag">TODO:</span> Update LOGO screen variables here!</span></span><br><span class="line"></span><br><span class="line"> framesCounter++; <span class="comment">// Count frames</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">// Wait for 2 seconds (120 frames) before jumping to TITLE screen</span></span><br><span class="line"> <span class="keyword">if</span> (framesCounter > <span class="number">120</span>)</span><br><span class="line"> {</span><br><span class="line"> currentScreen = TITLE;</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">break</span>;</span><br><span class="line"> <span class="keyword">case</span> TITLE:</span><br><span class="line"> {</span><br><span class="line"> <span class="comment">// <span class="doctag">TODO:</span> Update TITLE screen variables here!</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">// Press enter to change to GAMEPLAY screen</span></span><br><span class="line"> <span class="keyword">if</span> (<span class="built_in">IsKeyPressed</span>(KEY_ENTER) || <span class="built_in">IsGestureDetected</span>(GESTURE_TAP))</span><br><span class="line"> {</span><br><span class="line"> currentScreen = GAMEPLAY;</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">break</span>;</span><br><span class="line"> <span class="keyword">case</span> GAMEPLAY:</span><br><span class="line"> {</span><br><span class="line"> <span class="comment">// <span class="doctag">TODO:</span> Update GAMEPLAY screen variables here!</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">// Press enter to change to ENDING screen</span></span><br><span class="line"> <span class="keyword">if</span> (<span class="built_in">IsKeyPressed</span>(KEY_ENTER) || <span class="built_in">IsGestureDetected</span>(GESTURE_TAP))</span><br><span class="line"> {</span><br><span class="line"> currentScreen = ENDING;</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">break</span>;</span><br><span class="line"> <span class="keyword">case</span> ENDING:</span><br><span class="line"> {</span><br><span class="line"> <span class="comment">// <span class="doctag">TODO:</span> Update ENDING screen variables here!</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">// Press enter to return to TITLE screen</span></span><br><span class="line"> <span class="keyword">if</span> (<span class="built_in">IsKeyPressed</span>(KEY_ENTER) || <span class="built_in">IsGestureDetected</span>(GESTURE_TAP))</span><br><span class="line"> {</span><br><span class="line"> currentScreen = TITLE;</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">break</span>;</span><br><span class="line"> <span class="keyword">default</span>: <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">//----------------------------------------------------------------------------------</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">// Draw</span></span><br><span class="line"> <span class="comment">//----------------------------------------------------------------------------------</span></span><br><span class="line"> <span class="built_in">BeginDrawing</span>();</span><br><span class="line"></span><br><span class="line"> <span class="built_in">ClearBackground</span>(RAYWHITE);</span><br><span class="line"></span><br><span class="line"> <span class="built_in"><span class="keyword">switch</span></span>(currentScreen)</span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">case</span> LOGO:</span><br><span class="line"> {</span><br><span class="line"> <span class="comment">// <span class="doctag">TODO:</span> Draw LOGO screen here!</span></span><br><span class="line"> <span class="built_in">DrawText</span>(<span class="string">"LOGO SCREEN"</span>, <span class="number">20</span>, <span class="number">20</span>, <span class="number">40</span>, LIGHTGRAY);</span><br><span class="line"> <span class="built_in">DrawText</span>(<span class="string">"WAIT for 2 SECONDS..."</span>, <span class="number">290</span>, <span class="number">220</span>, <span class="number">20</span>, GRAY);</span><br><span class="line"></span><br><span class="line"> } <span class="keyword">break</span>;</span><br><span class="line"> <span class="keyword">case</span> TITLE:</span><br><span class="line"> {</span><br><span class="line"> <span class="comment">// <span class="doctag">TODO:</span> Draw TITLE screen here!</span></span><br><span class="line"> <span class="built_in">DrawRectangle</span>(<span class="number">0</span>, <span class="number">0</span>, screenWidth, screenHeight, GREEN);</span><br><span class="line"> <span class="built_in">DrawText</span>(<span class="string">"TITLE SCREEN"</span>, <span class="number">20</span>, <span class="number">20</span>, <span class="number">40</span>, DARKGREEN);</span><br><span class="line"> <span class="built_in">DrawText</span>(<span class="string">"PRESS ENTER or TAP to JUMP to GAMEPLAY SCREEN"</span>, <span class="number">120</span>, <span class="number">220</span>, <span class="number">20</span>, DARKGREEN);</span><br><span class="line"></span><br><span class="line"> } <span class="keyword">break</span>;</span><br><span class="line"> <span class="keyword">case</span> GAMEPLAY:</span><br><span class="line"> {</span><br><span class="line"> <span class="comment">// <span class="doctag">TODO:</span> Draw GAMEPLAY screen here!</span></span><br><span class="line"> <span class="built_in">DrawRectangle</span>(<span class="number">0</span>, <span class="number">0</span>, screenWidth, screenHeight, PURPLE);</span><br><span class="line"> <span class="built_in">DrawText</span>(<span class="string">"GAMEPLAY SCREEN"</span>, <span class="number">20</span>, <span class="number">20</span>, <span class="number">40</span>, MAROON);</span><br><span class="line"> <span class="built_in">DrawText</span>(<span class="string">"PRESS ENTER or TAP to JUMP to ENDING SCREEN"</span>, <span class="number">130</span>, <span class="number">220</span>, <span class="number">20</span>, MAROON);</span><br><span class="line"></span><br><span class="line"> } <span class="keyword">break</span>;</span><br><span class="line"> <span class="keyword">case</span> ENDING:</span><br><span class="line"> {</span><br><span class="line"> <span class="comment">// <span class="doctag">TODO:</span> Draw ENDING screen here!</span></span><br><span class="line"> <span class="built_in">DrawRectangle</span>(<span class="number">0</span>, <span class="number">0</span>, screenWidth, screenHeight, BLUE);</span><br><span class="line"> <span class="built_in">DrawText</span>(<span class="string">"ENDING SCREEN"</span>, <span class="number">20</span>, <span class="number">20</span>, <span class="number">40</span>, DARKBLUE);</span><br><span class="line"> <span class="built_in">DrawText</span>(<span class="string">"PRESS ENTER or TAP to RETURN to TITLE SCREEN"</span>, <span class="number">120</span>, <span class="number">220</span>, <span class="number">20</span>, DARKBLUE);</span><br><span class="line"></span><br><span class="line"> } <span class="keyword">break</span>;</span><br><span class="line"> <span class="keyword">default</span>: <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="built_in">EndDrawing</span>();</span><br><span class="line"> <span class="comment">//----------------------------------------------------------------------------------</span></span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// De-Initialization</span></span><br><span class="line"> <span class="comment">//--------------------------------------------------------------------------------------</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">// <span class="doctag">TODO:</span> Unload all loaded data (textures, fonts, audio) here!</span></span><br><span class="line"></span><br><span class="line"> <span class="built_in">CloseWindow</span>(); <span class="comment">// Close window and OpenGL context</span></span><br><span class="line"> <span class="comment">//--------------------------------------------------------------------------------------</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br><span class="line">raylib example source code</span><br></pre></td></tr></table></figure><link rel="stylesheet" href="/css/spoiler.css" type="text/css"><script src="/js/spoiler.js" type="text/javascript" async></script>]]></content>
<summary type="html"><p>学习c++与其他语言一个不同之处就是, 你可能不能很快地构建一些有趣的项目. </p>
<p>这时你可能会转向Rust作为代餐,享受安全系统编程语言带来的性能,同时也有一些高级语言所有的强大标准库.或者你也有可能转向Java,C#等更加具有高级应用生态的语言,利用自带的标准库以及强大的三方生态,快速构建性能并不算差的应用. 这里,我简单介绍一下可以使用c++的框架/库以便play for fun.<br></summary>
<category term="cpp" scheme="https://www.sekyoro.top/tags/cpp/"/>
<category term="fun" scheme="https://www.sekyoro.top/tags/fun/"/>
</entry>
<entry>
<title>脑子没有坏掉系列:使用Rust写Web</title>
<link href="https://www.sekyoro.top/2024/09/29/%E8%84%91%E5%AD%90%E6%B2%A1%E6%9C%89%E5%9D%8F%E6%8E%89%E7%B3%BB%E5%88%97-%E4%BD%BF%E7%94%A8Rust%E5%86%99Web/"/>
<id>https://www.sekyoro.top/2024/09/29/%E8%84%91%E5%AD%90%E6%B2%A1%E6%9C%89%E5%9D%8F%E6%8E%89%E7%B3%BB%E5%88%97-%E4%BD%BF%E7%94%A8Rust%E5%86%99Web/</id>
<published>2024-09-29T12:46:44.000Z</published>
<updated>2024-09-30T09:26:04.135Z</updated>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="\assets\js\Meting.min.js"></script><p>使用rust写web框架的目的: </p><p>1)学习rust语法等知识. 找个感兴趣的项目写写</p><p>2)写写web程序练练手感.<br><span id="more"></span><br>我这里使用的是<a href="https://rocket.rs/">Rocket - Simple, Fast, Type-Safe Web Framework for Rust</a></p><p><img data-src="https://s2.loli.net/2024/09/29/YBv5JnyopKIZDzC.png" alt="image-20240929205536867"></p><h2 id="生命周期"><a href="#生命周期" class="headerlink" title="生命周期"></a>生命周期</h2><ol><li>路由</li></ol><p>Rocket 会将传入的 HTTP 请求解析为本地结构,供你的代码间接操作。Rocket 通过与应用程序中声明的路由属性进行匹配,确定要调用的请求处理程序</p><ol><li>验证</li></ol><p>Rocket根据匹配路由中存在的类型和守卫验证传入请求。如果验证失败,Rocket将请求转发到下一个匹配路由或调用错误处理程序。</p><ol><li>处理</li></ol><p>使用经过验证的参数调用与路由关联的请求处理程序。这是应用程序的主要业务逻辑。处理通过返回一个响应完成。</p><ol><li>相应</li></ol><p>处理返回的响应。Rocket生成适当的HTTP响应并将其发送到客户机。这就完成了生命周期。Rocket继续侦听请求,重新启动每个传入请求的生命周期。</p><h2 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h2><ol><li><a href="https://github.com/flosse/rust-web-framework-comparison?tab=readme-ov-file">flosse/rust-web-framework-comparison: A comparison of some web frameworks and libs written in Rust (github.com)</a></li><li><a href="https://course.rs/practice/third-party-libs.html">日常开发三方库精选 - Rust语言圣经(Rust Course)</a></li></ol><link rel="stylesheet" href="/css/spoiler.css" type="text/css"><script src="/js/spoiler.js" type="text/javascript" async></script>]]></content>
<summary type="html"><p>使用rust写web框架的目的: </p>
<p>1)学习rust语法等知识. 找个感兴趣的项目写写</p>
<p>2)写写web程序练练手感.<br></summary>
<category term="web" scheme="https://www.sekyoro.top/tags/web/"/>
<category term="rust" scheme="https://www.sekyoro.top/tags/rust/"/>
</entry>
</feed>