PortableGL 0.98 MIT licensed software renderer that closely mirrors OpenGL 3.x portablegl.com robertwinkler.com Do this: #define PORTABLEGL_IMPLEMENTATION before you include this file in *one* C or C++ file to create the implementation. If you plan on using your own 3D vector/matrix library rather than crsw_math that is built into PortableGL and your names are the standard glsl vec[2-4], mat[3-4] etc., define PGL_PREFIX_TYPES too before including portablegl to prefix all those builtin types with pgl_ to avoid the clash. Note, if you use PGL_PREFIX_TYPES and write your own shaders, the type for vertex_attribs is also affected, changing from vec4* to pgl_vec4*. You can check all the C++ examples and demos, I use my C++ rsw_math library. // i.e. it should look like this: #include ... #include ... #include ... // if required #define PGL_PREFIX_TYPES #define PORTABLEGL_IMPLEMENTATION #include "portablegl.h" You can define PGL_ASSERT before the #include to avoid using assert.h. You can define PGL_MALLOC, PGL_REALLOC, and PGL_FREE to avoid using malloc, realloc, and free. You can define PGL_MEMMOVE to avoid using memmove. However, even if you define all of those before including portablegl, you will still be using the standard library (math.h, string.h, stdlib.h, stdio.h stdint.h, possibly others). It's not worth removing PortableGL's dependency on the C standard library as it would make it far larger and more complicated for no real benefit. QUICK NOTES: Primarily of interest to game/graphics developers and other people who just want to play with the graphics pipeline and don't need peak performance or the the entirety of OpenGL or Vulkan features. For textures, GL_UNSIGNED_BYTE is the only supported type. Internally, GL_RGBA is the only supported format, however other formats are converted automatically to RGBA unless PGL_DONT_CONVERT_TEXTURES is defined (in which case a format other than GL_RGBA is a GL_INVALID_ENUM error). The argument internalFormat is ignored to ease porting. Only GL_TEXTURE_MAG_FILTER is actually used internally but you can set the MIN_FILTER for a texture. Mipmaps are not supported (GenerateMipMap is a stub and the level argument is ignored/assumed 0) and *MIPMAP* filter settings are silently converted to NEAREST or LINEAR. 8-bit per channel RGBA is the only supported format for the framebuffer. You can specify the order using the masks in init_glContext. Technically it'd be relatively trivial to add support for other formats but for now we use a u32* to access the buffer. DOCUMENTATION ============= Any PortableGL program has roughly this structure, with some things possibly declared globally or passed around in function parameters as needed: #define WIDTH 640 #define HEIGHT 480 // shaders are functions matching these prototypes void smooth_vs(float* vs_output, vec4* vertex_attribs, Shader_Builtins* builtins, void* uniforms); void smooth_fs(float* fs_input, Shader_Builtins* builtins, void* uniforms); typedef struct My_Uniforms { mat4 mvp_mat; vec4 v_color; } My_Uniforms; u32* backbuf = NULL; glContext the_context; if (!init_glContext(&the_context, &backbuf, WIDTH, HEIGHT, 32, 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000)) { puts("Failed to initialize glContext"); exit(0); } // interpolation is an array with an entry of PGL_SMOOTH, PGL_FLAT or // PGL_NOPERSPECTIVE for each float being interpolated between the // vertex and fragment shaders. Convenience macros are available // for 2, 3, and 4 components, ie // PGL_FLAT3 expands to PGL_FLAT, PGL_FLAT, PGL_FLAT // the last parameter is whether the fragment shader writes to // gl_FragDepth or discard. When it is false, PGL may do early // fragment processing (scissor, depth, stencil etc) for a minor // performance boost but canonicaly these happen after the frag // shader GLenum interpolation[4] = { PGL_SMOOTH4 }; GLuint myshader = pglCreateProgram(smooth_vs, smooth_fs, 4, interpolation, GL_FALSE); glUseProgram(myshader); // Red is not actually used since we're using per vert color My_Uniform the_uniforms = { IDENTITY_MAT4(), Red }; pglSetUniform(&the_uniforms); // Your standard OpenGL buffer setup etc. here // Like the compatibility profile, we allow/enable a default // VAO. We also have a default shader program for the same reason, // something to fill index 0. // see implementation of init_glContext for details while (1) { // standard glDraw calls, switching shaders etc. // use backbuf however you want, whether that's blitting // it to some framebuffer in your GUI system, or even writing // it out to disk with something like stb_image_write. } free_glContext(&the_context); // compare with equivalent glsl below void smooth_vs(float* vs_output, vec4* vertex_attribs, Shader_Builtins* builtins, void* uniforms) { ((vec4*)vs_output)[0] = vertex_attribs[1]; //color builtins->gl_Position = mult_mat4_vec4(*((mat4*)uniforms), vertex_attribs[0]); } void smooth_fs(float* fs_input, Shader_Builtins* builtins, void* uniforms) { builtins->gl_FragColor = ((vec4*)fs_input)[0]; } // note smooth is the default so this is the same as smooth out vec4 vary_color // https://www.khronos.org/opengl/wiki/Type_Qualifier_(GLSL)#Interpolation_qualifiers uniform mvp_mat layout (location = 0) in vec4 in_vertex; layout (location = 1) in vec4 in_color; out vec4 vary_color; void main(void) { vary_color = in_color; gl_Position = mvp_mat * in_vertex; } in vec4 vary_color; out vec4 frag_color; void main(void) { frag_color = vary_color; } That's basically it. There are some other non-standard features like pglSetInterp that lets you change the interpolation of a shader whenever you want. In real OpenGL you'd have to have 2 (or more) separate but almost identical shaders to do that. ADDITIONAL CONFIGURATION ======================== We've already mentioned several configuration macros above but here are all of them: PGL_PREFIX_TYPES This prefixes the standard glsl types (and a couple other internal types) with pgl_ (ie vec2 becomes pgl_vec2) PGL_ASSERT PGL_MALLOC/PGL_REALLOC/PGL_FREE PGL_MEMMOVE These overwride the standard functions of the same names PGL_DONT_CONVERT_TEXTURES This makes passing PGL a texture with a format other than GL_RGBA an error. By default other types are automatically converted. You can perform the conversion manually using the function convert_format_to_packed_rgba(). The included function convert_grayscale_to_rgba() is also useful, especially for font textures. PGL_PREFIX_GLSL or PGL_SUFFIX_GLSL These replace PGL_EXCLUDE_GLSL. Since PGL depends on at least a few glsl functions and potentially more in the future it doesn't make sense to exclude GLSL entirely, especially since they're all inline so it really doesn't save you anything in the final executable. Instead, you can using one of these two macros you can change the handful of functions that are likely to cause a conflict with an external math library like glm (with a using declaration/directive of course). So mix() would become either pgl_mix() or mixf(). So far it is less than 10 functions that are modified but feel free to add more. PGL_HERMITE_SMOOTHING Turn on hermite smoothing when doing linear interpolation of textures. It is not required by the spec and it does slow it down but it does look smoother so it's worth trying if your curious. Note, most implementations do not use it. PGL_SIMPLE_THICK_LINES If defined, use a simpler (and less correct) thick line drawing algorithm. It is (currently) about 17-18% faster than the default algorithm. It draws lines that have LineWidth pixels along the x or y axis (whichever is closest to perpendicular) but this makes the line thinner than it should be the more diagonal the line. The ends also look wrong. Despite this many implementations use this (or a similar) algorithm but cap the thickness at a relatively low number (like 8) so the problems are less obvious. There are also these predefined maximums which you can change. However, considering the performance limitations of PortableGL, they are probably more than enough. MAX_DRAW_BUFFERS and MAX_COLOR_ATTACHMENTS aren't used since those features aren't implemented. PGL_MAX_VERTICES refers to the number of output vertices of a single draw call. It's mostly there as a sanity check, not a real limitation. #define PGL_MAX_VERTICES 500000 #define GL_MAX_VERTEX_ATTRIBS 8 #define GL_MAX_VERTEX_OUTPUT_COMPONENTS (4*GL_MAX_VERTEX_ATTRIBS) #define GL_MAX_DRAW_BUFFERS 4 #define GL_MAX_COLOR_ATTACHMENTS 4 MIT License Copyright (c) 2011-2023 Robert Winkler Copyright (c) 1997-2023 Fabrice Bellard (clipping code from TinyGL) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.