{"id":705,"date":"2010-10-13T04:41:40","date_gmt":"2010-10-13T04:41:40","guid":{"rendered":"http:\/\/www.mrericsir.com\/blog\/?p=705"},"modified":"2023-07-14T17:08:19","modified_gmt":"2023-07-14T17:08:19","slug":"accelerated-2d-graphics-with-opengl-and-sdl-part-1","status":"publish","type":"post","link":"https:\/\/www.mrericsir.com\/blog\/technology\/accelerated-2d-graphics-with-opengl-and-sdl-part-1\/","title":{"rendered":"Accelerated 2D graphics with OpenGL and SDL: part 1"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">As some of you know, I&#8217;m working on an old-school puzzle game with <a href=\"http:\/\/s.hryx.net\/\">Hryx<\/a>. This game makes use of OpenGL&#8217;s graphics capabilities, but not for 3D graphics. Nope, the game is entirely two dimensional.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">So why OpenGL? Because it&#8217;s really, really fast on most systems. Much faster than the slowpoke graphics in SDL.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This is going to be part one of a two part series on doing 2D graphics using OpenGL. And step one? Getting some 2D sprites loaded.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Loading the sprite with SDL_image<\/strong><br>Now since we&#8217;re using SDL for our game, we decided on the excellent <a href=\"http:\/\/www.libsdl.org\/projects\/SDL_image\/\">SDL_image<\/a> to do the image loading.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Using SDL_image to load a sprite into memory is absurdly easy.<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<pre class=\"wp-block-preformatted\">string filename = \"myImage.png\";\nSDL_Surface* surface = IMG_Load( filename.c_str() );<\/pre>\n<\/blockquote>\n\n\n\n<p class=\"wp-block-paragraph\">That&#8217;s it!<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">But wait&#8230; that loads the graphic into an SDL surface. We need it to be an OpenGL texture! Damn.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>OpenGL textures<\/strong><br>In OpenGL, there&#8217;s no such thing as a &#8220;sprite.&#8221; You just have &#8220;textures.&#8221; So we&#8217;ll need to take our sprite and map it to a texture. The texture will be referenced with an integer (a GLuint) rather than a pointer.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">But there&#8217;s an interesting restriction we have to work around that SDL simply doesn&#8217;t have. On most systems, OpenGL textures must have dimensions are are powers of two: 2, 4, 8, 16, and so on.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">So once we load our SDL_Surface, we&#8217;ll make a new surface where we&#8217;ve rounded up the dimensions to the next highest power of two. The new surface will be filled with the alpha &#8220;color&#8221; and then we can blit (copy) over the existing image to the top left corner of our new image.<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<pre class=\"wp-block-preformatted\">SDL_PixelFormat *format = surface-&gt;format;\nUint32 width = surface-&gt;w;\nUint32 height = surface-&gt;h;\nUint32 widthPow = (unsigned) pow( 2, ceil( log( width ) \/ log( 2 ) ) );\nUint32 heightPow = (unsigned) pow( 2, ceil( log( height ) \/ log( 2 ) ) );\n\n\/\/ Create new empty surface.\nSDL_Surface* newSurface = SDL_CreateRGBSurface( SDL_SRCALPHA,\n\t   widthPow, heightPow, 32,\n\t   rmask, bmask, gmask, amask );\n\n\/\/ Fill sprite with alpha.\nUint32 alpha = 0;\nalpha = SDL_MapRGBA( format, 0, 0, 0, amask );\nSDL_Rect rect;\nrect.x = 0;\nrect.y = 0;\nrect.h = heightPow;\nrect.w = widthPow;\nint ret = SDL_FillRect( newSurface, &amp;rect, alpha);\nsurface-&gt;flags &amp;= !SDL_SRCALPHA;\n\nSDL_SetAlpha( newSurface, SDL_SRCALPHA, SDL_ALPHA_TRANSPARENT );\n\n\/\/ Copy image data to our new surface.\nret = SDL_BlitSurface( surface, 0, newSurface, 0 );<\/pre>\n<\/blockquote>\n\n\n\n<p class=\"wp-block-paragraph\">Great, we got our surface loaded. Now it&#8217;s time to create an OpenGL texture, then take our SDL surface and copy it to the texture.<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<pre class=\"wp-block-preformatted\">\/\/ This will be our OpenGL texture.\nGLuint texture = 0;\n\n\/\/ Bind the texture.\nglPixelStorei(GL_UNPACK_ALIGNMENT,4);\nglGenTextures(1, &amp;texture );\nglBindTexture(GL_TEXTURE_2D, texture);\n\n\/\/ Convert surface to Open GL format.\ngluBuild2DMipmaps(GL_TEXTURE_2D, 4,\n\twidthPow, heightPow, GL_RGBA,GL_UNSIGNED_BYTE, newSurface-&gt;pixels);\n\n\/\/ Free our temporary SDL buffers.\nSDL_FreeSurface( surface );\nSDL_FreeSurface( newSurface );<\/pre>\n<\/blockquote>\n\n\n\n<p class=\"wp-block-paragraph\">Easy, huh?<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Dealing with 24-bit surfaces<\/strong><br>Up until now we&#8217;ve only dealt with is 32-bit images. One thing I hadn&#8217;t dealt with until now is 24-bit color-keyed images. A color-keyed image means you have RGB but no alpha or &#8220;A&#8221; channel to tell you what should be invisible. Instead, you just take one color (often black) and just say that it&#8217;s 100% transparent.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">What we&#8217;re going to have to do is make the SDL_surface color keyed before we copy it to the OpenGL texture.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">First, we&#8217;re going to need some functions for plotting individual pixels on an SDL texture. Unfortunately there&#8217;s no built in functions for this, so I stole some from <a href=\"http:\/\/www.gamedev.net\/reference\/programming\/features\/sdl2\/page5.asp\">this site.<\/a><\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<pre class=\"wp-block-preformatted\">Uint32 GetPixel( SDL_Surface *surface, int x, int y )\n{\n\tUint32 color = 0;\n\tchar* pPosition = ( char* ) surface-&gt;pixels;\n\tpPosition += ( surface-&gt;pitch * y );\n\tpPosition += ( surface-&gt;format-&gt;BytesPerPixel * x );\n\tmemcpy ( &amp;color , pPosition , surface-&gt;format-&gt;BytesPerPixel );\n\treturn color;\n}\n\nvoid SetPixel( SDL_Surface *surface, int x, int y, Uint32 color )\n{\n\tchar* pPosition = ( char* ) surface-&gt;pixels;\n\tpPosition += ( surface-&gt;pitch * y );\n\tpPosition += ( surface-&gt;format-&gt;BytesPerPixel * x );\n\tmemcpy ( pPosition , &amp;color , surface-&gt;format-&gt;BytesPerPixel );\n}<\/pre>\n<\/blockquote>\n\n\n\n<p class=\"wp-block-paragraph\">Cool, now we can plot pixels! Feel free to go crazy and write a routine to draw circles or something!<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">But all we really need to do here is to take pixels that correspond with our color key and set the alpha channel to the alpha mask channel color.<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<pre class=\"wp-block-preformatted\">if ( 24 == format-&gt;BitsPerPixel )\n{\n   Uint32 colorKey = format-&gt;colorkey;\n\n   for ( int y = 0; y &lt; surface-&gt;h; ++y )\n   {\n\t   for (int x = 0; x &lt; surface-&gt;w; x++)\n\t   {\n\t\t   Uint32 color = GetPixel( surface, x, y );\n\t\t   if ( color == colorKey )\n\t\t   {\n\t\t\t   SetPixel( newSurface, x, y, 0 );\n\t\t   }\n\t   }\n   }\n}<\/pre>\n<\/blockquote>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Putting it all together<\/strong><br>Here is the final code to load an image file to a texture using SDL.<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<pre class=\"wp-block-preformatted\">#include \n#include \n#include \n#include \n#include \n\n#if SDL_BYTEORDER == SDL_LIL_ENDIAN\n   static const Uint32 rmask = 0x000000FF;\n   static const Uint32 bmask = 0x0000FF00;\n   static const Uint32 gmask = 0x00FF0000;\n   static const Uint32 amask = 0xFF000000;\n#else\n   static const Uint32 rmask = 0xFF000000;\n   static const Uint32 bmask = 0x00FF0000;\n   static const Uint32 gmask = 0x0000FF00;\n   static const Uint32 amask = 0x000000FF;\n#endif\n\n\n\nUint32 GetPixel( SDL_Surface *surface, int x, int y )\n{\n\tUint32 color = 0;\n\tchar* pPosition = ( char* ) surface-&gt;pixels;\n\tpPosition += ( surface-&gt;pitch * y );\n\tpPosition += ( surface-&gt;format-&gt;BytesPerPixel * x );\n\tmemcpy ( &amp;color , pPosition , surface-&gt;format-&gt;BytesPerPixel );\n\treturn color;\n}\n\n\nvoid SetPixel( SDL_Surface *surface, int x, int y, Uint32 color )\n{\n\tchar* pPosition = ( char* ) surface-&gt;pixels;\n\tpPosition += ( surface-&gt;pitch * y );\n\tpPosition += ( surface-&gt;format-&gt;BytesPerPixel * x );\n\tmemcpy ( pPosition , &amp;color , surface-&gt;format-&gt;BytesPerPixel );\n}\n\n\nGLuint loadTexture( std::string filename )\n{\n\tSDL_Surface* surface = IMG_Load( filename.c_str() );\n\n\tif ( NULL == surface )\n\t{\n\t   \/\/ Error!\n\t   return 0;\n\t}\n\n\n\tSDL_PixelFormat *format = surface-&gt;format;\n\tUint32 width = surface-&gt;w;\n\tUint32 height = surface-&gt;h;\n\tUint32 widthPow = (unsigned) pow( 2, ceil( log( width ) \/ log( 2 ) ) );\n\tUint32 heightPow = (unsigned) pow( 2, ceil( log( height ) \/ log( 2 ) ) );\n\n\t\/\/ Create new empty surface.\n\tSDL_Surface* newSurface = SDL_CreateRGBSurface( SDL_SRCALPHA,\n\t\t   widthPow, heightPow, 32,\n\t\t   rmask, bmask, gmask, amask );\n\n\t\/\/ Fill sprite with alpha.\n\tUint32 alpha = 0;\n\talpha = SDL_MapRGBA( format, 0, 0, 0, amask );\n\tSDL_Rect rect;\n\trect.x = 0;\n\trect.y = 0;\n\trect.h = heightPow;\n\trect.w = widthPow;\n\tint ret = SDL_FillRect( newSurface, &amp;rect, alpha);\n\tsurface-&gt;flags &amp;= !SDL_SRCALPHA;\n\n\tSDL_SetAlpha( newSurface, SDL_SRCALPHA, SDL_ALPHA_TRANSPARENT );\n\n\t\/\/ Copy image data to our new surface.\n\tret = SDL_BlitSurface( surface, 0, newSurface, 0 );\n\n\t\/\/ Change color key to alpha transparency.\n\t\/\/ Used for 24-bit images.\n\tif ( 24 == format-&gt;BitsPerPixel )\n\t{\n\t   Uint32 colorKey = format-&gt;colorkey;\n\n\t   for ( int y = 0; y &lt; surface-&gt;h; ++y )\n\t   {\n\t\t   for (int x = 0; x &lt; surface-&gt;w; x++)\n\t\t   {\n\t\t\t   Uint32 color = GetPixel( surface, x, y );\n\t\t\t   if ( color == colorKey )\n\t\t\t   {\n\t\t\t\t   SetPixel( newSurface, x, y, 0 );\n\t\t\t   }\n\t\t   }\n\t   }\n\t}\n\n\t\/\/ This will be our OpenGL texture.\n\tGLuint texture = 0;\n\n\t\/\/ Bind the texture.\n\tglPixelStorei(GL_UNPACK_ALIGNMENT,4);\n\tglGenTextures(1, &amp;texture );\n\tglBindTexture(GL_TEXTURE_2D, texture);\n\n\t\/\/ Convert surface to Open GL format.\n\tgluBuild2DMipmaps(GL_TEXTURE_2D, 4,\n\t\twidthPow, heightPow, GL_RGBA,GL_UNSIGNED_BYTE, \n                newSurface-&gt;pixels);\n\n\t\/\/ Free our temporary SDL buffers.\n\tSDL_FreeSurface( surface );\n\tSDL_FreeSurface( newSurface );\n\n\treturn texture;\n}<\/pre>\n<\/blockquote>\n\n\n\n<p class=\"wp-block-paragraph\">That&#8217;s it! Next time: displaying our new OpenGL texture on the screen as a 2d sprite.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>As some of you know, I&#8217;m working on an old-school puzzle game with Hryx. This game makes use of OpenGL&#8217;s graphics capabilities, but not for [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_eb_attr":"","slim_seo":{"title":"Accelerated 2D graphics with OpenGL and SDL: part 1 - MrEricSir.com","description":"As some of you know, I'm working on an old-school puzzle game with Hryx . This game makes use of OpenGL's graphics capabilities, but not for 3D graphics. Nope,"},"autoblue_enabled":true,"autoblue_custom_message":"","autoblue_shares":[],"autoblue_post_url":"","autoblue_publish_document":false,"footnotes":""},"categories":[3],"tags":[40,277,29,278],"class_list":["post-705","post","type-post","status-publish","format-standard","hentry","category-technology","tag-c","tag-opengl","tag-programming","tag-stl"],"_links":{"self":[{"href":"https:\/\/www.mrericsir.com\/blog\/wp-json\/wp\/v2\/posts\/705","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.mrericsir.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.mrericsir.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.mrericsir.com\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.mrericsir.com\/blog\/wp-json\/wp\/v2\/comments?post=705"}],"version-history":[{"count":17,"href":"https:\/\/www.mrericsir.com\/blog\/wp-json\/wp\/v2\/posts\/705\/revisions"}],"predecessor-version":[{"id":10437,"href":"https:\/\/www.mrericsir.com\/blog\/wp-json\/wp\/v2\/posts\/705\/revisions\/10437"}],"wp:attachment":[{"href":"https:\/\/www.mrericsir.com\/blog\/wp-json\/wp\/v2\/media?parent=705"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.mrericsir.com\/blog\/wp-json\/wp\/v2\/categories?post=705"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.mrericsir.com\/blog\/wp-json\/wp\/v2\/tags?post=705"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}