#include #include #include #include #include #include #include #include #include #include #include "lephisto.h" #include "log.h" #include "pack.h" #include "opengl.h" /* Requirements. */ #define OPENGL_REQ_MULTITEX 2 /**< 2 is minimum OpenGL 1.2. Must have. */ glInfo gl_screen; /**< Give data of current opengl settings. */ Vec2* gl_camera; /**< Camera we are using. */ /* offsets to Adjust the pilot's place onscreen */ /*to be in the middle, even with the GUI. */ extern double gui_xoff; extern double gui_yoff; /* * Graphic list. */ typedef struct glTexList_ { struct glTexList_* next; /**< Next in linked list */ glTexture* tex; /**< Assosciated texture. */ int used; /**< Count how many times texture is being used. */ } glTexList; static glTexList* texture_list = NULL; /* Misc. */ static int SDL_VFlipSurface(SDL_Surface* surface); static int SDL_IsTrans(SDL_Surface* s, int x, int y); static uint8_t* SDL_MapTrans(SDL_Surface* s); /* glTexture. */ static GLuint gl_loadSurface(SDL_Surface* surface, int* rw, int* rh); static glTexture* gl_loadNewImage(const char* path); static void gl_blitTexture(const glTexture* texture, const double x, const double y, const double tx, const double ty, const glColour* c); /* PNG. */ int write_png(const char* file_name, png_bytep* rows, int w, int h, int colourtype, int bitdepth); /* Global. */ static GLboolean gl_hasExt(char* name); /* * MISC! */ /* Get the closest power of two. */ int gl_pot(int n) { int i = 1; while(i < n) i<<=1; return i; } /* Flips the surface vertically. Return 0 on success. */ static int SDL_VFlipSurface(SDL_Surface* surface) { /* Flip the image. */ Uint8* rowhi, *rowlo, *tmpbuf; int y; tmpbuf = malloc(surface->pitch); if(tmpbuf == NULL) { WARN("Out of memory"); return -1; } rowhi = (Uint8*)surface->pixels; rowlo = rowhi + (surface->h * surface->pitch) - surface->pitch; for(y = 0; y < surface->h / 2; ++y) { memcpy(tmpbuf, rowhi, surface->pitch); memcpy(rowhi, rowlo, surface->pitch); memcpy(rowlo, tmpbuf, surface->pitch); rowhi += surface->pitch; rowlo -= surface->pitch; } free(tmpbuf); /* Might be obvious, but I'm done flipping. */ return 0; } /* Return true if position (x,y) of s is transparent. */ static int SDL_IsTrans(SDL_Surface* s, int x, int y) { int bpp = s->format->BytesPerPixel; /* p is the address to the pixel we want to retrieve. */ Uint8* p = (Uint8*)s->pixels + y * s->pitch + x*bpp; Uint32 pixelcolour = 0; switch(bpp) { case 1: pixelcolour = *p; break; case 2: pixelcolour = *(Uint16*)p; break; case 3: if(SDL_BYTEORDER == SDL_BIG_ENDIAN) pixelcolour = p[0] << 16 | p[1] << 8 | p[2]; else pixelcolour = p[0] | p[1] << 8 | p[2] << 16; break; case 4: pixelcolour = *(Uint32*)p; break; } /* Test whetehr the pixels color is equal to color of */ /*transparent pixels for that surface. */ return (pixelcolour == s->format->colorkey); } /* Map the surface transparancy. */ /* Return 0 on success. */ static uint8_t* SDL_MapTrans(SDL_Surface* s) { /* Allocate memory for just enough bits to hold all the data we need. */ int size = s->w*s->h/8 + ((s->w*s->h%8)?1:0); uint8_t* t = malloc(size); memset(t, 0, size); /* *must* be set to zero. */ if(t == NULL) { WARN("Out of memeory"); return NULL; } int i, j; for(i = 0; i < s->h; i++) for(j = 0; j < s->w; j++) /* Set each bit to be 1 if not transparent or 0 if it is. */ t[(i*s->w+j)/8] |= (SDL_IsTrans(s,j,i)) ? 0 : (1<<((i*s->w+j)%8)); return t; } /* Take a screenshot. */ void gl_screenshot(const char* filename) { SDL_Surface* screen = SDL_GetVideoSurface(); unsigned rowbytes = screen->w * 3; unsigned char screenbuf[screen->h][rowbytes], *rows[screen->h]; int i; glReadPixels(0, 0, screen->w, screen->h, GL_RGB, GL_UNSIGNED_BYTE, screenbuf); for(i = 0; i < screen->h; i++) rows[i] = screenbuf[screen->h - i - 1]; write_png(filename, rows, screen->w, screen->h, PNG_COLOR_TYPE_RGB, 8); gl_checkErr(); } /* Save a surface to a file as a png. */ int SDL_savePNG(SDL_Surface* surface, const char* file) { static unsigned char** ss_rows; static int ss_size; static int ss_w, ss_h; SDL_Surface* ss_surface; SDL_Rect ss_rect; int r, i; int alpha = 0; int pixel_bits = 32; unsigned surf_flags; unsigned surf_alpha; ss_rows = 0; ss_size = 0; ss_surface = 0; ss_w = surface->w; ss_h = surface->h; if(surface->format->Amask) { alpha = 1; pixel_bits = 32; } else { pixel_bits = 24; } ss_surface = SDL_CreateRGBSurface(SDL_SWSURFACE | SDL_SRCALPHA, ss_w, ss_h, pixel_bits, RGBAMASK); if(ss_surface == 0) { return -1; } surf_flags = surface->flags & (SDL_SRCALPHA | SDL_SRCCOLORKEY); surf_alpha = surface->format->alpha; if(surf_flags & SDL_SRCALPHA) SDL_SetAlpha(surface, 0, SDL_ALPHA_OPAQUE); if(surf_flags & SDL_SRCCOLORKEY) SDL_SetColorKey(surface, 0, surface->format->colorkey); ss_rect.x = 0; ss_rect.y = 0; ss_rect.w = ss_w; ss_rect.h = ss_h; SDL_BlitSurface(surface, &ss_rect, ss_surface, 0); if(ss_size == 0) { ss_size = ss_h; ss_rows = (unsigned char**)malloc(sizeof(unsigned char*)*ss_size); if(ss_rows == 0) { return -1; } } if(surf_flags & SDL_SRCALPHA) SDL_SetAlpha(surface, SDL_SRCALPHA, (Uint8)surf_alpha); if(surf_flags & SDL_SRCCOLORKEY) SDL_SetColorKey(surface, SDL_SRCCOLORKEY, surface->format->colorkey); for(i = 0; i < ss_h; i++) { ss_rows[i] = ((unsigned char*)ss_surface->pixels) + i * ss_surface->pitch; } if(alpha) { r = write_png(file, ss_rows, surface->w, surface->h, PNG_COLOR_TYPE_RGB_ALPHA, 8); } else { r = write_png(file, ss_rows, surface->w, surface->h, PNG_COLOR_TYPE_RGB, 8); } free(ss_rows); SDL_FreeSurface(ss_surface); ss_surface = NULL; return r; } /* * ================ * TEXTURE! * ================ */ /* Returns the texture ID. * Stores real sizes in rw/rh (from POT padding). */ SDL_Surface* gl_prepareSurface(SDL_Surface* surface) { SDL_Surface* tmp; Uint32 saved_flags; Uint8 saved_alpha; int potw, poth; SDL_Rect rtemp; /* Make size power of two. */ potw = gl_pot(surface->w); poth = gl_pot(surface->h); rtemp.x = rtemp.y = 0; rtemp.w = surface->w; rtemp.h = surface->h; /* Save alpha. */ saved_flags = surface->flags & (SDL_SRCALPHA | SDL_RLEACCELOK); saved_alpha = surface->format->alpha; if((saved_flags & SDL_SRCALPHA) == SDL_SRCALPHA) SDL_SetAlpha(surface, 0, SDL_ALPHA_OPAQUE); if((saved_flags & SDL_SRCALPHA) == SDL_SRCALPHA) SDL_SetColorKey(surface, 0, surface->format->colorkey); /* Create the temp POT surface. */ tmp = SDL_CreateRGBSurface(SDL_SRCCOLORKEY, potw, poth, surface->format->BytesPerPixel*8, RGBAMASK); if(tmp == NULL) { WARN("Unable to create POT surface: %s", SDL_GetError()); return 0; } if(SDL_FillRect(tmp, NULL, SDL_MapRGBA(surface->format, 0, 0, 0, SDL_ALPHA_TRANSPARENT))) { WARN("Unable to fill rect: %s", SDL_GetError()); return 0; } /* Change the surface to the new blitted one. */ SDL_BlitSurface(surface, &rtemp, tmp, &rtemp); SDL_FreeSurface(surface); surface = tmp; /* Set saved alpha. */ if((saved_flags & SDL_SRCALPHA) == SDL_SRCALPHA) SDL_SetAlpha(surface, 0, 0); return surface; } /* * Return the texture id. * Stores real sizes in rw/rh (from POT padding). */ static GLuint gl_loadSurface(SDL_Surface* surface, int *rw, int* rh) { GLuint texture; surface = gl_prepareSurface(surface); if(rw != NULL) (*rw) = surface->w; if(rh != NULL) (*rh) = surface->h; /* Opengl texture binding. */ glGenTextures(1, &texture); /* Create the texure. */ glBindTexture(GL_TEXTURE_2D, texture); /* Load the texture. */ /* Filtering, LINEAR is better for scaling, nearest looks nicer, LINEAR */ /* also seems to create a bit of artifacts around the edges. */ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); /* Always wrap just in case. */ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); /* Now lead the texture data up. */ SDL_LockSurface(surface); glTexImage2D(GL_TEXTURE_2D, 0, surface->format->BytesPerPixel, surface->w, surface->h, 0, GL_RGBA, GL_UNSIGNED_BYTE, surface->pixels); /* Cleanup. */ SDL_UnlockSurface(surface); SDL_FreeSurface(surface); gl_checkErr(); return texture; } /* Load the SDL_Surface to an openGL texture. */ glTexture* gl_loadImage(SDL_Surface* surface) { int rw, rh; /* Set up the texture defaults. */ glTexture* texture = MALLOC_L(glTexture); texture->w = (double)surface->w; texture->h = (double)surface->h; texture->sx = 1.; texture->sy = 1.; texture->texture = gl_loadSurface(surface, &rw, &rh); texture->rw = (double)rw; texture->rh = (double)rh; texture->sw = texture->w; texture->sh = texture->h; texture->trans = NULL; texture->name = NULL; return texture; } /* Load the image if not already done. */ glTexture* gl_newImage(const char* path) { glTexList* cur, *last; /* Check to see if it already exists. */ if(texture_list != NULL) { for(cur = texture_list; cur != NULL; cur = cur->next) { if(strcmp(path, cur->tex->name)==0) { cur->used += 1; return cur->tex; } last = cur; } } /* Create the new node. */ cur = malloc(sizeof(glTexList)); cur->next = NULL; cur->used = 1; /* Load the image. */ cur->tex = gl_loadNewImage(path); if(texture_list == NULL) /* Special condition - creating new list. */ texture_list = cur; else last->next = cur; return cur->tex; } /* Load the image as an opengl texture directly. */ static glTexture* gl_loadNewImage(const char* path) { SDL_Surface* tmp, *surface; glTexture* t; uint8_t* trans = NULL; uint32_t filesize; char* buf; /* Load from packfile. */ buf = pack_readfile(DATA, (char*)path, &filesize); if(buf == NULL) { ERR("Loading surface from packfile."); return NULL; } SDL_RWops* rw = SDL_RWFromMem(buf, filesize); tmp = IMG_Load_RW(rw, 1); free(buf); if(tmp == 0) { ERR("'%s' could not be opened: %s", path, IMG_GetError()); return NULL; } surface = SDL_DisplayFormatAlpha(tmp); /* Sets the surface to what we use. */ if(surface == 0) { WARN("Error converting image to screen format: %s", SDL_GetError()); return NULL; } SDL_FreeSurface(tmp); /* Free the temp surface. */ /* We have to flip our surfaces to match the ortho. */ if(SDL_VFlipSurface(surface)) { WARN("Error flipping surface"); return NULL; } /* Do after flipping for collision detection. */ SDL_LockSurface(surface); trans = SDL_MapTrans(surface); SDL_UnlockSurface(surface); /* Set the texture. */ t = gl_loadImage(surface); t->trans = trans; t->name = strdup(path); return t; } /* Load the texture immediately, but also set is as a sprite. */ glTexture* gl_newSprite(const char* path, const int sx, const int sy) { glTexture* texture; if((texture = gl_newImage(path)) == NULL) return NULL; /* * Will possibly overwrite an existing textures properties * so we have to load some texture, always the same sprite. */ texture->sx = (double)sx; texture->sy = (double)sy; texture->sw = texture->w/texture->sx; texture->sh = texture->h/texture->sy; return texture; } /* Free the texture. */ void gl_freeTexture(glTexture* texture) { glTexList* cur, *last; /* Shouldn't be NULL (won't segfault though). */ if(texture == NULL) { WARN("Attempting to free NULL texture!"); return; } /* See if we can find it in stack. */ last = NULL; for(cur = texture_list; cur != NULL; cur = cur->next) { if(cur->tex == texture) { /* Found it! */ cur->used--; if(cur->used <= 0) { /* Not used anymore - Free the texture. */ glDeleteTextures(1, &texture->texture); if(texture->trans != NULL) free(texture->trans); if(texture->name != NULL) free(texture->name); free(texture); /* Free the list node. */ if(last == NULL) { /* Case there's no texture before it. */ if(cur->next != NULL) texture_list = cur->next; else /* Case it's the last texture. */ texture_list = NULL; } else last->next = cur->next; free(cur); } return; /* We already found it so we can exit. */ } last = cur; } /* Not found. */ if(texture->name != NULL) /* Surface will have NULL names. */ WARN("Attempring to free texture '%s' not found in stack!", texture->name); /* Free anyways. */ glDeleteTextures(1, &texture->texture); if(texture->trans != NULL) free(texture->trans); if(texture->name != NULL) free(texture->name); free(texture); gl_checkErr(); } /* Return true if pixel at pos (x,y) is transparent. */ int gl_isTrans(const glTexture* t, const int x, const int y) { return !(t->trans[(y*(int)(t->w)+x)/8] & (1<<((y*(int)(t->w)+x)%8))); } /* Set x and y to be the appropriate sprite for glTexture using dir. */ void gl_getSpriteFromDir(int* x, int* y, const glTexture* t, const double dir) { int s, sx, sy; double shard, rdir; /* What each image represents in angle. */ shard = 2.0*M_PI / (t->sy*t->sx); rdir = dir + shard/2.; if(rdir < 0.) rdir = 0.; /* Now calculate the sprite we need. */ s = (int)(rdir / shard); sx = t->sx; sy = t->sy; /* Make sure the sprite is "in range". */ if(s > (sy*sx-1)) s = s % (sy*sx); (*x) = s % sx; (*y) = s / sx; } /* ================ */ /* BLITTING! */ /* ================ */ static void gl_blitTexture(const glTexture* texture, const double x, const double y, const double tx, const double ty, const glColour* c) { double tw, th; /* Texture dimensions. */ tw = texture->sw / texture->rw; th = texture->sh / texture->rh; glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, texture->texture); glBegin(GL_QUADS); /* Set colour or default if not set. */ if(c == NULL) glColor4d(1., 1., 1., 1.); else COLOUR(*c); glTexCoord2d(tx, ty); glVertex2d(x, y); glTexCoord2d(tx + tw, ty); glVertex2d(x + texture->sw, y); glTexCoord2d(tx + tw, ty + th); glVertex2d(x + texture->sw, y + texture->sh); glTexCoord2d(tx, ty + th); glVertex2d(x, y + texture->sh); glEnd(); glDisable(GL_TEXTURE_2D); /* Did anything fail? */ gl_checkErr(); } /* Blit the sprite at given position. */ void gl_blitSprite(const glTexture* sprite, const double bx, const double by, const int sx, const int sy, const glColour* c) { double x, y, tx, ty; /* Calculate position - we'll use relative coords to player. */ x = bx - VX(*gl_camera) - sprite->sw/2. + gui_xoff; y = by - VY(*gl_camera) - sprite->sh/2. + gui_yoff; /* Check if inbounds. */ if((fabs(x) > SCREEN_W/2 + sprite->sw) || (fabs(y) > SCREEN_H/2 + sprite->sh)) return; /* Texture coords. */ tx = sprite->sw * (double)(sx)/sprite->rw; ty = sprite->sh * (sprite->sy-(double)sy-1)/sprite->rh; /* Actual blitting. */ gl_blitTexture(sprite, x, y, tx, ty, c); } /* Blit the sprite at pos (blits absolute position). */ void gl_blitStaticSprite(const glTexture* sprite, const double bx, const double by, const int sx, const int sy, const glColour* c) { double x, y, tx, ty; x = bx - (double)SCREEN_W/2.; y = by - (double)SCREEN_H/2.; /* Texture coords. */ tx = sprite->sw*(double)(sx)/sprite->rw; ty = sprite->sh*(sprite->sy-(double)sy-1)/sprite->rh; /* Actual blitting. */ gl_blitTexture(sprite, x, y, tx, ty, c); } /* Like gl_blitStatic but scales to size. */ void gl_blitScale(const glTexture* texture, const double bx, const double by, const double bw, const double bh, const glColour* c) { double x, y; double tw, th; double tx, ty; /* Here we use absolute coords. */ x = bx - (double)SCREEN_W/2.; y = by - (double)SCREEN_H/2.; /* Texture dimensions. */ tw = texture->sw / texture->rw; th = texture->sh / texture->rh; tx = ty = 0.; glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, texture->texture); glBegin(GL_QUADS); /* Set colour or default if not set. */ if(c == NULL) glColor4d(1., 1., 1., 1.); else COLOUR(*c); glTexCoord2d(tx, ty); glVertex2d(x, y); glTexCoord2d(tx+tw, ty); glVertex2d(x+bw, y); glTexCoord2d(tx+tw, ty+th); glVertex2d(x+bw, y+bh); glTexCoord2d(tx, ty+th); glVertex2d(x, y+bh); glEnd(); glDisable(GL_TEXTURE_2D); /* Anything failed? */ gl_checkErr(); } /* Just straight out blit the thing at position. */ void gl_blitStatic(const glTexture* texture, const double bx, const double by, const glColour* c) { double x, y; /* Here we use absolute coords. */ x = bx - (double)SCREEN_W/2.; y = by - (double)SCREEN_H/2.; /* Actual blitting.. */ gl_blitTexture(texture, x, y, 0, 0, c); } /* Bind our precious camera to a vector. */ void gl_bindCamera(const Vec2* pos) { gl_camera = (Vec2*)pos; } /* Draw circles. */ void gl_drawCircle(const double cx, const double cy, const double r) { double x, y, p; x = 0; y = r; p = (5. - (r*4.)) / 4.; glBegin(GL_POINTS); glVertex2d(cx, cy+y); glVertex2d(cx, cy-y); glVertex2d(cx+y, cy); glVertex2d(cx-y, cy); while(x < y) { x++; if(p < 0) p += 2*(double)(x)+1; else p += 2*(double)(x-(--y))+1; if(x == 0) { glVertex2d(cx, cy+y); glVertex2d(cx, cy-y); glVertex2d(cx+y, cy); glVertex2d(cx-y, cy); } else if(x == y) { glVertex2d(cx+x, cy+y); glVertex2d(cx-x, cy+y); glVertex2d(cx+x, cy-y); glVertex2d(cx-x, cy-y); } else if(x < y) { glVertex2d(cx+x, cy+y); glVertex2d(cx-x, cy+y); glVertex2d(cx+x, cy-y); glVertex2d(cx-x, cy-y); glVertex2d(cx+y, cy+x); glVertex2d(cx-y, cy+x); glVertex2d(cx+y, cy-x); glVertex2d(cx-y, cy-x); } } glEnd(); gl_checkErr(); } /* Draw a cirlce in a rect. */ #define PIXEL(x,y) \ if((x>rx) && (y>ry) && (x rxw) || (cy-r > ryh)) return; /* Can be drawn normally. */ else if((cx-r > rx) && (cy-r > ry) && (cx+r < rxw) && (cy+r < ryh)) { gl_drawCircle(cx, cy, r); return; } x = 0; y = r; p = (5. - (r*4.)) / 4.; glBegin(GL_POINTS); PIXEL(cx, cy+y); PIXEL(cx, cy-y); PIXEL(cx+y, cy); PIXEL(cx-y, cy); while(x < y) { x++; if(p < 0) p += 2*(double)(x)+1; else p += 2*(double)(x-(--y))+1; if(x == 0) { PIXEL(cx, cy+y); PIXEL(cx, cy-y); PIXEL(cx+y, cy); PIXEL(cx-y, cy); } else if(x == y) { PIXEL(cx+x, cy+y); PIXEL(cx-x, cy+y); PIXEL(cx+x, cy-y); PIXEL(cx-x, cy-y); } else if(x < y) { PIXEL(cx+x, cy+y); PIXEL(cx-x, cy+y); PIXEL(cx+x, cy-y); PIXEL(cx-x, cy-y); PIXEL(cx+y, cy+x); PIXEL(cx-y, cy+x); PIXEL(cx+y, cy-x); PIXEL(cx-y, cy-x); } } glEnd(); gl_checkErr(); } #undef PIXEL /* ================ */ /* GLOBAL. */ /* ================ */ /* Check for extensions. */ static GLboolean gl_hasExt(char* name) { /* ====================================================== */ /* Search for name in the extensions string. Use of strstr() */ /* is not sufficient because extensions names can be prefixes of */ /* other extension names. Could use strtol() but the constant */ /* string returned by glGetString can be in read-only memory. */ /* ====================================================== */ char* p, *end; size_t len, n; p = (char*) glGetString(GL_EXTENSIONS); len = strlen(name); end = p + strlen(p); while(p < end) { n = strcspn(p, " "); if((len == n) && (strncmp(name, p, n)==0)) return GL_TRUE; p += (n+1); } return GL_FALSE; } /* Check and report if there's been an error. */ /*#ifndef gl_checkErr // I know, I know, it's a little hackish. */ void gl_checkErr(void) { #ifdef DEBUG GLenum err; char* errstr; err = glGetError(); if(err == GL_NO_ERROR) return; /* No error. */ switch(err) { case GL_INVALID_ENUM: errstr = "GL invalid enum"; break; case GL_INVALID_VALUE: errstr = "GL invalid operation"; break; case GL_INVALID_OPERATION: errstr = "GL invalid operation"; break; case GL_STACK_OVERFLOW: errstr = "GL stack overflow"; break; case GL_STACK_UNDERFLOW: errstr = "GL stack underflow"; break; case GL_OUT_OF_MEMORY: errstr = "GL out of memory"; break; case GL_TABLE_TOO_LARGE: errstr = "GL table too large"; break; default: errstr = "GL unkown error"; break; } WARN("OpenGL error: %s", errstr); #endif } /*#endif */ /* Initialize SDL/OpenGL etc. */ int gl_init(void) { int doublebuf, depth, i, j, off, toff, supported; SDL_Rect** modes; int flags = SDL_OPENGL; flags |= SDL_FULLSCREEN * (gl_has(OPENGL_FULLSCREEN) ? 1: 0); supported = 0; /* Initializes video. */ if(SDL_InitSubSystem(SDL_INIT_VIDEO) < 0) { WARN("Unable to initialize SDL Video: %s", SDL_GetError()); return -1; } /* Set opengl flags. */ SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); /* Ideally want double buffering. */ if(gl_has(OPENGL_VSYNC)) SDL_GL_SetAttribute(SDL_GL_SWAP_CONTROL, 1); /* Get available fullscreen modes. */ if(gl_has(OPENGL_FULLSCREEN)) { modes = SDL_ListModes(NULL, SDL_OPENGL | SDL_FULLSCREEN); if(modes == NULL) { /* Could happen, but rare. */ WARN("No fullscreen modes available"); if(flags & SDL_FULLSCREEN) { WARN("Disabling fullscreen mode"); flags ^= SDL_FULLSCREEN; } } else if(modes == (SDL_Rect**)-1) DEBUG("All fullscreen modes available"); else { DEBUG("Available fullscreen modes:"); for(i = 0; modes[i]; i++) { DEBUG("\t%dx%d", modes[i]->w, modes[i]->h); if((flags & SDL_FULLSCREEN) && (modes[i]->w == SCREEN_W) && (modes[i]->h == SCREEN_H)) supported = 1; /* Mode we asked for is supported. */ } } /* Make sure fullscreen mode is supported. */ if((flags & SDL_FULLSCREEN) && !supported) { /* Try to get the closest aproximation to mode we asked for. */ off = -1; j = 0; for(i = 0; modes[i]; i++) { toff = ABS(SCREEN_W-modes[i]->w) + ABS(SCREEN_H-modes[i]->h); if((off == -1) || (toff < off)) { j = i; off = toff; } } WARN("Fullscreen mode %dx%d is not supported by your setup\n" " Switching to %dx%d", SCREEN_W, SCREEN_H, modes[j]->w, modes[j]->h); gl_screen.w = modes[j]->w; gl_screen.h = modes[j]->h; } } /* Test the setup - aim for 32. */ gl_screen.depth = 32; depth = SDL_VideoModeOK(SCREEN_W, SCREEN_H, gl_screen.depth, flags); if(depth == 0) WARN("Video mode %dx%d @ %d bpp not supported" " going to try to create it anyway...", SCREEN_W, SCREEN_H, gl_screen.depth); if(depth != gl_screen.depth) LOG("Depth: %d bpp unavailable, will use %d bpp", gl_screen.depth, depth); gl_screen.depth = depth; /* Actually creating the screen. */ if(SDL_SetVideoMode(SCREEN_W, SCREEN_H, gl_screen.depth, flags) == NULL) { ERR("Unable to create OpenGL window: %s", SDL_GetError()); return -1; } /* Grab some info. */ SDL_GL_GetAttribute(SDL_GL_RED_SIZE, &gl_screen.r); SDL_GL_GetAttribute(SDL_GL_GREEN_SIZE, &gl_screen.g); SDL_GL_GetAttribute(SDL_GL_BLUE_SIZE, &gl_screen.b); SDL_GL_GetAttribute(SDL_GL_ALPHA_SIZE, &gl_screen.a); SDL_GL_GetAttribute(SDL_GL_DOUBLEBUFFER, &doublebuf); if(doublebuf) gl_screen.flags |= OPENGL_DOUBLEBUF; gl_screen.depth = gl_screen.r + gl_screen.g + gl_screen.b + gl_screen.a; /* Get info about some extensions. */ if(gl_hasExt("GL_ARB_vertex_program")==GL_TRUE) gl_screen.flags |= OPENGL_VERT_SHADER; if(gl_hasExt("GL_ARB_fragment_program")==GL_TRUE) gl_screen.flags |= OPENGL_FRAG_SHADER; /* Texture information. */ glGetIntegerv(GL_MAX_TEXTURE_SIZE, &gl_screen.tex_max); glGetIntegerv(GL_MAX_TEXTURE_UNITS, &gl_screen.multitex_max); /* Debug heaven. */ DEBUG("OpenGL Window Created: %dx%d@%dbpp %s", SCREEN_W, SCREEN_H, gl_screen.depth, (gl_has(OPENGL_FULLSCREEN)) ? "fullscreen" : "window"); DEBUG("r: %d, g: %d, b: %d, a: %d, db: %s, tex: %d", gl_screen.r, gl_screen.g, gl_screen.b, gl_screen.a, gl_has(OPENGL_DOUBLEBUF) ? "yes" : "no", gl_screen.tex_max); DEBUG("Renderer: %s", glGetString(GL_RENDERER)); DEBUG("Version: %s", glGetString(GL_VERSION)); /* Now check for things that can be bad. */ if(gl_screen.multitex_max < OPENGL_REQ_MULTITEX) WARN("Missing texture units (%d required, %d found)", OPENGL_REQ_MULTITEX, gl_screen.multitex_max); if(!gl_has(OPENGL_FRAG_SHADER)) DEBUG("No fragment shader extension detected"); /* Not a warning yet. */ DEBUG(""); /* Some openGL options. */ glClearColor(0., 0., 0., 1.); /* Enable/Disable. */ glDisable(GL_DEPTH_TEST); /* Set for doing 2D shidazles. */ /*glEnable(GL_TEXTURE_2D); // Don't enable globally, it will break non-texture blits. */ glDisable(GL_LIGHTING); /* No lighting, it is done when rendered. */ glEnable(GL_BLEND); /* Alpha blending ftw. */ /* Models. */ glShadeModel(GL_FLAT); /* Default shade model. Functions should keep this when done.. */ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); /* Good blend model. */ /* Set up the matrix. */ gl_defViewport(); /* Finishing touches. */ glClear(GL_COLOR_BUFFER_BIT); /* Must clear the buffer first. */ gl_checkErr(); return 0; } /* Reset viewport to default. */ void gl_defViewport(void) { glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho(-SCREEN_W /2, /* Left edge. */ SCREEN_W /2, /* Right edge. */ -SCREEN_H /2, /* Bottom edge. */ SCREEN_H /2, /* Top edge. */ -1., /* Near. */ 1.); /* Far. */ } /* Clean up our mess. */ void gl_exit(void) { glTexList* tex; /* Make sure there's no texture leak. */ if(texture_list != NULL) { DEBUG("Texture leak detected!"); for(tex = texture_list; tex != NULL; tex = tex->next) DEBUG(" '%s' opened %d times", tex->tex->name, tex->used); } /* Shut down the subsystem. */ SDL_QuitSubSystem(SDL_INIT_VIDEO); } /* Saves a png. */ int write_png(const char* file_name, png_bytep* rows, int w, int h, int colourtype, int bitdepth) { png_structp png_ptr; png_infop info_ptr; FILE* fp = NULL; char* doing = "Open for writing"; if(!(fp = fopen(file_name, "wb"))) goto fail; doing = "Create png write struct"; if(!(png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL))) goto fail; doing = "Create png info struct"; if(!(info_ptr = png_create_info_struct(png_ptr))) goto fail; if(setjmp(png_jmpbuf(png_ptr))) goto fail; doing = "Init IO"; png_init_io(png_ptr, fp); png_set_IHDR(png_ptr, info_ptr, w, h, bitdepth, colourtype, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); doing = "Write info"; png_write_info(png_ptr, info_ptr); doing = "Write image"; png_write_image(png_ptr, rows); doing = "Write end"; png_write_end(png_ptr, NULL); doing = "Closing file"; if(0 != fclose(fp)) goto fail; return 0; fail: WARN("write_png: Could not %s", doing); return -1; }