From 58810746abaf70e4862d276c92014aa018b47574 Mon Sep 17 00:00:00 2001 From: Kevin Cathcart Date: Sat, 12 Jul 2014 18:05:43 -0400 Subject: [PATCH] Add OpenGL Support The OpenGL code used is probably substandard, and using a single GL_TEXTURE_RECTANGLE for the overlay may not be ideal but it seems to work, although really old graphics cards or software mode GL may not like it. There is definately room for improvement by a GL expert. --- Overlay.cpp | 234 ++++++++++++++++++++++++++++++++++++++++++---------- Overlay.h | 67 ++++++++++++++- 2 files changed, 255 insertions(+), 46 deletions(-) diff --git a/Overlay.cpp b/Overlay.cpp index fc7d05de..d6aa0fc7 100644 --- a/Overlay.cpp +++ b/Overlay.cpp @@ -9,11 +9,21 @@ #include "df/viewscreen_dungeonmodest.h" +struct renderer_custom : renderer_opengl +{ + virtual void draw(int vertex_count); +}; + +//Globals used to support OpenGL rendering mode +extern Overlay* overlay; +static bool is_hooked=false; +static draw_ptr old_draw; + DFhackCExport void * SDL_GetVideoSurface(void); DFhackCExport vPtr SDL_CreateRGBSurface(uint32_t flags, int width, int height, int depth, uint32_t Rmask, uint32_t Gmask, uint32_t Bmask, uint32_t Amask); DFhackCExport vPtr SDL_CreateRGBSurfaceFrom(vPtr pixels, int width, int height, int depth, int pitch, - uint32_t Rmask, uint32_t Gmask, uint32_t Bmask, uint32_t Amask); + uint32_t Rmask, uint32_t Gmask, uint32_t Bmask, uint32_t Amask); DFhackCExport void SDL_FreeSurface(vPtr surface); DFhackCExport int SDL_UpperBlit(DFHack::DFSDL_Surface* src, DFHack::DFSDL_Rect* srcrect, DFHack::DFSDL_Surface* dst, DFHack::DFSDL_Rect* dstrect); @@ -27,15 +37,21 @@ void Overlay::ReadTileLocations() actualWindowSize(width, height); DFHack::DFSDL_Surface * dfsurf = (DFHack::DFSDL_Surface *) SDL_GetVideoSurface(); - offsetx = fontx+((dfsurf->w) % fontx)/2; - offsety = fontx+((dfsurf->h) % fonty)/2; + + offsetx_tile=1; + offsety_tile=1; + if (!df::global::gamemode || *df::global::gamemode == game_mode::ADVENTURE) { //Adventure mode doesn't have a single-tile border around it. - offsetx = offsetx - fontx; - offsety = offsety - fonty; + offsetx_tile = 0; + offsety_tile = 0; } + offsetx_pixel = fontx * offsetx_tile + ((dfsurf->w) % fontx)/2; + offsety_pixel = fonty * offsety_tile + ((dfsurf->h) % fonty)/2; + + ssState.ScreenW = fontx*width; ssState.ScreenH = fonty*height; } @@ -54,10 +70,8 @@ void Overlay::CheckViewscreen() bool Overlay::PaintingOverTileAt(int32_t x, int32_t y) { - if (!df::global::gamemode || *df::global::gamemode == game_mode::ADVENTURE) - return x >= 0 && x <= width && y >= 0 && y <= height; - else - return x > 0 && x <= width && y > 0 && y <= height; + return x >= offsetx_tile && x < offsetx_tile + width + && y >= offsety_tile && y < offsety_tile + height ; } void Overlay::set_to_null() @@ -112,6 +126,12 @@ Overlay::Overlay(renderer* parent) : parent(parent) { { CoreSuspender suspend; + + if (parent->uses_opengl()){ + hook(); + glGenTextures(1, &tex_id); + } + //parent->zoom(df::zoom_commands::zoom_reset); good_viewscreen = false; ReadTileLocations(); @@ -138,6 +158,12 @@ Overlay::~Overlay() { { CoreSuspender suspend; + + if (parent->uses_opengl()){ + glDeleteTextures(1,&tex_id); + unhook(); + } + df::global::enabler->renderer = parent; } @@ -210,7 +236,8 @@ bool Overlay::GoodViewscreen() void Overlay::update_tile(int32_t x, int32_t y) { //don't update tiles we are painting overtop of - if(!PaintingOverTileAt(x,y)){ + //except in GL mode to provide for better twbt compat + if(!PaintingOverTileAt(x,y)||parent->uses_opengl()){ copy_to_inner(); parent->update_tile(x,y); } @@ -231,36 +258,55 @@ void Overlay::render() CheckViewscreen(); ReadTileLocations(); if(good_viewscreen){ + tex_width=al_get_bitmap_width(front); + tex_height=al_get_bitmap_height(front); if(front_data != NULL && front_updated){ //allegro sometimes gives a negative pitch, which SDL doesn't understand, so take care of that case int neg = 1; int dataoffset = 0; if(front_data->pitch < 0){ neg = -1; - dataoffset = (al_get_bitmap_height(front) - 1)*front_data->pitch; + dataoffset = (tex_height - 1)*front_data->pitch; } - //get the SDL surface information so we can do a blit - DFHack::DFSDL_Surface * dfsurf = (DFHack::DFSDL_Surface *) SDL_GetVideoSurface(); - DFHack::DFSDL_Surface * sssurf = (DFHack::DFSDL_Surface *) SDL_CreateRGBSurfaceFrom( ((char*) front_data->data) + dataoffset, - al_get_bitmap_width(front), al_get_bitmap_height(front), 8*front_data->pixel_size, neg*front_data->pitch, 0, 0, 0, 0); - - DFSDL_Rect src; - src.x = 0; - src.y = 0; - src.w = ssState.ScreenW; - src.h = ssState.ScreenH; - - DFSDL_Rect pos; - pos.x = offsetx; - pos.y = offsety; - pos.w = 0; - pos.h = 0; - - //do the blit - SDL_UpperBlit(sssurf, &src, dfsurf, &pos); - - SDL_FreeSurface(sssurf); + if(uses_opengl()) + { + glPushAttrib(GL_TEXTURE_BIT); + glDisable(GL_TEXTURE_2D); + glEnable(GL_TEXTURE_RECTANGLE); + //glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_RECTANGLE, tex_id); + //glPixelStorei(GL_UNPACK_ROW_LENGTH,neg*front_data->pitch); + glTexImage2D(GL_TEXTURE_RECTANGLE, 0, GL_RGBA, tex_width, tex_height, 0, GL_BGRA,GL_UNSIGNED_BYTE, + ((char*) front_data->data) + dataoffset); + glBindTexture(GL_TEXTURE_RECTANGLE, 0); + glDisable(GL_TEXTURE_RECTANGLE); + glPopAttrib(); + } + else + { + //get the SDL surface information so we can do a blit + DFHack::DFSDL_Surface * dfsurf = (DFHack::DFSDL_Surface *) SDL_GetVideoSurface(); + DFHack::DFSDL_Surface * sssurf = (DFHack::DFSDL_Surface *) SDL_CreateRGBSurfaceFrom( ((char*) front_data->data) + dataoffset, + tex_width, tex_height, 8*front_data->pixel_size, neg*front_data->pitch, 0, 0, 0, 0); + + DFSDL_Rect src; + src.x = 0; + src.y = 0; + src.w = ssState.ScreenW; + src.h = ssState.ScreenH; + + DFSDL_Rect pos; + pos.x = offsetx_pixel; + pos.y = offsety_pixel; + pos.w = 0; + pos.h = 0; + + //do the blit + SDL_UpperBlit(sssurf, &src, dfsurf, &pos); + + SDL_FreeSurface(sssurf); + } } front_updated = false; } else { @@ -332,8 +378,8 @@ bool Overlay::get_mouse_coords(int32_t* x, int32_t* y) if(ret && PaintingOverTileAt(*x,*y)){ int xpx, ypx, xpos, ypos, zpos; SDL_GetMouseState(&xpx, &ypx); - xpx = xpx - offsetx; - ypx = ypx - offsety; + xpx = xpx - offsetx_pixel; + ypx = ypx - offsety_pixel; //first figure out which tile in the segment it came from ScreenToPoint(xpx,ypx,xpos,ypos,zpos); @@ -352,17 +398,10 @@ bool Overlay::get_mouse_coords(int32_t* x, int32_t* y) //remove the offset of the df window int dfviewx, dfviewy, dfviewz; Gui::getViewCoords(dfviewx, dfviewy, dfviewz); - xpos = xpos - dfviewx + 1; - ypos = ypos - dfviewy + 1; + xpos = xpos - dfviewx + offsetx_tile; + ypos = ypos - dfviewy + offsety_tile; //zpos = zpos - dfviewz; - if (!df::global::gamemode || *df::global::gamemode == game_mode::ADVENTURE) - { - //Adventure mode doesn't have a single-tile border around it. - xpos = xpos - 1; - ypos = ypos - 1; - } - //check to see if this new loaction is within the area we are painting over //since we don't want to accidentally click somewhere in the interface if(PaintingOverTileAt(xpos,ypos)) { @@ -383,3 +422,112 @@ bool Overlay::uses_opengl() { return parent->uses_opengl(); } + +//Some Typedefs and helper functions to assitst with vtable patching: + +typedef void *vtable_pointer; +typedef void *m_pointer; + +vtable_pointer get_vtable(void* instance_ptr) { return *(void**)instance_ptr; } + +m_pointer get_vtable_entry(vtable_pointer vtable_ptr,int idx) +{ + void **vtable = (void**)vtable_ptr; + if (!vtable) return NULL; + return vtable[idx]; +} + +bool set_vtable_entry(MemoryPatcher &patcher,vtable_pointer vtable_ptr, int idx, m_pointer ptr) +{ + assert(idx >= 0); + void **vtable = (void**)vtable_ptr; + if (!vtable) return NULL; + return patcher.write(&vtable[idx], &ptr, sizeof(void*)); +} + +// Note that the unusual interposing method used here is very deliberate. +// We need to interpose whichever class's draw method is actually used. +// In practice that will alays be some subclass of renderer_opengl, +// but if we just interpose renderers known in DFHack's data files then +// renderers defined in other plugins like the TextWillBeText plugin +// will be missed. We want to support them to, so we patch the correct +// slot of the vtable of whatever the exting renderer really is. + +void Overlay::hook() +{ + if(is_hooked) return; + + int draw_index=vmethod_pointer_to_idx(&renderer_opengl::draw); + + //Get and save off a non-virtual pointer to the original draw method + vtable_pointer oldr_vtable=get_vtable(parent); + old_draw = addr_to_method_pointer(get_vtable_entry(oldr_vtable,draw_index)); + + + //Get the address of the new draw method + renderer_custom newr2; + vtable_pointer newr_vtable=get_vtable(&newr2); + m_pointer newdraw =get_vtable_entry(newr_vtable,draw_index); + + //Patch existing renderer to use new draw method + MemoryPatcher patcher; + set_vtable_entry(patcher,oldr_vtable,draw_index,newdraw); + + is_hooked=true; +} + +void Overlay::unhook() +{ + if(!is_hooked) return; + + int draw_index=vmethod_pointer_to_idx(&renderer_opengl::draw); + + //Get and save off a non-virtual pointer to the original draw method + vtable_pointer oldr_vtable=get_vtable(parent); + + //Patch existing renderer to use new draw method + MemoryPatcher patcher; + set_vtable_entry(patcher,oldr_vtable,draw_index,method_pointer_to_addr(old_draw)); + + is_hooked=false; +} + + +void renderer_custom::draw(int vertex_count) +{ + (this->*old_draw)(vertex_count); + custom_draw(); +} + + +void custom_draw() +{ + if(!overlay) return; + if(!overlay->good_viewscreen) return; + + glPushClientAttrib(GL_CLIENT_ALL_ATTRIB_BITS); + glPushAttrib(GL_TEXTURE_BIT); + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + glDisable(GL_BLEND); + glDisable(GL_ALPHA_TEST); + glDisable(GL_TEXTURE_2D); + glEnable(GL_TEXTURE_RECTANGLE); + + glBindTexture(GL_TEXTURE_RECTANGLE, overlay->tex_id); + glColor3f(1,1,1); + + glBegin(GL_QUADS); + glTexCoord2f(0,0); + glVertex2f(overlay->offsetx_tile, overlay->offsety_tile); + glTexCoord2f(0, overlay->tex_height); + glVertex2f(overlay->offsetx_tile, overlay->offsety_tile+overlay->height); + glTexCoord2f(overlay->tex_width, overlay->tex_height); + glVertex2f(overlay->offsetx_tile+overlay->width, overlay->offsety_tile+overlay->height); + glTexCoord2f(overlay->tex_width,0); + glVertex2f(overlay->offsetx_tile+overlay->width, overlay->offsety_tile); + glEnd(); + + glDisable(GL_TEXTURE_RECTANGLE); + glPopAttrib(); + glPopClientAttrib(); +} diff --git a/Overlay.h b/Overlay.h index 949b0634..4cdf6a2f 100644 --- a/Overlay.h +++ b/Overlay.h @@ -13,11 +13,20 @@ class Overlay : public df::renderer private: //size of the current df display's font uint8_t fontx, fonty; - //the pixel-offset of the first DF tile in the top-left corner of the screen - int32_t offsetx, offsety; + //the pixel-offset of the upper left corner of first tile of the main view + int32_t offsetx_pixel, offsety_pixel; + //the tile offset of the firtst tile of the main view + int32_t offsetx_tile, offsety_tile; + //the width and height in tiles of the current df display's view int32_t width, height; + //Texture id ("name") for OpenGL mode + GLuint tex_id; + + //Width and height of texure in texels + int32_t tex_width, tex_height; + //used by the renderer to skip drawing from the overlay buffer if it wasn't updated bool front_updated; //keeps track of if the most recently visited viewscreen is one it makes sense to overlay @@ -41,6 +50,10 @@ class Overlay : public df::renderer //determines if the given tile location is within the area we're painting over bool PaintingOverTileAt(int32_t, int32_t); + //Install and remove hooks for OpenGL mode + void hook(); + void unhook(); + //df::renderer overrides void set_to_null(); void copy_from_inner(); @@ -64,4 +77,52 @@ class Overlay : public df::renderer void grid_resize(int32_t w, int32_t h); bool get_mouse_coords(int32_t* x, int32_t* y); bool uses_opengl(); -}; \ No newline at end of file + + friend void custom_draw(); +}; + +// from g_src/renderer_opengl.hpp +struct renderer_opengl : df::renderer +{ + void *screen; + int dispx, dispy; + + //bool init_video(int w, int h); + + GLfloat *vertexes, *fg, *bg, *tex; + + //void write_tile_vertexes(GLfloat x, GLfloat y, GLfloat *vertex); + virtual void allocate(int tiles) {}; + virtual void init_opengl() {}; + virtual void uninit_opengl() {}; + virtual void draw(int vertex_count) {}; + //void write_tile_arrays(int x, int y, GLfloat *fg, GLfloat *bg, GLfloat *tex); + + //void update_tile(int x, int y); + //void update_all(); + //void render(); + //renderer_opengl(); + virtual ~renderer_opengl() {}; + + int zoom_steps, forced_steps; + int natural_w, natural_h; + + //void zoom(zoom_commands cmd); + //void compute_forced_zoom(); + //pair compute_zoom(bool clamp = false); + //void reshape(pair size); + + int off_x, off_y, size_x, size_y; + + //bool get_mouse_coords(int &x, int &y); + virtual void reshape_gl() {}; + //void resize(int w, int h); + //void grid_resize(int w, int h); + //void set_fullscreen(); + + bool needs_reshape; + int needs_zoom; + +}; + +typedef void (renderer_opengl::*draw_ptr)(int vertex_count);