├── .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 | --------------------------------------------------------------------------------