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);