Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add OpenGL Support to Overlay Mode #21

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
234 changes: 191 additions & 43 deletions Overlay.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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;
}
Expand All @@ -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()
Expand Down Expand Up @@ -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();
Expand All @@ -138,6 +158,12 @@ Overlay::~Overlay()
{
{
CoreSuspender suspend;

if (parent->uses_opengl()){
glDeleteTextures(1,&tex_id);
unhook();
}

df::global::enabler->renderer = parent;
}

Expand Down Expand Up @@ -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);
}
Expand All @@ -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 {
Expand Down Expand Up @@ -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);
Expand All @@ -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)) {
Expand All @@ -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<draw_ptr>(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();
}
Loading