#include #include #include #include #include #include #include #include #include #include "lephisto.h" #include "log.h" #include "pack.h" #include "opengl.h" #define SCREEN_W gl_screen.w #define SCREEN_H gl_screen.h // 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; // The screen info, gives data of current opengl settings. glInfo gl_screen; // Our precious camera. Vec2* gl_camera; // 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); static int pot(int n); // glTexture. static GLuint gl_loadSurface(SDL_Surface* surface, int* rw, int* rh); // PNG. int write_png(const char* file_name, png_bytep* rows, int w, int h, int colourtype, int bitdepth); // ================ // MISC! // ================ // Get the closest power of two. static int 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); } // ================ // TEXTURE! // ================ // Returns 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; SDL_Surface* tmp; Uint32 saved_flags; Uint8 saved_alpha; int potw, poth; // Make size power of two. potw = pot(surface->w); poth = pot(surface->h); if(rw)*rw = potw; if(rh)*rh = poth; if((surface->w != potw) || (surface->h != poth)) { // Size isn't original. SDL_Rect rtemp; 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, 0); // Create the temp POT surface. tmp = SDL_CreateRGBSurface(SDL_SRCCOLORKEY, potw, poth, surface->format->BytesPerPixel*8, RGBMASK); 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; } 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); // Create the temp POT surface. tmp = SDL_CreateRGBSurface(SDL_SRCCOLORKEY, potw, poth, surface->format->BytesPerPixel*8, RGBMASK); 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; } SDL_BlitSurface(surface, &rtemp, tmp, &rtemp); SDL_FreeSurface(surface); surface = tmp; // Set saved alpha. if((saved_flags & SDL_SRCALPHA) == SDL_SRCALPHA) SDL_SetAlpha(surface, saved_flags, saved_alpha); } 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); SDL_LockSurface(surface); glTexImage2D(GL_TEXTURE_2D, 0, surface->format->BytesPerPixel, surface->w, surface->h, 0, GL_RGBA, GL_UNSIGNED_BYTE, surface->pixels); SDL_UnlockSurface(surface); SDL_FreeSurface(surface); 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; return texture; } // Load the image directly as an opengl texture. glTexture* gl_newImage(const char* path) { SDL_Surface* tmp, *surface; glTexture* t; uint8_t* trans = NULL; uint32_t filesize; char* 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. 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); t = gl_loadImage(surface); t->trans = trans; 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; 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) { glDeleteTextures(1, &texture->texture); if(texture->trans) free(texture->trans); free(texture); } // 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; s = (int)(dir / (2.0*M_PI / (t->sy*t->sx))); 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! // ================ // 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) { // Don't bother drawing if offscreen -- waste of cycles. if(fabs(bx-VX(*gl_camera)+gui_xoff) > gl_screen.w / 2 + sprite->sw / 2 || fabs(by-VY(*gl_camera)+gui_yoff) > gl_screen.h / 2 + sprite->sh / 2) return; double x, y, tx, ty; glEnable(GL_TEXTURE_2D); x = bx - VX(*gl_camera) - sprite->sw/2. + gui_xoff; y = by - VY(*gl_camera) - sprite->sh/2. + gui_yoff; tx = sprite->sw * (double)(sx)/sprite->rw; ty = sprite->sh * (sprite->sy-(double)sy-1)/sprite->rh; // Actual blitting.... glBindTexture(GL_TEXTURE_2D, sprite->texture); glBegin(GL_QUADS); if(c == NULL) glColor4d(1., 1., 1., 1.); else COLOUR(*c); glTexCoord2d(tx, ty); glVertex2d(x, y); glTexCoord2d(tx + sprite->sw/sprite->rw, ty); glVertex2d(x + sprite->sw, y); glTexCoord2d(tx + sprite->sw/sprite->rw, ty + sprite->sh/sprite->rh); glVertex2d(x + sprite->sw, y + sprite->sh); glTexCoord2d(tx, ty + sprite->sh/sprite->rh); glVertex2d(x, y + sprite->sh); glEnd(); glDisable(GL_TEXTURE_2D); } // 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; glEnable(GL_TEXTURE_2D); x = bx - (double)gl_screen.w/2.; y = by - (double)gl_screen.h/2.; // Actual blitting.. glBindTexture(GL_TEXTURE_2D, texture->texture); glBegin(GL_QUADS); if(c == NULL) glColor4d(1., 1., 1., 1.); else COLOUR(*c); glTexCoord2d(0., 0.); glVertex2d(x, y); glTexCoord2d(texture->sw/texture->rw, 0.); glVertex2d(x + texture->sw, y); glTexCoord2d(texture->sw/texture->rw, texture->sh/texture->rh); glVertex2d(x + texture->sw, y + texture->sh); glTexCoord2d(0., texture->sh/texture->rh); glVertex2d(x, y + texture->sh); glEnd(); glDisable(GL_TEXTURE_2D); } // Bind our precious camera to a vector. void gl_bindCamera(const Vec2* pos) { gl_camera = (Vec2*)pos; } // ================ // GLOBAL. // ================ // Initialize SDL/OpenGL etc. int gl_init(void) { int doublebuf, depth, i, j, off, toff, supported = 0; SDL_Rect** modes; int flags = SDL_OPENGL; flags |= SDL_FULLSCREEN * (gl_has(OPENGL_FULLSCREEN) ? 1: 0); // Initializes video. if(SDL_InitSubSystem(SDL_INIT_VIDEO) < 0) { WARN("Unable to initialize SDL: %s", SDL_GetError()); return -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 == gl_screen.w) && (modes[i]->h == gl_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(gl_screen.w-modes[i]->w) + ABS(gl_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", gl_screen.w, gl_screen.h, modes[j]->w, modes[j]->h); gl_screen.w = modes[j]->w; gl_screen.h = modes[j]->h; } // Free the video modes. for(i = 0; modes[i]; ++i) free(modes[i]); free(modes); } // Test the setup. depth = SDL_VideoModeOK(gl_screen.w, gl_screen.h, gl_screen.depth, flags); if(depth != gl_screen.depth) WARN("Depth: %d bpp unavailable, will use %d bpp", gl_screen.depth, depth); gl_screen.depth = depth; // Actually creating the screen. if(SDL_SetVideoMode(gl_screen.w, gl_screen.h, gl_screen.depth, flags) == NULL) { ERR("Unable to create OpenGL window: %s", SDL_GetError()); SDL_Quit(); 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; // Debug heaven. DEBUG("OpenGL Window Created: %dx%d@%dbpp %s", gl_screen.w, gl_screen.h, gl_screen.depth, (gl_has(OPENGL_FULLSCREEN)) ? "fullscreen" : "window"); DEBUG("r: %d, g: %d, b: %d, a: %d, doublebuffer: %s", gl_screen.r, gl_screen.g, gl_screen.b, gl_screen.a, (gl_has(OPENGL_DOUBLEBUF)) ? "yes" : "no"); DEBUG("Renderer: %s", glGetString(GL_RENDERER)); // Some openGL options. glClearColor(0., 0., 0., 1.); 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); glShadeModel(GL_FLAT); // Default shade model. Functions should keep this when done.. glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 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. glClear(GL_COLOR_BUFFER_BIT); return 0; } // Clean up our mess. void gl_exit(void) { } // 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; }