├── .gitattributes
├── .gitignore
├── LICENSE
└── README.md
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
4 | # Custom for Visual Studio
5 | *.cs diff=csharp
6 |
7 | # Standard to msysgit
8 | *.doc diff=astextplain
9 | *.DOC diff=astextplain
10 | *.docx diff=astextplain
11 | *.DOCX diff=astextplain
12 | *.dot diff=astextplain
13 | *.DOT diff=astextplain
14 | *.pdf diff=astextplain
15 | *.PDF diff=astextplain
16 | *.rtf diff=astextplain
17 | *.RTF diff=astextplain
18 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Windows image file caches
2 | Thumbs.db
3 | ehthumbs.db
4 |
5 | # Folder config file
6 | Desktop.ini
7 |
8 | # Recycle Bin used on file shares
9 | $RECYCLE.BIN/
10 |
11 | # Windows Installer files
12 | *.cab
13 | *.msi
14 | *.msm
15 | *.msp
16 |
17 | # Windows shortcuts
18 | *.lnk
19 |
20 | # =========================
21 | # Operating System Files
22 | # =========================
23 |
24 | # OSX
25 | # =========================
26 |
27 | .DS_Store
28 | .AppleDouble
29 | .LSOverride
30 |
31 | # Thumbnails
32 | ._*
33 |
34 | # Files that might appear in the root of a volume
35 | .DocumentRevisions-V100
36 | .fseventsd
37 | .Spotlight-V100
38 | .TemporaryItems
39 | .Trashes
40 | .VolumeIcon.icns
41 |
42 | # Directories potentially created on remote AFP share
43 | .AppleDB
44 | .AppleDesktop
45 | Network Trash Folder
46 | Temporary Items
47 | .apdisk
48 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | CC0 1.0 Universal
2 |
3 | Statement of Purpose
4 |
5 | The laws of most jurisdictions throughout the world automatically confer
6 | exclusive Copyright and Related Rights (defined below) upon the creator and
7 | subsequent owner(s) (each and all, an "owner") of an original work of
8 | authorship and/or a database (each, a "Work").
9 |
10 | Certain owners wish to permanently relinquish those rights to a Work for the
11 | purpose of contributing to a commons of creative, cultural and scientific
12 | works ("Commons") that the public can reliably and without fear of later
13 | claims of infringement build upon, modify, incorporate in other works, reuse
14 | and redistribute as freely as possible in any form whatsoever and for any
15 | purposes, including without limitation commercial purposes. These owners may
16 | contribute to the Commons to promote the ideal of a free culture and the
17 | further production of creative, cultural and scientific works, or to gain
18 | reputation or greater distribution for their Work in part through the use and
19 | efforts of others.
20 |
21 | For these and/or other purposes and motivations, and without any expectation
22 | of additional consideration or compensation, the person associating CC0 with a
23 | Work (the "Affirmer"), to the extent that he or she is an owner of Copyright
24 | and Related Rights in the Work, voluntarily elects to apply CC0 to the Work
25 | and publicly distribute the Work under its terms, with knowledge of his or her
26 | Copyright and Related Rights in the Work and the meaning and intended legal
27 | effect of CC0 on those rights.
28 |
29 | 1. Copyright and Related Rights. A Work made available under CC0 may be
30 | protected by copyright and related or neighboring rights ("Copyright and
31 | Related Rights"). Copyright and Related Rights include, but are not limited
32 | to, the following:
33 |
34 | i. the right to reproduce, adapt, distribute, perform, display, communicate,
35 | and translate a Work;
36 |
37 | ii. moral rights retained by the original author(s) and/or performer(s);
38 |
39 | iii. publicity and privacy rights pertaining to a person's image or likeness
40 | depicted in a Work;
41 |
42 | iv. rights protecting against unfair competition in regards to a Work,
43 | subject to the limitations in paragraph 4(a), below;
44 |
45 | v. rights protecting the extraction, dissemination, use and reuse of data in
46 | a Work;
47 |
48 | vi. database rights (such as those arising under Directive 96/9/EC of the
49 | European Parliament and of the Council of 11 March 1996 on the legal
50 | protection of databases, and under any national implementation thereof,
51 | including any amended or successor version of such directive); and
52 |
53 | vii. other similar, equivalent or corresponding rights throughout the world
54 | based on applicable law or treaty, and any national implementations thereof.
55 |
56 | 2. Waiver. To the greatest extent permitted by, but not in contravention of,
57 | applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and
58 | unconditionally waives, abandons, and surrenders all of Affirmer's Copyright
59 | and Related Rights and associated claims and causes of action, whether now
60 | known or unknown (including existing as well as future claims and causes of
61 | action), in the Work (i) in all territories worldwide, (ii) for the maximum
62 | duration provided by applicable law or treaty (including future time
63 | extensions), (iii) in any current or future medium and for any number of
64 | copies, and (iv) for any purpose whatsoever, including without limitation
65 | commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes
66 | the Waiver for the benefit of each member of the public at large and to the
67 | detriment of Affirmer's heirs and successors, fully intending that such Waiver
68 | shall not be subject to revocation, rescission, cancellation, termination, or
69 | any other legal or equitable action to disrupt the quiet enjoyment of the Work
70 | by the public as contemplated by Affirmer's express Statement of Purpose.
71 |
72 | 3. Public License Fallback. Should any part of the Waiver for any reason be
73 | judged legally invalid or ineffective under applicable law, then the Waiver
74 | shall be preserved to the maximum extent permitted taking into account
75 | Affirmer's express Statement of Purpose. In addition, to the extent the Waiver
76 | is so judged Affirmer hereby grants to each affected person a royalty-free,
77 | non transferable, non sublicensable, non exclusive, irrevocable and
78 | unconditional license to exercise Affirmer's Copyright and Related Rights in
79 | the Work (i) in all territories worldwide, (ii) for the maximum duration
80 | provided by applicable law or treaty (including future time extensions), (iii)
81 | in any current or future medium and for any number of copies, and (iv) for any
82 | purpose whatsoever, including without limitation commercial, advertising or
83 | promotional purposes (the "License"). The License shall be deemed effective as
84 | of the date CC0 was applied by Affirmer to the Work. Should any part of the
85 | License for any reason be judged legally invalid or ineffective under
86 | applicable law, such partial invalidity or ineffectiveness shall not
87 | invalidate the remainder of the License, and in such case Affirmer hereby
88 | affirms that he or she will not (i) exercise any of his or her remaining
89 | Copyright and Related Rights in the Work or (ii) assert any associated claims
90 | and causes of action with respect to the Work, in either case contrary to
91 | Affirmer's express Statement of Purpose.
92 |
93 | 4. Limitations and Disclaimers.
94 |
95 | a. No trademark or patent rights held by Affirmer are waived, abandoned,
96 | surrendered, licensed or otherwise affected by this document.
97 |
98 | b. Affirmer offers the Work as-is and makes no representations or warranties
99 | of any kind concerning the Work, express, implied, statutory or otherwise,
100 | including without limitation warranties of title, merchantability, fitness
101 | for a particular purpose, non infringement, or the absence of latent or
102 | other defects, accuracy, or the present or absence of errors, whether or not
103 | discoverable, all to the greatest extent permissible under applicable law.
104 |
105 | c. Affirmer disclaims responsibility for clearing rights of other persons
106 | that may apply to the Work or any use thereof, including without limitation
107 | any person's Copyright and Related Rights in the Work. Further, Affirmer
108 | disclaims responsibility for obtaining any necessary consents, permissions
109 | or other rights required for any use of the Work.
110 |
111 | d. Affirmer understands and acknowledges that Creative Commons is not a
112 | party to this document and has no duty or obligation with respect to this
113 | CC0 or use of the Work.
114 |
115 | For more information, please see
116 |
117 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # A Guide to Modern OpenGL Functions
2 |
3 | ## Index
4 |
5 | * [DSA](#dsa-direct-state-access)
6 | * [glTexture](#gltexture)
7 | * [glCreateTextures](#glcreatetextures)
8 | * [glTextureParameter](#gltextureparameter)
9 | * [glTextureStorage](#gltexturestorage)
10 | * [glBindTextureUnit](#glbindtextureunit)
11 | * [Generating Mip Maps](#generating-mip-maps)
12 | * [Uploading Cube Maps](#uploading-cube-maps)
13 | * [Where is glTextureImage?](#where-is-gltextureimage)
14 |
15 | * [glFramebuffer](#glframebuffer)
16 | * [glCreateFramebuffers](#glcreateframebuffers)
17 | * [glBlitNamedFramebuffer](#glblitnamedframebuffer)
18 | * [glClearNamedFramebuffer](#glclearnamedframebuffer)
19 |
20 | * [glBuffer](#glbuffer)
21 | * [glCreateBuffers](#glcreatebuffers)
22 | * [glNamedBufferData](#glnamedbufferdata)
23 | * [glVertexAttribFormat & glBindVertexBuffer](#glvertexattribformat--glbindvertexbuffer)
24 |
25 | * [Detailed Messages with Debug Output](#detailed-messages-with-debug-output)
26 | * [Storing Index and Vertex Data Under Single Buffer](#storing-index-and-vertex-data-under-single-buffer)
27 | * [Ideal Way Of Retrieving All Uniform Names](#ideal-way-of-retrieving-all-uniform-names)
28 | * [Texture Atlases vs Arrays](#texture-atlases-vs-arrays)
29 | * [Texture Views & Aliases](#texture-views--aliases)
30 | * [Setting up Mix & Match Shaders with Program Pipelines](#setting-up-mix--match-shaders-with-program-pipelines)
31 | * [Faster Reads and Writes with Persistent Mapping](#faster-reads-and-writes-with-persistent-mapping)
32 | * [More Information](#more-information)
33 |
34 | What this is:
35 |
36 | * A guide on how to apply modern OpenGL functionality.
37 |
38 | What this is not:
39 |
40 | * A guide on modern OpenGL rendering techniques.
41 |
42 | When I say modern I'm talking DSA modern, not VAO modern, because that's old modern or "middle" GL (however I will be covering some from it), I can't tell you what minimal version you need to make use of DSA because it's not clear at all but you can check if you support it yourself with something like glew's `glewIsSupported("ARB_direct_state_access")` or checking your API version.
43 |
44 | ## DSA (Direct State Access)
45 | With DSA we, in theory, can keep our bind count outside of drawing operations at zero. Great right? Sure, but if you were to research how to use all the new DSA functions you'd have a hard time finding anywhere where it's all explained, which is what this guide is all about.
46 |
47 | ###### DSA Naming Convention
48 |
49 | The [wiki page](https://www.opengl.org/wiki/Direct_State_Access) does a fine job comparing the DSA naming convention to the traditional one so I stole their table:
50 |
51 | | OpenGL Object Type | Context Object Name | DSA Object Name |
52 | | :------------- |:-------------| :-----|
53 | | [Texture Object](https://www.opengl.org/wiki/Texture) | Tex | Texture |
54 | | [Framebuffer Object](https://www.opengl.org/wiki/Framebuffer_Object) | Framebuffer | NamedFramebuffer |
55 | | [Buffer Object](https://www.opengl.org/wiki/Buffer_Object) | Buffer | NamedBuffer |
56 | | [Transform Feedback Object](https://www.opengl.org/wiki/Transform_Feedback_Object) | TransformFeedback | TransformFeedback |
57 | | [Vertex Array Object](https://www.opengl.org/wiki/Vertex_Array_Object) | N/A | VertexArray |
58 | | [Sampler Object](https://www.opengl.org/wiki/Sampler_Object) | N/A | Sampler |
59 | | [Query Object](https://www.opengl.org/wiki/Query_Object) | N/A | Query |
60 | | [Program Object](https://www.opengl.org/wiki/Program_Object) | N/A | Program |
61 |
62 | ### glTexture
63 | ------
64 | * The texture related calls aren't hard to figure out so let's jump right in.
65 |
66 | ###### glCreateTextures
67 | * [`glCreateTextures`](http://docs.gl/gl4/glCreateTextures) is the equivalent of [`glGenTextures`](http://docs.gl/gl4/glGenTextures) + [`glBindTexture`](http://docs.gl/gl4/glBindTexture)(for initialization).
68 |
69 | ```c
70 | void glCreateTextures(GLenum target, GLsizei n, GLuint *textures);
71 | ```
72 |
73 | Unlike [`glGenTextures`](http://docs.gl/gl4/glGenTextures) [`glCreateTextures`](http://docs.gl/gl4/glCreateTextures) will create the handle *and* initialize the object which is why the field `GLenum target` is listed as the internal initialization depends on knowing the type.
74 |
75 | So this:
76 | ```c
77 | glGenTextures(1, &name);
78 | glBindTexture(GL_TEXTURE_2D, name);
79 | ```
80 | DSA-ified becomes:
81 | ```c
82 | glCreateTextures(GL_TEXTURE_2D, 1, &name);
83 | ```
84 |
85 | ###### glTextureParameter
86 |
87 | * [`glTextureParameter`](http://docs.gl/gl4/glTexParameter) is the equivalent of [`glTexParameterX`](http://docs.gl/gl4/glTexParameter)
88 |
89 | ```c
90 | void glTextureParameteri(GLuint texture, GLenum pname, GLenum param);
91 | ```
92 |
93 | There isn't much to say about this family of functions; they're used exactly the same but take in the texture name rather than the texture target.
94 |
95 | ```c
96 | glTextureParameteri(name, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
97 | ```
98 |
99 | ###### glTextureStorage
100 |
101 | * [`glTextureStorage`](http://docs.gl/gl4/glTexStorage2D) is semi-equivalent to [`glTexStorage`](http://docs.gl/gl4/glTexStorage2D) ([Where is glTextureImage?](#where-is-gltextureimage)).
102 |
103 | The [`glTextureStorage`](http://docs.gl/gl4/glTexStorage2D) and [`glTextureSubImage`](http://docs.gl/gl4/glTexSubImage2D) families are the same exact way.
104 |
105 | Time for the big comparison:
106 |
107 | ```c
108 | glGenTextures(1, &name);
109 | glBindTexture(GL_TEXTURE_2D, name);
110 |
111 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
112 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
113 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
114 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
115 |
116 | glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, width, height);
117 | glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
118 | ```
119 |
120 | ```c
121 | glCreateTextures(GL_TEXTURE_2D, 1, &name);
122 |
123 | glTextureParameteri(name, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
124 | glTextureParameteri(name, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
125 | glTextureParameteri(name, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
126 | glTextureParameteri(name, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
127 |
128 | glTextureStorage2D(name, 1, GL_RGBA8, width, height);
129 | glTextureSubImage2D(name, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
130 | ```
131 |
132 | ###### glBindTextureUnit
133 |
134 | * [`glBindTextureUnit`](http://docs.gl/gl4/glBindTextureUnit) is the equivalent of [`glActiveTexture`](http://docs.gl/gl4/glActiveTexture) + [`glBindTexture`](http://docs.gl/gl4/glBindTexture)
135 |
136 | Defeats the need for:
137 | ```c
138 | glActiveTexture(GL_TEXTURE0 + 3);
139 | glBindTexture(GL_TEXTURE_2D, name);
140 | ```
141 |
142 | And replaces it with a simple:
143 | ```c
144 | glBindTextureUnit(3, name);
145 | ```
146 |
147 | ###### Generating Mip Maps
148 |
149 | * [`glGenerateTextureMipmap`](http://docs.gl/gl4/glGenerateMipmap) is the equivalent of [`glGenerateMipmap`](http://docs.gl/gl4/glGenerateMipmap)
150 |
151 | Takes in the texture name instead of the texture target.
152 |
153 | ```c
154 | void glGenerateTextureMipmap(GLuint texture);
155 | ```
156 |
157 | ###### Uploading Cube Maps
158 |
159 | I should briefly point out that in order to upload cube map textures you need to use [`glTextureSubImage3D`](http://docs.gl/gl4/glTexSubImage3D).
160 |
161 | ```c
162 | glTextureStorage2D(name, 1, GL_RGBA8, bitmap.width, bitmap.height);
163 |
164 | for (size_t face = 0; face < 6; ++face)
165 | {
166 | auto const& bitmap = bitmaps[face];
167 | glTextureSubImage3D(name, 0, 0, 0, face, bitmap.width, bitmap.height, 1, bitmap.format, GL_UNSIGNED_BYTE, bitmap.pixels);
168 | }
169 | ```
170 |
171 | ###### Where is glTextureImage?
172 |
173 | If you look at the OpenGL function listing you will see a lack of `glTextureImage` and here's why:
174 |
175 | Having to build up a valid texture object piecewise as you would with `glTexImage` left plenty of room for mistakes and required the driver to do validation as late as possible, and so when it came time to specify a new set of texture functions they took the opportunity to address this rough spot. The result was [`glTexStorage`](http://docs.gl/gl4/glTexStorage2D).
176 |
177 | Storage provides a way to create complete textures with checks done on-call, which means less room for error, it solves most if not all problems brought on by mutable textures.
178 |
179 | tl;dr "Immutable textures are a more robust approach to handle textures"
180 |
181 | * However be mindful as allocating immutable textures requires physical video memory to be available upfront rather than having the driver deal with when and where the data goes, this means it's very possible to unintentionally exceed your card's capacity.
182 |
183 | Sources: [ARB_texture_storage](https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_texture_storage.txt), ["What does glTexStorage do?"](https://stackoverflow.com/questions/9224300/what-does-gltexstorage-do), ["What's the DSA version of glTexImage2D?"](https://gamedev.stackexchange.com/questions/134177/whats-the-dsa-version-of-glteximage2d)
184 |
185 | ### glFramebuffer
186 | ------
187 | ###### glCreateFramebuffers
188 |
189 | * [`glCreateFramebuffers`](http://docs.gl/gl4/glCreateFramebuffers) is the equivalent of [`glGenFramebuffers`](http://docs.gl/gl4/glGenFramebuffers)
190 |
191 | [`glCreateFramebuffers`](http://docs.gl/gl4/glCreateFramebuffers) is used exactly the same but initializes the object for you.
192 |
193 | Everything else is pretty much the same but takes in the framebuffer handle instead of the target.
194 |
195 | ```cpp
196 | glCreateFramebuffers(1, &fbo);
197 |
198 | glNamedFramebufferTexture(fbo, GL_COLOR_ATTACHMENT0, tex, 0);
199 | glNamedFramebufferTexture(fbo, GL_DEPTH_ATTACHMENT, depthTex, 0);
200 |
201 | if(glCheckNamedFramebufferStatus(fbo, GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
202 | std::cerr << "framebuffer error\n";
203 | ```
204 |
205 | ###### glBlitNamedFramebuffer
206 |
207 | * [`glBlitNamedFramebuffer`](http://docs.gl/gl4/glBlitFramebuffer) is the equivalent of [`glBlitFramebuffer`](http://docs.gl/gl4/glBlitFramebuffer)
208 |
209 | The difference here is that we no longer need to bind the two framebuffers and specify which is which through the `GL_READ_FRAMEBUFFER` and `GL_WRITE_FRAMEBUFFER` enums.
210 |
211 | ```c
212 | glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo_src);
213 | glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo_dst);
214 |
215 | glBlitFramebuffer(src_x, src_y, src_w, src_h, dst_x, dst_y, dst_w, dst_h, GL_COLOR_BUFFER_BIT, GL_LINEAR);
216 | ```
217 | Becomes
218 | ```c
219 | glBlitNamedFramebuffer(fbo_src, fbo_dst, src_x, src_y, src_w, src_h, dst_x, dst_y, dst_w, dst_h, GL_COLOR_BUFFER_BIT, GL_LINEAR);
220 | ```
221 |
222 | ###### glClearNamedFramebuffer
223 |
224 | * [`glClearNamedFramebuffer`](http://docs.gl/gl4/glClearBuffer) is the equivalent of [`glClearBuffer`](http://docs.gl/gl4/glClearBuffer)
225 |
226 | There are two ways to go about clearing a framebuffer:
227 |
228 | The more familar way
229 | ```c
230 | glBindFramebuffer(GL_FRAMEBUFFER, fb);
231 | glClearColor(r, g, b, a);
232 | glClearDepth(d);
233 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
234 | ```
235 |
236 | and the more versatile per-attachment way
237 | ```c
238 | glBindFramebuffer(GL_FRAMEBUFFER, fb);
239 | glClearBufferfv(GL_COLOR, col_buff_index, &rgba);
240 | glClearBufferfv(GL_DEPTH, 0, &d);
241 | ```
242 |
243 | `col_buff_index` is the attachment index, so it would be equivalent to `GL_DRAW_BUFFER0 + col_buff_index`, and the draw buffer index for depth is always `0`.
244 |
245 | As you can see with `glClearBuffer` we can clear the texels of any attachment to some value, both methods are similar enough that you could reimplement the functions of method 1 using those of method 2.
246 |
247 | Despite the name it has nothing to do with buffer objects and this gets cleared up with the DSA version: `glClearNamedFramebuffer`
248 |
249 | So the DSA version looks like this:
250 | ```c
251 | glClearNamedFramebufferfv(fb, GL_COLOR, col_buff_index, &rgba);
252 | glClearNamedFramebufferfv(fb, GL_DEPTH, 0, &d);
253 | ```
254 |
255 | `fb` can be `0` if you're clearing the default framebuffer.
256 |
257 | ### glBuffer
258 | ------
259 | None of the DSA glBuffer functions ask for the buffer target and is only required to be specified whilst drawing.
260 |
261 | ###### glCreateBuffers
262 | * [`glCreateBuffers`](glCreateBuffers) is the equivalent of [`glGenBuffers`](http://docs.gl/gl4/glGenBuffers) + [`glBindBuffer`](http://docs.gl/gl4/glBindBuffer)(the initialization part)
263 |
264 | [`glCreateBuffers`](http://docs.gl/gl4/glGenBuffers) is used exactly like its traditional equivalent and automatically initializes the object.
265 |
266 | ###### glNamedBufferData
267 | * [`glNamedBufferData`](http://docs.gl/gl4/glBufferData) is the equivalent of [`glBufferData`](http://docs.gl/gl4/glBufferData)
268 |
269 | [`glNamedBufferData`](http://docs.gl/gl4/glBufferData) is just like [`glBufferData`](http://docs.gl/gl4/glBufferData) but instead of requiring the buffer target it takes in the buffer handle itself.
270 |
271 | ###### glVertexAttribFormat & glBindVertexBuffer
272 | * [`glVertexAttribFormat`](http://docs.gl/gl4/glVertexAttribFormat) and [`glBindVertexBuffer`](http://docs.gl/gl4/glBindVertexBuffer) are the equivalent of [`glVertexAttribPointer`](http://docs.gl/gl4/glVertexAttribPointer)
273 |
274 | If you aren't familiar with the application of [`glVertexAttribPointer`](http://docs.gl/gl4/glVertexAttribPointer) it is used like so:
275 |
276 | ```c
277 | struct vertex { vec3 pos, nrm; vec2 tex; };
278 |
279 | glBindVertexArray(vao);
280 | glBindBuffer(GL_ARRAY_BUFFER, vbo);
281 |
282 | glEnableVertexAttribArray(0);
283 | glEnableVertexAttribArray(1);
284 | glEnableVertexAttribArray(2);
285 |
286 | glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(vertex), (void*)(offsetof(vertex, pos));
287 | glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(vertex), (void*)(offsetof(vertex, nrm));
288 | glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(vertex), (void*)(offsetof(vertex, tex));
289 | ```
290 |
291 | [`glVertexAttribFormat`](http://docs.gl/gl4/glVertexAttribFormat) isn't much different, the main thing with it is that it's one out of a two-parter with [`glVertexAttribBinding`](http://docs.gl/gl4/glVertexAttribBinding).
292 |
293 | In order to get out the same effect as the previous snippet we first need to make a call to [`glBindVertexBuffer`](http://docs.gl/gl4/glBindVertexBuffer). Despite *Bind* being in the name it isn't the same kind as for instance: `glBindTexture`.
294 |
295 | Here's how they're both put into action:
296 |
297 | ```c
298 | struct vertex { vec3 pos, nrm; vec2 tex; };
299 |
300 | glBindVertexArray(vao);
301 | glBindVertexBuffer(0, vbo, 0, sizeof(vertex));
302 |
303 | glEnableVertexAttribArray(0);
304 | glEnableVertexAttribArray(1);
305 | glEnableVertexAttribArray(2);
306 |
307 | glVertexAttribFormat(0, 3, GL_FLOAT, GL_FALSE, offsetof(vertex, pos));
308 | glVertexAttribFormat(1, 3, GL_FLOAT, GL_FALSE, offsetof(vertex, nrm));
309 | glVertexAttribFormat(2, 2, GL_FLOAT, GL_FALSE, offsetof(vertex, tex));
310 |
311 | glVertexAttribBinding(0, 0);
312 | glVertexAttribBinding(1, 0);
313 | glVertexAttribBinding(2, 0);
314 | ```
315 |
316 | Although this is the newer way of going about it this isn't fully DSA as we still need to make that VAO bind call, to go all the way we need to transform [`glEnableVertexAttribArray`](http://docs.gl/gl4/glEnableVertexAttribArray), [`glVertexAttribFormat`](http://docs.gl/gl4/glVertexAttribFormat), [`glVertexAttribBinding`](http://docs.gl/gl4/glVertexAttribBinding), and [`glBindVertexBuffer`](http://docs.gl/gl4/glBindVertexBuffer) into [`glEnableVertexArrayAttrib`](http://docs.gl/gl4/glEnableVertexAttribArray), [`glVertexArrayAttribFormat`](http://docs.gl/gl4/glVertexAttribFormat), [`glVertexArrayAttribBinding`](http://docs.gl/gl4/glVertexAttribBinding), and [`glVertexArrayVertexBuffer`](http://docs.gl/gl4/glBindVertexBuffer).
317 |
318 | ```c
319 | glVertexArrayVertexBuffer(vao, 0, data->vbo, 0, sizeof(vertex));
320 |
321 | glEnableVertexArrayAttrib(vao, 0);
322 | glEnableVertexArrayAttrib(vao, 1);
323 | glEnableVertexArrayAttrib(vao, 2);
324 |
325 | glVertexArrayAttribFormat(vao, 0, 3, GL_FLOAT, GL_FALSE, offsetof(vertex, pos));
326 | glVertexArrayAttribFormat(vao, 1, 3, GL_FLOAT, GL_FALSE, offsetof(vertex, nrm));
327 | glVertexArrayAttribFormat(vao, 2, 2, GL_FLOAT, GL_FALSE, offsetof(vertex, tex));
328 |
329 | glVertexArrayAttribBinding(vao, 0, 0);
330 | glVertexArrayAttribBinding(vao, 1, 0);
331 | glVertexArrayAttribBinding(vao, 2, 0);
332 | ```
333 |
334 | The version that takes in the VAO for binding the VBO, [`glVertexArrayVertexBuffer`](http://docs.gl/gl4/glBindVertexBuffer), has an equivalent for the IBO: [`glVertexArrayElementBuffer`](http://docs.gl/gl4/glVertexArrayElementBuffer).
335 |
336 | All together this is how uploading an indexed model with *only* DSA should look:
337 |
338 | ```c
339 | glCreateBuffers(1, &vbo);
340 | glNamedBufferStorage(vbo, sizeof(vertex)*vertex_count, vertices, GL_DYNAMIC_STORAGE_BIT);
341 |
342 | glCreateBuffers(1, &ibo);
343 | glNamedBufferStorage(ibo, sizeof(uint32_t)*index_count, indices, GL_DYNAMIC_STORAGE_BIT);
344 |
345 | glCreateVertexArrays(1, &vao);
346 |
347 | glVertexArrayVertexBuffer(vao, 0, vbo, 0, sizeof(vertex));
348 | glVertexArrayElementBuffer(vao, ibo);
349 |
350 | glEnableVertexArrayAttrib(vao, 0);
351 | glEnableVertexArrayAttrib(vao, 1);
352 | glEnableVertexArrayAttrib(vao, 2);
353 |
354 | glVertexArrayAttribFormat(vao, 0, 3, GL_FLOAT, GL_FALSE, offsetof(vertex, pos));
355 | glVertexArrayAttribFormat(vao, 1, 3, GL_FLOAT, GL_FALSE, offsetof(vertex, nrm));
356 | glVertexArrayAttribFormat(vao, 2, 2, GL_FLOAT, GL_FALSE, offsetof(vertex, tex));
357 |
358 | glVertexArrayAttribBinding(vao, 0, 0);
359 | glVertexArrayAttribBinding(vao, 1, 0);
360 | glVertexArrayAttribBinding(vao, 2, 0);
361 | ```
362 |
363 | ## Detailed Messages with Debug Output
364 |
365 | [`KHR_debug`](http://www.opengl.org/registry/specs/KHR/debug.txt) has been in core since version 4.3 and it's a big step up from how we used to do error polling.
366 |
367 | With [Debug Output](https://www.khronos.org/opengl/wiki/Debug_Output) we can receive meaningful messages on the state of the GL through a callback function that we'll be providing.
368 |
369 | All it takes to get running are two calls: [`glEnable`](http://docs.gl/gl4/glEnable) & [`glDebugMessageCallback`](http://docs.gl/gl4/glDebugMessageCallback).
370 | ```cpp
371 | glEnable(GL_DEBUG_OUTPUT);
372 | glDebugMessageCallback(message_callback, nullptr);
373 | ```
374 |
375 | Your callback must have the signature `void callback(GLenum src, GLenum type, GLuint id, GLenum severity, GLsizei length, GLchar const* msg, void const* user_param)`.
376 |
377 | Here's how I have mine defined:
378 |
379 | ```cpp
380 | void message_callback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, GLchar const* message, void const* user_param)
381 | {
382 | auto const src_str = [source]() {
383 | switch (source)
384 | {
385 | case GL_DEBUG_SOURCE_API: return "API";
386 | case GL_DEBUG_SOURCE_WINDOW_SYSTEM: return "WINDOW SYSTEM";
387 | case GL_DEBUG_SOURCE_SHADER_COMPILER: return "SHADER COMPILER";
388 | case GL_DEBUG_SOURCE_THIRD_PARTY: return "THIRD PARTY";
389 | case GL_DEBUG_SOURCE_APPLICATION: return "APPLICATION";
390 | case GL_DEBUG_SOURCE_OTHER: return "OTHER";
391 | }
392 | }();
393 |
394 | auto const type_str = [type]() {
395 | switch (type)
396 | {
397 | case GL_DEBUG_TYPE_ERROR: return "ERROR";
398 | case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR: return "DEPRECATED_BEHAVIOR";
399 | case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR: return "UNDEFINED_BEHAVIOR";
400 | case GL_DEBUG_TYPE_PORTABILITY: return "PORTABILITY";
401 | case GL_DEBUG_TYPE_PERFORMANCE: return "PERFORMANCE";
402 | case GL_DEBUG_TYPE_MARKER: return "MARKER";
403 | case GL_DEBUG_TYPE_OTHER: return "OTHER";
404 | }
405 | }();
406 |
407 | auto const severity_str = [severity]() {
408 | switch (severity) {
409 | case GL_DEBUG_SEVERITY_NOTIFICATION: return "NOTIFICATION";
410 | case GL_DEBUG_SEVERITY_LOW: return "LOW";
411 | case GL_DEBUG_SEVERITY_MEDIUM: return "MEDIUM";
412 | case GL_DEBUG_SEVERITY_HIGH: return "HIGH";
413 | }
414 | }();
415 | std::cout << src_str << ", " << type_str << ", " << severity_str << ", " << id << ": " << message << '\n';
416 | }
417 | ```
418 |
419 | There will be times when you want to filter your messages, maybe you're interested in anything but notifications. OpenGL has a function for this: [`glDebugMessageControl`](http://docs.gl/gl4/glDebugMessageControl).
420 |
421 | Here's how we use it to disable notifications:
422 |
423 | ```cpp
424 | glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DEBUG_SEVERITY_NOTIFICATION, 0, nullptr, GL_FALSE);
425 | ```
426 |
427 | Something we can do is have messages fire synchronously where it will call on the same thread as the context and from within the OpenGL call. This way we can guarantee function call order and this means if we were to add a breakpoint into the definition of our callback we could traverse the call stack and locate the origin of the error.
428 |
429 | All it takes is another call to [`glEnable`](http://docs.gl/gl4/glEnable) with the value of `GL_DEBUG_OUTPUT_SYNCHRONOUS`, so you end up with this:
430 |
431 | ```cpp
432 | glEnable(GL_DEBUG_OUTPUT);
433 | glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
434 | glDebugMessageCallback(message_callback, nullptr);
435 | ```
436 |
437 | Farewell, `glGetError`.
438 |
439 | ## Storing Index and Vertex Data Under Single Buffer
440 |
441 | Most material on OpenGL that touch on indexed drawing will separate vertex and index data between buffers, this is because the [vertex_buffer_object](https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_vertex_buffer_object.txt) spec strongly recommends to do so. The reasoning for this is that different GL implementations may have different memory type requirements, so having the index data in its own buffer allows the driver to decide the optimal storage strategy.
442 |
443 | This was useful when there were were several ways to attach GPUs to the main system, technically there still are, but AGP was completely phased out by PCIe about a decade ago and regular PCI ports aren't really used for this anymore save a few cases.
444 |
445 | The overhead that comes with managing an additional buffer for indexed geometry isn't as justifiable a trade off as it used to be.
446 |
447 | We store the vertices and indices at known byte offsets and pass the information to OpenGL. For the vertices we do this with [`glVertexArrayVertexBuffer`](http://docs.gl/gl4/glBindVertexBuffer)'s `offset` parameter, and indices through [`glDrawElements`](http://docs.gl/gl4/glDrawElements)'s `indices` parameter.
448 |
449 | ```cpp
450 | GLint alignment = GL_NONE;
451 | glGetIntegerv(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT, &alignment);
452 |
453 | GLuint vao = GL_NONE;
454 | GLuint buffer = GL_NONE;
455 |
456 | auto const ind_len = GLsizei(ind_count * sizeof(element_t));
457 | auto const vrt_len = GLsizei(vrt_count * sizeof(vertex));
458 |
459 | auto const ind_len_aligned = align(ind_len, alignment);
460 | auto const vrt_len_aligned = align(vrt_len, alignment);
461 |
462 | auto const ind_offset = vrt_len_aligned;
463 | auto const vrt_offset = 0;
464 |
465 | glCreateBuffers(1, &buffer);
466 | glNamedBufferStorage(buffer, ind_len_aligned + vrt_len_aligned, nullptr, GL_DYNAMIC_STORAGE_BIT);
467 |
468 | glNamedBufferSubData(buffer, ind_offset, ind_len, ind_data);
469 | glNamedBufferSubData(buffer, vrt_offset, vrt_len, vrt_data);
470 |
471 | glCreateVertexArrays(1, &vao);
472 | glVertexArrayVertexBuffer(vao, 0, buffer, vrt_offset, sizeof(vertex));
473 | glVertexArrayElementBuffer(vao, buffer);
474 |
475 | //continue with setup
476 | ```
477 |
478 | And then when it's time to render:
479 |
480 | ```c
481 | glDrawElements(GL_TRIANGLES, ind_count, indices_format, (void *) ind_offset);
482 | ```
483 |
484 | If you're curious why the pointer cast is necessary it's because in immediate mode you'd pass in your index buffer directly from host memory but in retained mode it's an offset into the buffer store.
485 |
486 | ## Ideal Way Of Retrieving All Uniform Names
487 |
488 | There is material out there that teach beginners to retrieve uniform information by manually parsing the shader source strings, please don't do this.
489 |
490 | Here is how it should be done:
491 |
492 | ```cpp
493 | struct uniform_info
494 | {
495 | GLint location;
496 | GLsizei count;
497 | };
498 |
499 | GLint uniform_count = 0;
500 | glGetProgramiv(program_name, GL_ACTIVE_UNIFORMS, &uniform_count);
501 |
502 | if (uniform_count != 0)
503 | {
504 | GLint max_name_len = 0;
505 | GLsizei length = 0;
506 | GLsizei count = 0;
507 | GLenum type = GL_NONE;
508 | glGetProgramiv(program_name, GL_ACTIVE_UNIFORM_MAX_LENGTH, &max_name_len);
509 |
510 | auto uniform_name = std::make_unique(max_name_len);
511 |
512 | std::unordered_map uniforms;
513 |
514 | for (GLint i = 0; i < uniform_count; ++i)
515 | {
516 | glGetActiveUniform(program_name, i, max_name_len, &length, &count, &type, uniform_name.get());
517 |
518 | uniform_info_t uniform_info = {};
519 | uniform_info.location = glGetUniformLocation(program_name, uniform_name.get());
520 | uniform_info.count = count;
521 |
522 | uniforms.emplace(std::make_pair(std::string(uniform_name.get(), length), uniform_info));
523 | }
524 | }
525 | ```
526 |
527 | Note that the `GLsizei size` parameter refers to the number of locations the uniform takes up with `mat3`, `vec4`, `float`, etc. being 1 and arrays having it be the number of elements, the locations are arranged in a way that allows you to do `array_location + element_number` to find the location of an element, so if you wanted to write to element `5` it would be done like this:
528 | ```cpp
529 | glProgramUniformXX(program_name, uniforms["my_array[0]"].location + 5, value);
530 | ```
531 | or if you want to modify the whole array:
532 | ```cpp
533 | glProgramUniformXXv(program_name, uniforms["my_array[0]"].location, uniforms["my_array[0]"].count, my_array);
534 | ```
535 | *Ideally UBOs would be used when dealing with collections of data **larger than 16K** as it may be slower than packing the data into vec4s and using [`glProgramUniform4f`](http://docs.gl/gl4/glProgramUniform).*
536 |
537 | With this you can store the uniform datatype and check it within your uniform update functions.
538 |
539 | ## Texture Atlases vs Arrays
540 |
541 | Array textures are a great way of managing collections of textures of the same size and format. They allow for using a set of textures without having to bind between them.
542 |
543 | They turn out to be good as an alternative to atlases as long as some criteria are met, that being all the sub-textures, or swatches, fit under the same dimensions and levels.
544 |
545 | The advantages of using this over an atlas is that each layer is treated as a separate texture in terms of wrapping and mipmapping.
546 |
547 | Array textures come with three targets: `GL_TEXTURE_1D_ARRAY`, `GL_TEXTURE_2D_ARRAY`, and `GL_TEXTURE_CUBE_MAP_ARRAY`.
548 |
549 | 2D array textures and 3d textures are similar but are semantically different, the differences come in where the mipmap level are and how layers are filtered.
550 |
551 | There is no built-in filtering for interpolation between layers where the Z part of a 3d texture will have filtering available. The same goes for 1d arrays and 2d textures.
552 |
553 | 2d array
554 | ```
555 | |layer 0 |
556 | | level 1 |
557 | | level 2 |
558 | |layer 1 |
559 | | level 1 |
560 | | level 2 |
561 | ...
562 | ```
563 |
564 | 3d texture
565 | ```
566 | |z off 0 |
567 | |z off 1 |
568 | |z off 2 |
569 | ...
570 | | level 1 |
571 | | level 2 |
572 | ...
573 | ```
574 |
575 | To allocate a 2D texture array we do this:
576 |
577 | ```c
578 | GLuint texarray = 0;
579 | GLsizei width = 512, height = 512, layers = 3;
580 | glCreateTextures(GL_TEXTURE_2D_ARRAY, 1, &texarray);
581 | glTextureStorage3D(texarray, 1, GL_RGBA8, width, height, layers);
582 | ```
583 |
584 | [`glTextureStorage3D`](http://docs.gl/gl4/glTexStorage3D) has been modified to accommodate 2d array textures which I imagine is confusing at first but there's a pattern: the last dimension parameter acts as the layer specifier, so if you were to allocate a 1D texture array you would have to use [`glTextureStorage2D`](http://docs.gl/gl4/glTexStorage2D) with height as the layer capacity.
585 |
586 | Anyway, uploading to individual layers is very straightforward:
587 |
588 | ```c
589 | glTextureSubImage3D(texarray, mipmap_level, offset.x, offset.y, layer, width, height, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
590 | ```
591 |
592 | It's super duper simple.
593 |
594 | The most notable difference between arrays and atlases in terms of implementation lies in the shader.
595 |
596 | To bind a texture array to the context you need a specialized sampler called `samplerXXArray`. We will also need a uniform to store the layer id.
597 |
598 | ```glsl
599 | #version 450 core
600 |
601 | layout (location = 0) out vec4 color;
602 | layout (location = 0) in vec2 tex0;
603 |
604 | uniform sampler2DArray texarray;
605 | uniform uint diffuse_layer;
606 |
607 | float layer2coord(uint capacity, uint layer)
608 | {
609 | return max(0, min(float(capacity - 1), floor(float(layer) + 0.5)));
610 | }
611 |
612 | void main()
613 | {
614 | color = texture(texarray, vec3(tex0, layer2coord(3, diffuse_layer)));
615 | }
616 | ```
617 |
618 | Ideally you should calculate the layer coordinate outside of the shader.
619 |
620 | You can take this way further and set up a little UBO/SSBO system of arrays containing layer id and texture array id pairs and update which layer id is used with regular uniforms.
621 |
622 | Also, I advise against using ubos and ssbos for per object/draw stuff without a plan otherwise you will end up with everything not working as you'd like because the command queue has no involvement during the reads and writes.
623 |
624 | As a bonus let me tell you an easy way to populate a texture array with parts of an atlas, in our case a basic rpg tileset.
625 |
626 | Modern OpenGL comes with two generic memory copy functions: [`glCopyImageSubData`](http://docs.gl/gl4/glCopyImageSubData) and [`glCopyBufferSubData`](http://docs.gl/gl4/glCopyBufferSubData). Here we'll be dealing with [`glCopyImageSubData`](http://docs.gl/gl4/glCopyImageSubData), this function allows us to copy sections of a source image to a region of a destination image.
627 | We're going to take advantage of its offset and size parameters so that we can copy tiles from every location and paste them in the appropriate layers within our texture array.
628 |
629 | Image files loaded with [nothings](https://github.com/nothings)' [stb_image](https://github.com/nothings/stb) header library.
630 |
631 | Here it is:
632 | ```cpp
633 | GLsizei image_w, image_h, c, tile_w = 16, tile_h = 16;
634 | stbi_uc* pixels = stbi_load(".\\textures\\tiles_packed.png", &image_w, &image_h, &c, STBI_rgb_alpha);
635 | GLuint tileset;
636 | GLsizei
637 | tiles_x = image_w / tile_w,
638 | tiles_y = image_h / tile_h,
639 | tile_count = tiles_x * tiles_y;
640 |
641 | glCreateTextures(GL_TEXTURE_2D_ARRAY, 1, &tileset);
642 | glTextureStorage3D(tileset, 1, GL_RGBA8, tile_w, tile_h, tile_count);
643 |
644 | {
645 | GLuint temp_tex = 0;
646 | glCreateTextures(GL_TEXTURE_2D, 1, &temp_tex);
647 | glTextureStorage2D(temp_tex, 1, GL_RGBA8, image_w, image_h);
648 | glTextureSubImage2D(temp_tex, 0, 0, 0, image_w, image_h, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
649 |
650 | for (GLsizei i = 0; i < tile_count; ++i)
651 | {
652 | GLint x = (i % tiles_x) * tile_w, y = (i / tiles_x) * tile_h;
653 | glCopyImageSubData(temp_tex, GL_TEXTURE_2D, 0, x, y, 0, tileset, GL_TEXTURE_2D_ARRAY, 0, 0, 0, i, tile_w, tile_h, 1);
654 | }
655 | glDeleteTextures(1, &temp_tex);
656 | }
657 |
658 | stbi_image_free(pixels);
659 | ```
660 |
661 | ## Texture Views & Aliases
662 |
663 | Texture views allow us to share a section of a texture's storage with an object of a different texture target and/or format. *Share* as in there's no copying of the texture data, any changes you make to a view's data is visible to all views that share the storage along with the original texture object.
664 |
665 | Views are mostly indistinguishable from regular texture objects so you can use them as if they are.
666 |
667 | The original storage is only freed once all references to it are deleted, if you are familiar with C++'s `std::shared_ptr` it's very similar.
668 |
669 | We can only make views if we use a target and format which is compatible with our original texture, you can read the format tables on the [wiki](https://www.khronos.org/opengl/wiki/Texture_Storage#View_texture_aliases).
670 |
671 | Making the view itself is simple, it's only two function calls: [`glGenTextures`](http://docs.gl/gl4/glGenTextures) & [`glTextureView`](http://docs.gl/gl4/glTextureView).
672 |
673 | Despite this being about modern OpenGL [`glGenTextures`](http://docs.gl/gl4/glGenTextures) has an important role here: we need only an available texture name and nothing more; this needs to be a completely empty uninitialized object and because this function only handles the generation of a valid handle it's perfect for this.
674 |
675 | [`glTextureView`](http://docs.gl/gl4/glTextureView) is how we'll create the view.
676 |
677 | If we were to have a typical 2d texture array and needed a view of layer 5 in isolation this is how it would look:
678 |
679 | ```c
680 | glGenTextures(1, &view_name);
681 | glTextureView(view_name, GL_TEXTURE_2D, src_name, internal_format, min_level, level_count, 5, 1);
682 | ```
683 |
684 | With this you can bind layer 5 alone as a `GL_TEXTURE_2D` texture.
685 |
686 | This is the exact same when dealing with cube maps, the layer parameters will correspond to the cube faces with the layer params of cube map arrays being `cubemap_layer * 6 + face`.
687 |
688 | Texture views can be of other views as well, so there could be a texture array, and a view of a section of that array, and another view of a specific layer within that array view. The parameters are relative to the properties of the source.
689 |
690 | The fact that we can specify which mipmaps we want in the view means that we can have views which are just of those specific mipmap levels, so for example you could make textures views of the *N*th mipmap level of a bunch of textures and use only those for expensive texture dependant lighting calculations.
691 |
692 | [ARB_texture_view](https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_texture_view.txt)
693 |
694 | ## Setting up Mix & Match Shaders with Program Pipelines
695 |
696 | * Nvidia drivers have spotty performance as they lean towards monolithic shader programs, so it may be better suited for non-performance-critical applications.
697 |
698 | [Program Pipeline](https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_separate_shader_objects.txt) objects allow us to change shader stages on the fly without having to relink them.
699 |
700 | To create and set up a simple program pipeline without any debugging looks like this:
701 |
702 | ```cpp
703 | const char*
704 | vs_source = load_file(".\\main_shader.vs").c_str(),
705 | fs_source = load_file(".\\main_shader.fs").c_str();
706 |
707 | GLuint
708 | vs = glCreateShaderProgramv(GL_VERTEX_SHADER, 1, &vs_source),
709 | fs = glCreateShaderProgramv(GL_FRAGMENT_SHADER, 1, &fs_source),
710 | pr = 0;
711 |
712 | glCreateProgramPipelines(1, &pr);
713 | glUseProgramStages(pr, GL_VERTEX_SHADER_BIT, vs);
714 | glUseProgramStages(pr, GL_FRAGMENT_SHADER_BIT, fs);
715 |
716 | glBindProgramPipeline(pr);
717 | ```
718 |
719 | [`glCreateProgramPipelines`](http://docs.gl/gl4/glCreateProgramPipelines) generates the handle and initializes the object, [`glCreateShaderProgramv`](http://docs.gl/gl4/glCreateShaderProgram) generates, initializes, compiles, and links a shader program using the sources given, and [`glUseProgramStages`](http://docs.gl/gl4/glUseProgramStages) attaches the program's stage(s) to the pipeline object. [`glBindProgramPipeline`](http://docs.gl/gl4/glBindProgramPipeline) as you can tell binds the pipeline to the context.
720 |
721 | Because our shaders are now looser and flexible we need to get stricter with our input and output variables.
722 | Either we declare the input and output in the same order with the same names or we make their locations explicitly match through the location qualifier.
723 |
724 | I greatly suggest the latter option for non-blocks, this will allow us to set up a well-defined interface while also being flexible with the naming and ordering.
725 | Interface blocks also need to match members.
726 |
727 | As collateral for needing a stricter interface we also need to declare the built-in input and output blocks we wish to use for every stage.
728 |
729 | The built-in block interfaces are defined as ([from the wiki](https://www.khronos.org/opengl/wiki/Built-in_Variable_(GLSL))):
730 |
731 | Vertex:
732 | ```glsl
733 | out gl_PerVertex
734 | {
735 | vec4 gl_Position;
736 | float gl_PointSize;
737 | float gl_ClipDistance[];
738 | };
739 | ```
740 |
741 | Tesselation Control:
742 | ```glsl
743 | out gl_PerVertex
744 | {
745 | vec4 gl_Position;
746 | float gl_PointSize;
747 | float gl_ClipDistance[];
748 | } gl_out[];
749 | ```
750 |
751 | Tesselation Evaluation:
752 | ```glsl
753 | out gl_PerVertex {
754 | vec4 gl_Position;
755 | float gl_PointSize;
756 | float gl_ClipDistance[];
757 | };
758 | ```
759 |
760 | Geometry:
761 | ```glsl
762 |
763 | out gl_PerVertex
764 | {
765 | vec4 gl_Position;
766 | float gl_PointSize;
767 | float gl_ClipDistance[];
768 | };
769 | ```
770 |
771 | An extremely basic vertex shader enabled for use in a pipeline object looks like this:
772 | ```glsl
773 | #version 450
774 |
775 | out gl_PerVertex { vec4 gl_Position; };
776 |
777 | layout (location = 0) in vec3 pos;
778 | layout (location = 1) in vec3 col;
779 |
780 | layout (location = 0) out v_out
781 | {
782 | vec3 col;
783 | } v_out;
784 |
785 | void main()
786 | {
787 | v_out.col = col;
788 | gl_Position = vec4(pos, 1.0);
789 | }
790 | ```
791 |
792 | ## Faster Reads and Writes with Persistent Mapping
793 |
794 | With [`persistent mapping`](https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_buffer_storage.txt) we can get a pointer to a region of memory that OpenGL will be using as a sort of intermediate buffer zone, this will allow us to make reads and writes to this area and let the driver decide when to use the contents.
795 |
796 | First we need the right flags for both buffer storage creation and the mapping itself:
797 | ```cpp
798 | constexpr GLbitfield
799 | mapping_flags = GL_MAP_WRITE_BIT | GL_MAP_READ_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT,
800 | storage_flags = GL_DYNAMIC_STORAGE_BIT | mapping_flags;
801 | ```
802 |
803 | **GL_MAP_COHERENT_BIT**: This flag ensures writes will be seen by the server when done from the client and vice versa.
804 |
805 | **GL_MAP_PERSISTENT_BIT**: This tells our driver you wish to keep the mapping through subsequent OpenGL operations.
806 |
807 | **GL_MAP_READ_BIT**: Tells OpenGL we wish to read from the buffer.
808 |
809 | **GL_MAP_WRITE_BIT**: Lets OpenGL know we're gonna write to it, if you don't specify this *anything could happen*.
810 |
811 | If we don't use these flags for the storage creation GL will reject your mapping request with scorn. What's worse is that you absolutely won't know unless you're doing some form of error checking.
812 |
813 | Setting up our immutable storage is straight forward:
814 | ```cpp
815 | glCreateBuffers(1, &name);
816 | glNamedBufferStorage(name, size, nullptr, storage_flags);
817 | ```
818 | Whatever we put in the `const void* data` parameter is arbitrary and marking it as `nullptr` specifies we wish not to copy any data into it.
819 |
820 | Here is how we get that pointer we're after:
821 | ```cpp
822 | void* ptr = glMapNamedBufferRange(name, offset, size, mapping_flags);
823 | ```
824 |
825 | I recommend mapping it as infrequently as possible because the process of mapping the buffer isn't particularly fast. In most cases you only need to do it just the once.
826 |
827 | Make sure to unmap the buffer before deleting it:
828 | ```cpp
829 | glUnmapNamedBuffer(name);
830 | glDeleteBuffers(1, &name);
831 | ```
832 |
833 | If C++20 is available you can drop it into a std::span.
834 |
835 | ## More information
836 | * [OpenGL wiki](https://www.khronos.org/opengl/wiki/).
837 | * [docs.GL](http://docs.gl/)
838 | * [DSA ARB specification](https://www.opengl.org/registry/specs/ARB/direct_state_access.txt).
839 |
--------------------------------------------------------------------------------