{"id":13731705,"url":"https://github.com/fendevel/Guide-to-Modern-OpenGL-Functions","last_synced_at":"2025-05-08T05:30:38.737Z","repository":{"id":37909256,"uuid":"69830476","full_name":"fendevel/Guide-to-Modern-OpenGL-Functions","owner":"fendevel","description":"A guide to using modern OpenGL functions.","archived":false,"fork":false,"pushed_at":"2021-11-08T08:01:54.000Z","size":119,"stargazers_count":1167,"open_issues_count":3,"forks_count":43,"subscribers_count":21,"default_branch":"master","last_synced_at":"2024-11-14T22:35:40.233Z","etag":null,"topics":["direct-state-access","dsa","modern","modern-opengl","modern-opengl-functions","opengl","opengl-dsa","pipeline-object","texture-atlas","textureview","uniform"],"latest_commit_sha":null,"homepage":null,"language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"cc0-1.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/fendevel.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-10-03T00:31:42.000Z","updated_at":"2024-11-08T15:05:49.000Z","dependencies_parsed_at":"2022-09-18T15:14:43.061Z","dependency_job_id":null,"html_url":"https://github.com/fendevel/Guide-to-Modern-OpenGL-Functions","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fendevel%2FGuide-to-Modern-OpenGL-Functions","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fendevel%2FGuide-to-Modern-OpenGL-Functions/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fendevel%2FGuide-to-Modern-OpenGL-Functions/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fendevel%2FGuide-to-Modern-OpenGL-Functions/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/fendevel","download_url":"https://codeload.github.com/fendevel/Guide-to-Modern-OpenGL-Functions/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253008359,"owners_count":21839638,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["direct-state-access","dsa","modern","modern-opengl","modern-opengl-functions","opengl","opengl-dsa","pipeline-object","texture-atlas","textureview","uniform"],"created_at":"2024-08-03T02:01:36.589Z","updated_at":"2025-05-08T05:30:38.727Z","avatar_url":"https://github.com/fendevel.png","language":null,"funding_links":[],"categories":["Others","Computer Graphics","Graphics"],"sub_categories":["OpenGL"],"readme":"# A Guide to Modern OpenGL Functions\n\n## Index\n\n* [DSA](#dsa-direct-state-access)\n  * [glTexture](#gltexture)\n    * [glCreateTextures](#glcreatetextures)\n    * [glTextureParameter](#gltextureparameter)\n    * [glTextureStorage](#gltexturestorage)\n    * [glBindTextureUnit](#glbindtextureunit)\n    * [Generating Mip Maps](#generating-mip-maps)\n    * [Uploading Cube Maps](#uploading-cube-maps)\n    * [Where is glTextureImage?](#where-is-gltextureimage)\n\n  * [glFramebuffer](#glframebuffer)\n    * [glCreateFramebuffers](#glcreateframebuffers)\n    * [glBlitNamedFramebuffer](#glblitnamedframebuffer)\n    * [glClearNamedFramebuffer](#glclearnamedframebuffer)\n\n  * [glBuffer](#glbuffer)\n    * [glCreateBuffers](#glcreatebuffers)\n    * [glNamedBufferData](#glnamedbufferdata)\n    * [glVertexAttribFormat \u0026 glBindVertexBuffer](#glvertexattribformat--glbindvertexbuffer)\n\n* [Detailed Messages with Debug Output](#detailed-messages-with-debug-output)\n* [Storing Index and Vertex Data Under Single Buffer](#storing-index-and-vertex-data-under-single-buffer)\n* [Ideal Way Of Retrieving All Uniform Names](#ideal-way-of-retrieving-all-uniform-names)\n* [Texture Atlases vs Arrays](#texture-atlases-vs-arrays)\n* [Texture Views \u0026 Aliases](#texture-views--aliases)\n* [Setting up Mix \u0026 Match Shaders with Program Pipelines](#setting-up-mix--match-shaders-with-program-pipelines)\n* [Faster Reads and Writes with Persistent Mapping](#faster-reads-and-writes-with-persistent-mapping)\n* [More Information](#more-information)\n\nWhat this is:\n\n* A guide on how to apply modern OpenGL functionality.\n\nWhat this is not:\n\n* A guide on modern OpenGL rendering techniques.\n\nWhen 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.\n\n## DSA (Direct State Access)\nWith 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.\n\n###### DSA Naming Convention\n\nThe [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:\n\n| OpenGL Object Type | Context Object Name | DSA Object Name  |\n| :------------- |:-------------| :-----|\n| [Texture Object](https://www.opengl.org/wiki/Texture) | Tex | Texture |\n| [Framebuffer Object](https://www.opengl.org/wiki/Framebuffer_Object) | Framebuffer | NamedFramebuffer |\n| [Buffer Object](https://www.opengl.org/wiki/Buffer_Object) | Buffer | NamedBuffer |\n| [Transform Feedback Object](https://www.opengl.org/wiki/Transform_Feedback_Object) | TransformFeedback | TransformFeedback |\n| [Vertex Array Object](https://www.opengl.org/wiki/Vertex_Array_Object) | N/A | VertexArray |\n| [Sampler Object](https://www.opengl.org/wiki/Sampler_Object) | N/A | Sampler |\n| [Query Object](https://www.opengl.org/wiki/Query_Object) | N/A | Query |\n| [Program Object](https://www.opengl.org/wiki/Program_Object) | N/A | Program |\n\n### glTexture\n------\n* The texture related calls aren't hard to figure out so let's jump right in.\n\n###### glCreateTextures\n* [`glCreateTextures`](http://docs.gl/gl4/glCreateTextures) is the equivalent of [`glGenTextures`](http://docs.gl/gl4/glGenTextures) + [`glBindTexture`](http://docs.gl/gl4/glBindTexture)(for initialization).\n\n```c\nvoid glCreateTextures(GLenum target, GLsizei n, GLuint *textures);\n```\n\nUnlike [`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.\n\nSo this:\n```c\nglGenTextures(1, \u0026name);\nglBindTexture(GL_TEXTURE_2D, name);\n```\nDSA-ified becomes:\n```c\nglCreateTextures(GL_TEXTURE_2D, 1, \u0026name);\n```\n\n###### glTextureParameter\n\n* [`glTextureParameter`](http://docs.gl/gl4/glTexParameter) is the equivalent of [`glTexParameterX`](http://docs.gl/gl4/glTexParameter)\n\n```c\nvoid glTextureParameteri(GLuint texture, GLenum pname, GLenum param);\n```\n\nThere 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.\n\n```c\nglTextureParameteri(name, GL_TEXTURE_MIN_FILTER, GL_NEAREST);\n```\n\n###### glTextureStorage\n\n* [`glTextureStorage`](http://docs.gl/gl4/glTexStorage2D) is semi-equivalent to [`glTexStorage`](http://docs.gl/gl4/glTexStorage2D) ([Where is glTextureImage?](#where-is-gltextureimage)).\n\nThe [`glTextureStorage`](http://docs.gl/gl4/glTexStorage2D) and [`glTextureSubImage`](http://docs.gl/gl4/glTexSubImage2D) families are the same exact way.\n\nTime for the big comparison:\n\n```c\nglGenTextures(1, \u0026name);\nglBindTexture(GL_TEXTURE_2D, name);\n\nglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );\nglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );\nglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);\nglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);\n\nglTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, width, height);\nglTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels);\n```\n\n```c\nglCreateTextures(GL_TEXTURE_2D, 1, \u0026name);\n\nglTextureParameteri(name, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );\nglTextureParameteri(name, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );\nglTextureParameteri(name, GL_TEXTURE_MIN_FILTER, GL_NEAREST);\nglTextureParameteri(name, GL_TEXTURE_MAG_FILTER, GL_NEAREST);\n\nglTextureStorage2D(name, 1, GL_RGBA8, width, height);\nglTextureSubImage2D(name, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels);\n```\n\n###### glBindTextureUnit\n\n* [`glBindTextureUnit`](http://docs.gl/gl4/glBindTextureUnit) is the equivalent of [`glActiveTexture`](http://docs.gl/gl4/glActiveTexture) + [`glBindTexture`](http://docs.gl/gl4/glBindTexture)\n\nDefeats the need for:\n```c\nglActiveTexture(GL_TEXTURE0 + 3);\nglBindTexture(GL_TEXTURE_2D, name);\n```\n\nAnd replaces it with a simple:\n```c\nglBindTextureUnit(3, name);\n```\n\n###### Generating Mip Maps\n\n* [`glGenerateTextureMipmap`](http://docs.gl/gl4/glGenerateMipmap) is the equivalent of [`glGenerateMipmap`](http://docs.gl/gl4/glGenerateMipmap)\n\nTakes in the texture name instead of the texture target.\n\n```c\nvoid glGenerateTextureMipmap(GLuint texture);\n```\n\n###### Uploading Cube Maps\n\nI should briefly point out that in order to upload cube map textures you need to use [`glTextureSubImage3D`](http://docs.gl/gl4/glTexSubImage3D).\n\n```c\nglTextureStorage2D(name, 1, GL_RGBA8, bitmap.width, bitmap.height);\n\nfor (size_t face = 0; face \u003c 6; ++face)\n{\n\tauto const\u0026 bitmap = bitmaps[face];\n\tglTextureSubImage3D(name, 0, 0, 0, face, bitmap.width, bitmap.height, 1, bitmap.format, GL_UNSIGNED_BYTE, bitmap.pixels);\n}\n```\n\n###### Where is glTextureImage?\n\nIf you look at the OpenGL function listing you will see a lack of `glTextureImage` and here's why:\n\nHaving 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).\n\nStorage 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. \n\ntl;dr \"Immutable textures are a more robust approach to handle textures\"\n\n* 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.\n\nSources: [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)\n\n### glFramebuffer\n------\n###### glCreateFramebuffers\n\n* [`glCreateFramebuffers`](http://docs.gl/gl4/glCreateFramebuffers) is the equivalent of [`glGenFramebuffers`](http://docs.gl/gl4/glGenFramebuffers)\n\n[`glCreateFramebuffers`](http://docs.gl/gl4/glCreateFramebuffers) is used exactly the same but initializes the object for you.\n\nEverything else is pretty much the same but takes in the framebuffer handle instead of the target.\n\n```cpp\nglCreateFramebuffers(1, \u0026fbo);\n\nglNamedFramebufferTexture(fbo, GL_COLOR_ATTACHMENT0, tex, 0);\nglNamedFramebufferTexture(fbo, GL_DEPTH_ATTACHMENT, depthTex, 0);\n\nif(glCheckNamedFramebufferStatus(fbo, GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)\n\tstd::cerr \u003c\u003c \"framebuffer error\\n\";\n```\n\n###### glBlitNamedFramebuffer\n\n* [`glBlitNamedFramebuffer`](http://docs.gl/gl4/glBlitFramebuffer) is the equivalent of [`glBlitFramebuffer`](http://docs.gl/gl4/glBlitFramebuffer)\n\nThe 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.\n\n```c\nglBindFramebuffer(GL_READ_FRAMEBUFFER, fbo_src);\nglBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo_dst);\n\nglBlitFramebuffer(src_x, src_y, src_w, src_h, dst_x, dst_y, dst_w, dst_h, GL_COLOR_BUFFER_BIT, GL_LINEAR);\n```\nBecomes\n```c\nglBlitNamedFramebuffer(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);\n```\n\n###### glClearNamedFramebuffer\n\n* [`glClearNamedFramebuffer`](http://docs.gl/gl4/glClearBuffer) is the equivalent of [`glClearBuffer`](http://docs.gl/gl4/glClearBuffer)\n\nThere are two ways to go about clearing a framebuffer:\n\nThe more familar way\n```c\nglBindFramebuffer(GL_FRAMEBUFFER, fb);\nglClearColor(r, g, b, a);\nglClearDepth(d);\nglClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);\n```\n\nand the more versatile per-attachment way\n```c\nglBindFramebuffer(GL_FRAMEBUFFER, fb);\nglClearBufferfv(GL_COLOR, col_buff_index, \u0026rgba);\nglClearBufferfv(GL_DEPTH, 0, \u0026d);\n```\n\n`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`.\n\nAs 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.\n\nDespite the name it has nothing to do with buffer objects and this gets cleared up with the DSA version: `glClearNamedFramebuffer` \n\nSo the DSA version looks like this:\n```c\nglClearNamedFramebufferfv(fb, GL_COLOR, col_buff_index, \u0026rgba);\nglClearNamedFramebufferfv(fb, GL_DEPTH, 0, \u0026d);\n```\n\n`fb` can be `0` if you're clearing the default framebuffer.\n\n### glBuffer\n------\nNone of the DSA glBuffer functions ask for the buffer target and is only required to be specified whilst drawing.\n\n###### glCreateBuffers\n* [`glCreateBuffers`](glCreateBuffers) is the equivalent of [`glGenBuffers`](http://docs.gl/gl4/glGenBuffers) + [`glBindBuffer`](http://docs.gl/gl4/glBindBuffer)(the initialization part)\n\n[`glCreateBuffers`](http://docs.gl/gl4/glGenBuffers) is used exactly like its traditional equivalent and automatically initializes the object.\n\n###### glNamedBufferData\n* [`glNamedBufferData`](http://docs.gl/gl4/glBufferData) is the equivalent of [`glBufferData`](http://docs.gl/gl4/glBufferData)\n\n[`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.\n\n###### glVertexAttribFormat \u0026 glBindVertexBuffer\n* [`glVertexAttribFormat`](http://docs.gl/gl4/glVertexAttribFormat) and [`glBindVertexBuffer`](http://docs.gl/gl4/glBindVertexBuffer) are the equivalent of [`glVertexAttribPointer`](http://docs.gl/gl4/glVertexAttribPointer)\n\nIf you aren't familiar with the application of [`glVertexAttribPointer`](http://docs.gl/gl4/glVertexAttribPointer) it is used like so:\n\n```c\nstruct vertex { vec3 pos, nrm; vec2 tex; };\n\nglBindVertexArray(vao);\nglBindBuffer(GL_ARRAY_BUFFER, vbo);\n\nglEnableVertexAttribArray(0);\nglEnableVertexAttribArray(1);\nglEnableVertexAttribArray(2);\n\nglVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(vertex), (void*)(offsetof(vertex, pos));\nglVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(vertex), (void*)(offsetof(vertex, nrm));\nglVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(vertex), (void*)(offsetof(vertex, tex));\n```\n\n[`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).\n\nIn 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`.\n\nHere's how they're both put into action:\n\n```c\nstruct vertex { vec3 pos, nrm; vec2 tex; };\n\nglBindVertexArray(vao);\nglBindVertexBuffer(0, vbo, 0, sizeof(vertex));\n\nglEnableVertexAttribArray(0);\nglEnableVertexAttribArray(1);\nglEnableVertexAttribArray(2);\n\nglVertexAttribFormat(0, 3, GL_FLOAT, GL_FALSE, offsetof(vertex, pos));\nglVertexAttribFormat(1, 3, GL_FLOAT, GL_FALSE, offsetof(vertex, nrm));\nglVertexAttribFormat(2, 2, GL_FLOAT, GL_FALSE, offsetof(vertex, tex));\n\nglVertexAttribBinding(0, 0);\nglVertexAttribBinding(1, 0);\nglVertexAttribBinding(2, 0);\n```\n\nAlthough 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).\n\n```c\nglVertexArrayVertexBuffer(vao, 0, data-\u003evbo, 0, sizeof(vertex));\n\nglEnableVertexArrayAttrib(vao, 0);\nglEnableVertexArrayAttrib(vao, 1);\nglEnableVertexArrayAttrib(vao, 2);\n\nglVertexArrayAttribFormat(vao, 0, 3, GL_FLOAT, GL_FALSE, offsetof(vertex, pos));\nglVertexArrayAttribFormat(vao, 1, 3, GL_FLOAT, GL_FALSE, offsetof(vertex, nrm));\nglVertexArrayAttribFormat(vao, 2, 2, GL_FLOAT, GL_FALSE, offsetof(vertex, tex));\n\nglVertexArrayAttribBinding(vao, 0, 0);\nglVertexArrayAttribBinding(vao, 1, 0);\nglVertexArrayAttribBinding(vao, 2, 0);\n```\n\nThe 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).\n\nAll together this is how uploading an indexed model with *only* DSA should look:\n\n```c\nglCreateBuffers(1, \u0026vbo);\t\nglNamedBufferStorage(vbo, sizeof(vertex)*vertex_count, vertices, GL_DYNAMIC_STORAGE_BIT);\n\nglCreateBuffers(1, \u0026ibo);\nglNamedBufferStorage(ibo, sizeof(uint32_t)*index_count, indices, GL_DYNAMIC_STORAGE_BIT);\n\nglCreateVertexArrays(1, \u0026vao);\n\nglVertexArrayVertexBuffer(vao, 0, vbo, 0, sizeof(vertex));\nglVertexArrayElementBuffer(vao, ibo);\n\nglEnableVertexArrayAttrib(vao, 0);\nglEnableVertexArrayAttrib(vao, 1);\nglEnableVertexArrayAttrib(vao, 2);\n\nglVertexArrayAttribFormat(vao, 0, 3, GL_FLOAT, GL_FALSE, offsetof(vertex, pos));\nglVertexArrayAttribFormat(vao, 1, 3, GL_FLOAT, GL_FALSE, offsetof(vertex, nrm));\nglVertexArrayAttribFormat(vao, 2, 2, GL_FLOAT, GL_FALSE, offsetof(vertex, tex));\n\nglVertexArrayAttribBinding(vao, 0, 0);\nglVertexArrayAttribBinding(vao, 1, 0);\nglVertexArrayAttribBinding(vao, 2, 0);\n```\n\n## Detailed Messages with Debug Output\n\n[`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.\n\nWith [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.\n\nAll it takes to get running are two calls: [`glEnable`](http://docs.gl/gl4/glEnable) \u0026 [`glDebugMessageCallback`](http://docs.gl/gl4/glDebugMessageCallback).\n```cpp\nglEnable(GL_DEBUG_OUTPUT);\nglDebugMessageCallback(message_callback, nullptr);\n```\n\nYour callback must have the signature `void callback(GLenum src, GLenum type, GLuint id, GLenum severity, GLsizei length, GLchar const* msg, void const* user_param)`. \n\nHere's how I have mine defined:\n\n```cpp\nvoid message_callback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, GLchar const* message, void const* user_param)\n{\n\tauto const src_str = [source]() {\n\t\tswitch (source)\n\t\t{\n\t\tcase GL_DEBUG_SOURCE_API: return \"API\";\n\t\tcase GL_DEBUG_SOURCE_WINDOW_SYSTEM: return \"WINDOW SYSTEM\";\n\t\tcase GL_DEBUG_SOURCE_SHADER_COMPILER: return \"SHADER COMPILER\";\n\t\tcase GL_DEBUG_SOURCE_THIRD_PARTY: return \"THIRD PARTY\";\n\t\tcase GL_DEBUG_SOURCE_APPLICATION: return \"APPLICATION\";\n\t\tcase GL_DEBUG_SOURCE_OTHER: return \"OTHER\";\n\t\t}\n\t}();\n\n\tauto const type_str = [type]() {\n\t\tswitch (type)\n\t\t{\n\t\tcase GL_DEBUG_TYPE_ERROR: return \"ERROR\";\n\t\tcase GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR: return \"DEPRECATED_BEHAVIOR\";\n\t\tcase GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR: return \"UNDEFINED_BEHAVIOR\";\n\t\tcase GL_DEBUG_TYPE_PORTABILITY: return \"PORTABILITY\";\n\t\tcase GL_DEBUG_TYPE_PERFORMANCE: return \"PERFORMANCE\";\n\t\tcase GL_DEBUG_TYPE_MARKER: return \"MARKER\";\n\t\tcase GL_DEBUG_TYPE_OTHER: return \"OTHER\";\n\t\t}\n\t}();\n\n\tauto const severity_str = [severity]() {\n\t\tswitch (severity) {\n\t\tcase GL_DEBUG_SEVERITY_NOTIFICATION: return \"NOTIFICATION\";\n\t\tcase GL_DEBUG_SEVERITY_LOW: return \"LOW\";\n\t\tcase GL_DEBUG_SEVERITY_MEDIUM: return \"MEDIUM\";\n\t\tcase GL_DEBUG_SEVERITY_HIGH: return \"HIGH\";\n\t\t}\n\t}();\n\tstd::cout \u003c\u003c src_str \u003c\u003c \", \" \u003c\u003c type_str \u003c\u003c \", \" \u003c\u003c severity_str \u003c\u003c \", \" \u003c\u003c id \u003c\u003c \": \" \u003c\u003c message \u003c\u003c '\\n';\n}\n```\n\nThere 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).\n\nHere's how we use it to disable notifications:\n\n```cpp\nglDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DEBUG_SEVERITY_NOTIFICATION, 0, nullptr, GL_FALSE);\n```\n\nSomething 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.\n\nAll 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:\n\n```cpp\nglEnable(GL_DEBUG_OUTPUT);\nglEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);\nglDebugMessageCallback(message_callback, nullptr);\n```\n\nFarewell, `glGetError`.\n\n## Storing Index and Vertex Data Under Single Buffer\n\nMost 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.\n\nThis 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.\n\nThe overhead that comes with managing an additional buffer for indexed geometry isn't as justifiable a trade off as it used to be.\n\nWe 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.\n\n```cpp\nGLint alignment = GL_NONE;\nglGetIntegerv(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT, \u0026alignment);\n\nGLuint vao \t= GL_NONE;\nGLuint buffer \t= GL_NONE;\n\nauto const ind_len = GLsizei(ind_count * sizeof(element_t));\nauto const vrt_len = GLsizei(vrt_count * sizeof(vertex));\n\nauto const ind_len_aligned = align(ind_len, alignment);\nauto const vrt_len_aligned = align(vrt_len, alignment);\n\nauto const ind_offset = vrt_len_aligned;\nauto const vrt_offset = 0;\n\nglCreateBuffers(1, \u0026buffer);\nglNamedBufferStorage(buffer, ind_len_aligned + vrt_len_aligned, nullptr, GL_DYNAMIC_STORAGE_BIT);\n\nglNamedBufferSubData(buffer, ind_offset, ind_len, ind_data);\nglNamedBufferSubData(buffer, vrt_offset, vrt_len, vrt_data);\n\nglCreateVertexArrays(1, \u0026vao);\nglVertexArrayVertexBuffer(vao, 0, buffer, vrt_offset, sizeof(vertex));\nglVertexArrayElementBuffer(vao, buffer);\n\n//continue with setup\n```\n\nAnd then when it's time to render:\n\n```c\nglDrawElements(GL_TRIANGLES, ind_count, indices_format, (void *) ind_offset);\n```\n\nIf 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.\n\n## Ideal Way Of Retrieving All Uniform Names\n\nThere is material out there that teach beginners to retrieve uniform information by manually parsing the shader source strings, please don't do this.\n\nHere is how it should be done:\n\n```cpp\nstruct uniform_info\n{ \n\tGLint location;\n\tGLsizei count;\n};\n\nGLint uniform_count = 0;\nglGetProgramiv(program_name, GL_ACTIVE_UNIFORMS, \u0026uniform_count);\n\nif (uniform_count != 0)\n{\n\tGLint \tmax_name_len = 0;\n\tGLsizei length = 0;\n\tGLsizei count = 0;\n\tGLenum \ttype = GL_NONE;\n\tglGetProgramiv(program_name, GL_ACTIVE_UNIFORM_MAX_LENGTH, \u0026max_name_len);\n\t\n\tauto uniform_name = std::make_unique\u003cchar[]\u003e(max_name_len);\n\n\tstd::unordered_map\u003cstd::string, uniform_info\u003e uniforms;\n\n\tfor (GLint i = 0; i \u003c uniform_count; ++i)\n\t{\n\t\tglGetActiveUniform(program_name, i, max_name_len, \u0026length, \u0026count, \u0026type, uniform_name.get());\n\n\t\tuniform_info_t uniform_info = {};\n\t\tuniform_info.location = glGetUniformLocation(program_name, uniform_name.get());\n\t\tuniform_info.count = count;\n\n\t\tuniforms.emplace(std::make_pair(std::string(uniform_name.get(), length), uniform_info));\n\t}\n}\n```\n\nNote 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:\n```cpp\nglProgramUniformXX(program_name, uniforms[\"my_array[0]\"].location + 5, value);\n``` \nor if you want to modify the whole array:\n```cpp\nglProgramUniformXXv(program_name, uniforms[\"my_array[0]\"].location, uniforms[\"my_array[0]\"].count, my_array);\n```\n*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).*\n\nWith this you can store the uniform datatype and check it within your uniform update functions.\n\n## Texture Atlases vs Arrays\n\nArray 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.\n\nThey 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.\n\nThe advantages of using this over an atlas is that each layer is treated as a separate texture in terms of wrapping and mipmapping.\n\nArray textures come with three targets: `GL_TEXTURE_1D_ARRAY`, `GL_TEXTURE_2D_ARRAY`, and `GL_TEXTURE_CUBE_MAP_ARRAY`.\n\n2D 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.\n\nThere 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.\n\n2d array\n```\n|layer 0 \t|\n|\tlevel 1\t|\n|\tlevel 2\t|\n|layer 1 \t|\n|\tlevel 1\t|\n|\tlevel 2\t|\n...\n```\n\n3d texture\n```\n|z off 0 \t|\n|z off 1 \t|\n|z off 2 \t|\n...\n|\tlevel 1 |\n|\tlevel 2 |\n...\n```\n\nTo allocate a 2D texture array we do this:\n\n```c\nGLuint texarray = 0;\nGLsizei width = 512, height = 512, layers = 3;\nglCreateTextures(GL_TEXTURE_2D_ARRAY, 1, \u0026texarray);\nglTextureStorage3D(texarray, 1, GL_RGBA8, width, height, layers);\n```\n\n[`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.\n\nAnyway, uploading to individual layers is very straightforward:\n\n```c\nglTextureSubImage3D(texarray, mipmap_level, offset.x, offset.y, layer, width, height, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixels);\n```\n\nIt's super duper simple.\n\nThe most notable difference between arrays and atlases in terms of implementation lies in the shader.\n\nTo 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.\n\n```glsl\n#version 450 core\n\nlayout (location = 0) out vec4 color;\nlayout (location = 0) in vec2 tex0;\n\nuniform sampler2DArray texarray;\nuniform uint diffuse_layer;\n\nfloat layer2coord(uint capacity, uint layer)\n{\n\treturn max(0, min(float(capacity - 1), floor(float(layer) + 0.5)));\n}\n\nvoid main()\n{\n\tcolor = texture(texarray, vec3(tex0, layer2coord(3, diffuse_layer)));\n}\n```\n\nIdeally you should calculate the layer coordinate outside of the shader.\n\nYou 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.\n\nAlso, 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.\n\nAs 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.\n\nModern 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.\nWe'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.\n\nImage files loaded with [nothings](https://github.com/nothings)' [stb_image](https://github.com/nothings/stb) header library.\n\nHere it is:\n```cpp\nGLsizei image_w, image_h, c, tile_w = 16, tile_h = 16;\nstbi_uc* pixels = stbi_load(\".\\\\textures\\\\tiles_packed.png\", \u0026image_w, \u0026image_h, \u0026c, STBI_rgb_alpha);\nGLuint tileset;\nGLsizei\n\ttiles_x = image_w / tile_w,\n\ttiles_y = image_h / tile_h,\n\ttile_count = tiles_x * tiles_y;\n\nglCreateTextures(GL_TEXTURE_2D_ARRAY, 1, \u0026tileset);\nglTextureStorage3D(tileset, 1, GL_RGBA8, tile_w, tile_h, tile_count);\n\n{\n\tGLuint temp_tex = 0;\n\tglCreateTextures(GL_TEXTURE_2D, 1, \u0026temp_tex);\n\tglTextureStorage2D(temp_tex, 1, GL_RGBA8, image_w, image_h);\n\tglTextureSubImage2D(temp_tex, 0, 0, 0, image_w, image_h, GL_RGBA, GL_UNSIGNED_BYTE, pixels);\n\n\tfor (GLsizei i = 0; i \u003c tile_count; ++i)\n\t{\n\t\tGLint x = (i % tiles_x) * tile_w, y = (i / tiles_x) * tile_h;\n\t\tglCopyImageSubData(temp_tex, GL_TEXTURE_2D, 0, x, y, 0, tileset, GL_TEXTURE_2D_ARRAY, 0, 0, 0, i, tile_w, tile_h, 1);\n\t}\n\tglDeleteTextures(1, \u0026temp_tex);\n}\n\nstbi_image_free(pixels);\n```\n\n## Texture Views \u0026 Aliases\n\nTexture 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.\n\nViews are mostly indistinguishable from regular texture objects so you can use them as if they are.\n\nThe 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. \n\nWe 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).\n\nMaking the view itself is simple, it's only two function calls: [`glGenTextures`](http://docs.gl/gl4/glGenTextures) \u0026 [`glTextureView`](http://docs.gl/gl4/glTextureView).\n\nDespite 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.\n\n[`glTextureView`](http://docs.gl/gl4/glTextureView) is how we'll create the view.\n\nIf we were to have a typical 2d texture array and needed a view of layer 5 in isolation this is how it would look:\n\n```c\nglGenTextures(1, \u0026view_name);\nglTextureView(view_name, GL_TEXTURE_2D, src_name, internal_format, min_level, level_count, 5, 1);\n```\n\nWith this you can bind layer 5 alone as a `GL_TEXTURE_2D` texture.\n\nThis 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`.\n\nTexture 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. \n\nThe 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.\n\n[ARB_texture_view](https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_texture_view.txt)\n\n## Setting up Mix \u0026 Match Shaders with Program Pipelines\n\n* Nvidia drivers have spotty performance as they lean towards monolithic shader programs, so it may be better suited for non-performance-critical applications.\n\n[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.\n\nTo create and set up a simple program pipeline without any debugging looks like this:\n\n```cpp\nconst char*\n\tvs_source = load_file(\".\\\\main_shader.vs\").c_str(),\n\tfs_source = load_file(\".\\\\main_shader.fs\").c_str();\n\t\nGLuint \n\tvs = glCreateShaderProgramv(GL_VERTEX_SHADER, 1, \u0026vs_source),\n\tfs = glCreateShaderProgramv(GL_FRAGMENT_SHADER, 1, \u0026fs_source),\n\tpr = 0;\n\t\t\nglCreateProgramPipelines(1, \u0026pr);\nglUseProgramStages(pr, GL_VERTEX_SHADER_BIT, vs);\nglUseProgramStages(pr, GL_FRAGMENT_SHADER_BIT, fs);\n\nglBindProgramPipeline(pr);\n```\n\n[`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.\n\nBecause our shaders are now looser and flexible we need to get stricter with our input and output variables.\nEither 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.  \n\nI 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.\nInterface blocks also need to match members.\n\nAs 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.\n\nThe built-in block interfaces are defined as ([from the wiki](https://www.khronos.org/opengl/wiki/Built-in_Variable_(GLSL))):\n\nVertex:\n```glsl\nout gl_PerVertex\n{\n  vec4 gl_Position;\n  float gl_PointSize;\n  float gl_ClipDistance[];\n};\n```\n\nTesselation Control:\n```glsl\nout gl_PerVertex\n{\n  vec4 gl_Position;\n  float gl_PointSize;\n  float gl_ClipDistance[];\n} gl_out[];\n```\n\nTesselation Evaluation:\n```glsl\nout gl_PerVertex {\n  vec4 gl_Position;\n  float gl_PointSize;\n  float gl_ClipDistance[];\n};\n```\n\nGeometry:\n```glsl\n\nout gl_PerVertex\n{\n  vec4 gl_Position;\n  float gl_PointSize;\n  float gl_ClipDistance[];\n};\n```\n\nAn extremely basic vertex shader enabled for use in a pipeline object looks like this:\n```glsl\n#version 450\n\nout gl_PerVertex { vec4 gl_Position; };\n\nlayout (location = 0) in vec3 pos;\nlayout (location = 1) in vec3 col;\n\nlayout (location = 0) out v_out\n{\n    vec3 col;\n} v_out;\n\nvoid main()\n{\n    v_out.col = col;\n    gl_Position = vec4(pos, 1.0);\n}\n```\n\n## Faster Reads and Writes with Persistent Mapping\n\nWith [`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.\n\nFirst we need the right flags for both buffer storage creation and the mapping itself:\n```cpp\nconstexpr GLbitfield \n\tmapping_flags = GL_MAP_WRITE_BIT | GL_MAP_READ_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT,\n\tstorage_flags = GL_DYNAMIC_STORAGE_BIT | mapping_flags;\n```\n\n**GL_MAP_COHERENT_BIT**: \tThis flag ensures writes will be seen by the server when done from the client and vice versa.\n\n**GL_MAP_PERSISTENT_BIT**: \tThis tells our driver you wish to keep the mapping through subsequent OpenGL operations.\n\n**GL_MAP_READ_BIT**:\tTells OpenGL we wish to read from the buffer.\n\n**GL_MAP_WRITE_BIT**:\tLets OpenGL know we're gonna write to it, if you don't specify this *anything could happen*.\n\nIf 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.\n\nSetting up our immutable storage is straight forward:\n```cpp\nglCreateBuffers(1, \u0026name);\nglNamedBufferStorage(name, size, nullptr, storage_flags);\n```\nWhatever 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.\n\nHere is how we get that pointer we're after:\n```cpp\nvoid* ptr = glMapNamedBufferRange(name, offset, size, mapping_flags);\n```\n\nI 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.\n\nMake sure to unmap the buffer before deleting it:\n```cpp\nglUnmapNamedBuffer(name);\nglDeleteBuffers(1, \u0026name);\n```\n\nIf C++20 is available you can drop it into a std::span.\n\n## More information\n * [OpenGL wiki](https://www.khronos.org/opengl/wiki/).\n * [docs.GL](http://docs.gl/)\n * [DSA ARB specification](https://www.opengl.org/registry/specs/ARB/direct_state_access.txt).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffendevel%2FGuide-to-Modern-OpenGL-Functions","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffendevel%2FGuide-to-Modern-OpenGL-Functions","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffendevel%2FGuide-to-Modern-OpenGL-Functions/lists"}