1
0
Fork 0
mirror of https://github.com/AquariaOSE/Aquaria.git synced 2024-11-25 17:53:47 +00:00

Make GridRender use a single draw call instead of expensive grid scanning

This commit is contained in:
fgenesis 2024-01-13 04:25:27 +01:00
parent 70b8e69402
commit 511f064bbe
9 changed files with 244 additions and 93 deletions

View file

@ -18,6 +18,9 @@ You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#include "Game.h"
#include <algorithm>
#include "../BBGE/Gradient.h"
#include "../BBGE/AfterEffect.h"
#include "../BBGE/MathFunctions.h"
@ -31,7 +34,6 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#include "ReadXML.h"
#include "RenderBase.h"
#include "Game.h"
#include "GridRender.h"
#include "WaterSurfaceRender.h"
#include "ScriptedEntity.h"
@ -328,6 +330,8 @@ void Game::addObsRow(unsigned tx, unsigned ty, unsigned len)
void Game::clearObsRows()
{
obsRows.clear();
mapGridW = 0;
mapGridH = 0;
}
void Game::fillGridFromQuad(Quad *q, ObsType obsType, bool trim)
@ -539,6 +543,8 @@ void Game::reconstructEntityGrid()
Entity *e = *i;
e->fillGrid();
}
updateGridRender(OT_INVISIBLEENT);
}
void Game::reconstructGrid(bool force)
@ -574,6 +580,9 @@ void Game::reconstructGrid(bool force)
}
trimGrid();
// This does intentionally not update black.
updateGridRender(OT_MASK_NOTBLACK);
}
void Game::trimGrid()
@ -1887,25 +1896,36 @@ void Game::setMusicToPlay(const std::string &m)
stringToLower(musicToPlay);
}
TileVector Game::computeMapSizeFromObs() const
{
TileVector ret;
const size_t N = obsRows.size();
for (size_t i = 0; i < N; i++)
{
const ObsRow *r = &obsRows[i];
ret.x = std::max<unsigned>(ret.x, r->tx + r->len);
ret.y = std::max<unsigned>(ret.y, r->ty);
}
return ret;
}
void Game::findMaxCameraValues()
{
cameraMin.x = 20;
cameraMin.y = 20;
cameraMax.x = -1;
cameraMax.y = -1;
for (size_t i = 0; i < obsRows.size(); i++)
if(obsRows.size())
{
ObsRow *r = &obsRows[i];
TileVector t(r->tx + r->len, r->ty);
Vector v = t.worldVector();
if (v.x > cameraMax.x)
{
cameraMax.x = v.x;
}
if (v.y > cameraMax.y)
{
cameraMax.y = v.y;
}
TileVector wh = computeMapSizeFromObs();
mapGridW = wh.x;
mapGridH = wh.y;
cameraMax = wh.worldVector();
}
else
{
mapGridW = 0;
mapGridH = 0;
cameraMax.x = -1;
cameraMax.y = -1;
}
}
@ -2679,6 +2699,16 @@ void Game::applyState()
bindInput();
// loadScene() calls reconstructGrid(), which requires these to exist
blackRender = new GridRender(OT_BLACK);
gridRender = new GridRender(OT_INVISIBLE);
gridRender2 = new GridRender(OT_HURT);
gridRender3 = new GridRender(OT_INVISIBLEIN);
edgeRender = new GridRender(OT_BLACKINVIS);
gridRenderEnt = new GridRender(OT_INVISIBLEENT);
gridRenderUser1 = new GridRender(OT_USER1);
gridRenderUser2 = new GridRender(OT_USER2);
if (verbose) debugLog("Loading Scene");
if(!loadScene(sceneToLoad))
{
@ -2686,6 +2716,8 @@ void Game::applyState()
}
if (verbose) debugLog("...Done");
// ----------------- SCENE IS LOADED BELOW HERE -------------------
dsq->continuity.worldMap.revealMap(sceneName);
if (verbose) debugLog("Adding Avatar");
@ -2703,37 +2735,31 @@ void Game::applyState()
songLineRender = new SongLineRender();
addRenderObject(songLineRender, LR_HUD);
gridRender = new GridRender(OT_INVISIBLE);
gridRender->color = Vector(1, 0, 0);
addRenderObject(gridRender, LR_DEBUG_TEXT);
gridRender->alpha = 0;
gridRender2 = new GridRender(OT_HURT);
gridRender2->color = Vector(1, 1, 0);
addRenderObject(gridRender2, LR_DEBUG_TEXT);
gridRender2->alpha = 0;
gridRender3 = new GridRender(OT_INVISIBLEIN);
gridRender3->color = Vector(1, 0.5f, 0);
addRenderObject(gridRender3, LR_DEBUG_TEXT);
gridRender3->alpha = 0;
edgeRender = new GridRender(OT_BLACKINVIS);
edgeRender->color = Vector(0.3f, 0, 0.6f);
addRenderObject(edgeRender, LR_DEBUG_TEXT);
edgeRender->alpha = 0;
gridRenderEnt = new GridRender(OT_INVISIBLEENT);
gridRenderEnt->color = Vector(0, 1, 0.5);
addRenderObject(gridRenderEnt, LR_DEBUG_TEXT);
gridRenderEnt->alpha = 0;
gridRenderUser1 = new GridRender(OT_USER1);
addRenderObject(gridRenderUser1, LR_DEBUG_TEXT);
gridRenderUser1->color = Vector(1, 0, 1);
gridRenderUser1->alpha = 0;
gridRenderUser2 = new GridRender(OT_USER2);
addRenderObject(gridRenderUser2, LR_DEBUG_TEXT);
gridRenderUser2->color = Vector(1, 1, 1);
gridRenderUser2->alpha = 0;
@ -2742,11 +2768,11 @@ void Game::applyState()
//waterSurfaceRender->setRenderPass(-1);
addRenderObject(waterSurfaceRender, LR_WATERSURFACE);
GridRender *blackRender = new GridRender(OT_BLACK);
blackRender->color = Vector(0, 0, 0);
//blackRender->alpha = 0;
blackRender->setBlendType(BLEND_DISABLED);
addRenderObject(blackRender, LR_ELEMENTS4);
blackRender->rebuildBuffers(this->obsRows);
miniMapRender = new MiniMapRender;
// position is set in minimaprender::onupdate
@ -3986,7 +4012,16 @@ void Game::toggleGridRender()
float t = 0;
float a = 0;
if (gridRender->alpha == 0)
{
a = 0.5f;
gridRender->rebuildBuffersIfNecessary();
gridRender2->rebuildBuffersIfNecessary();
gridRender3->rebuildBuffersIfNecessary();
edgeRender->rebuildBuffersIfNecessary();
gridRenderEnt->rebuildBuffersIfNecessary();
gridRenderUser1->rebuildBuffersIfNecessary();
gridRenderUser2->rebuildBuffersIfNecessary();
}
gridRender->alpha.interpolateTo(a, t);
gridRender2->alpha.interpolateTo(a, t);
@ -3997,6 +4032,30 @@ void Game::toggleGridRender()
gridRenderUser2->alpha.interpolateTo(a, t);
}
static void checkgridrender(GridRender *gr, ObsType obs)
{
if(gr->getObs() & obs)
gr->markForRebuild();
}
void Game::updateGridRender(ObsType obs)
{
// These are usually not visible. Delay rebuild until they are actually shown.
checkgridrender(gridRender, obs);
checkgridrender(gridRender2, obs);
checkgridrender(gridRender3, obs);
checkgridrender(edgeRender, obs);
checkgridrender(gridRenderEnt, obs);
checkgridrender(gridRenderUser1, obs);
checkgridrender(gridRenderUser2, obs);
// This is normally not necessary, because black is only changed by the editor.
// Keeping it here possibly for future mod compat.
// It's also always shown, so we can immediately rebuild it
if(obs & OT_BLACK)
blackRender->rebuildBuffers(this->obsRows);
}
Vector Game::getCameraPositionFor(const Vector &pos)
{
return Vector(pos.x - 400 * core->invGlobalScale, pos.y - 300 * core->invGlobalScale, 0);
@ -4721,6 +4780,7 @@ void Game::removeState()
controlHint_text = 0;
miniMapRender = 0;
blackRender = 0;
gridRender = 0;
gridRender2 = 0;
gridRender3 = 0;

View file

@ -85,22 +85,6 @@ struct MinimapIcon
typedef std::list<Ingredient*> Ingredients;
class ObsRow
{
public:
inline ObsRow(unsigned tx, unsigned ty, unsigned len)
: tx(tx), ty(ty), len(len) {}
inline ObsRow(const ObsRow& o)
: tx(o.tx), ty(o.ty), len(o.len) {}
const unsigned tx, ty, len;
};
enum FlagCheckType
{
NO_TYPE =-1,
AND =0,
OR =1
};
class EntityClass
{
@ -136,6 +120,7 @@ public:
Avatar *avatar;
Entity *li;
TileVector getGridSize() const; // available after calling findMaxCameraValues()
ObsType getGrid(const TileVector &tile) const;
ObsType getGridRaw(const TileVector &tile) const;
unsigned char *getGridColumn(int tileX);
@ -381,8 +366,9 @@ public:
void createGradient();
std::string saveMusic;
GridRender *gridRender, *gridRender2, *gridRender3, *edgeRender, *gridRenderEnt, *gridRenderUser1, *gridRenderUser2;
GridRender *blackRender, *gridRender, *gridRender2, *gridRender3, *edgeRender, *gridRenderEnt, *gridRenderUser1, *gridRenderUser2;
void toggleGridRender();
void updateGridRender(ObsType obs);
bool invinciblity;
@ -457,7 +443,9 @@ protected:
void createLi();
void createPets();
void findMaxCameraValues();
TileVector computeMapSizeFromObs() const;
std::vector<ObsRow> obsRows;
size_t mapGridW, mapGridH;
std::string musicToPlay;
@ -503,6 +491,11 @@ ObsType Game::getGridRaw(const TileVector &tile) const
: OT_OUTOFBOUNDS;
}
inline TileVector Game::getGridSize() const
{
return TileVector(int(this->mapGridW), int(this->mapGridH));
}
inline
ObsType Game::getGrid(const TileVector &tile) const
{

View file

@ -14,6 +14,7 @@ enum ObsType
OT_BLACK = 0x01,
OT_BLACKINVIS = 0x02, // same as OT_BLACK, but not drawn
OT_MASK_BLACK = OT_BLACK | OT_BLACKINVIS,
OT_MASK_NOTBLACK = ~OT_MASK_BLACK,
// set by tiles
OT_INVISIBLE = 0x04,
@ -31,7 +32,7 @@ enum ObsType
OT_USER2 = 0x80,
OT_USER_MASK = OT_USER1 | OT_USER2,
OT_OUTOFBOUNDS = 0xff
OT_OUTOFBOUNDS = 0xff, // all bits set
};

View file

@ -186,5 +186,15 @@ struct UnderWaterResult
Path *waterbubble;
};
class ObsRow
{
public:
inline ObsRow(unsigned tx, unsigned ty, unsigned len)
: tx(tx), ty(ty), len(len) {}
inline ObsRow(const ObsRow& o)
: tx(o.tx), ty(o.ty), len(o.len) {}
const unsigned tx, ty, len;
};
#endif

View file

@ -22,39 +22,142 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#include "Game.h"
#include "RenderBase.h"
GridRender::GridRender(ObsType obsType) : RenderObject()
static void collectRows(std::vector<ObsRow>& rows, ObsType obs)
{
const TileVector gs = game->getGridSize();
const size_t endX = std::min(gs.x, MAX_GRID);
const size_t endY = std::min(gs.y, MAX_GRID);
for(size_t y = 0; y < endY; ++y)
{
bool on = game->getGridRaw(TileVector(0, y)) == obs;
size_t startx = 0;
for(size_t x = 1; x < endX; ++x)
{
const ObsType ot = game->getGridRaw(TileVector(x, y));
if(ot == obs)
{
if(!on)
{
startx = x;
on = true;
}
}
else
{
if(on)
{
// previous tile is the last one, so -1
rows.push_back(ObsRow(startx, y, x - startx));
on = false;
}
}
}
if(on)
rows.push_back(ObsRow(startx, y, endX - startx));
}
}
GridRender::GridRender(ObsType obsType)
: RenderObject()
, vbo(GPUBUF_VERTEXBUF | GPUBUF_STATIC)
, primsToDraw(0)
, obsType(obsType)
//, ibo(GPUBUF_INDEXBUF | GPUBUF_STATIC)
, markedForRebuild(true)
{
color = Vector(1, 0, 0);
position.z = 5;
cull = false;
alpha = 0.5f;
this->obsType = obsType;
this->scale.x = TILE_SIZE;
this->scale.y = TILE_SIZE;
}
void GridRender::onUpdate(float dt)
void GridRender::rebuildBuffers()
{
RenderObject::onUpdate(dt);
std::vector<ObsRow> rows;
collectRows(rows, obsType);
rebuildBuffers(rows);
}
inline static void doRenderGrid(int x, int startCol, int endCol)
void GridRender::rebuildBuffers(const std::vector<ObsRow>& rows)
{
const int drawx1 = x*TILE_SIZE;
const int drawx2 = (x+1)*TILE_SIZE;
const int drawy1 = startCol*TILE_SIZE;
const int drawy2 = (endCol+1)*TILE_SIZE;
markedForRebuild = false;
glBegin(GL_QUADS);
glVertex3i(drawx1, drawy2, 0.0f);
glVertex3i(drawx2, drawy2, 0.0f);
glVertex3i(drawx2, drawy1, 0.0f);
glVertex3i(drawx1, drawy1, 0.0f);
glEnd();
const size_t N = rows.size();
primsToDraw = N * 6;
if(!N)
return;
// 2 tris = 6 verts per ObsRow, each vertex is 2x uint16, makes 24b per quad.
// We could use indexed rendering and use 2 verts less (16b),
// but the 6 indices would cost another 12b so it's definitely cheaper to send
// triangle soup to the gpu in this case, since each vertex is only 4 bytes.
const size_t szv = N * 6 * 2 * sizeof(unsigned short);
do
{
unsigned short *pxy = (unsigned short*)vbo.beginWrite(GPUBUFTYPE_UVEC2, szv, GPUACCESS_DEFAULT);
for(size_t i = 0; i < N; ++i)
{
const ObsRow& row = rows[i];
// Don't bother to transform to float. The GPU can do that better.
// The scale factor of a GridRender is set to TILE_SIZE, that pre-bakes the
// required scaling multiplication into the object scale so we can get away
// with using raw, unscaled values here
const unsigned short x0 = row.tx;
const unsigned short x1 = row.tx + row.len;
const unsigned short y0 = row.ty;
const unsigned short y1 = row.ty + 1;
// top left triangle
*pxy++ = x0;
*pxy++ = y0;
*pxy++ = x1;
*pxy++ = y0;
*pxy++ = x0;
*pxy++ = y1;
// bottom right triangle
*pxy++ = x1;
*pxy++ = y0;
*pxy++ = x1;
*pxy++ = y1;
*pxy++ = x0;
*pxy++ = y1;
}
}
while(!vbo.commitWrite());
}
void GridRender::rebuildBuffersIfNecessary()
{
if(markedForRebuild)
rebuildBuffers();
}
void GridRender::rebuildBuffersIfNecessary(const std::vector<ObsRow>& rows)
{
if(markedForRebuild)
rebuildBuffers(rows);
}
void GridRender::onRender(const RenderState& rs) const
{
if(!primsToDraw)
return;
/*
const signed char obsType = this->obsType;
Vector camPos = core->cameraPos;
camPos.x -= core->getVirtualOffX() * (core->invGlobalScale);
@ -75,41 +178,13 @@ void GridRender::onRender(const RenderState& rs) const
endY = MAX_GRID-1;
if (startY > endY)
return;
for (int x = startX; x <= endX; ++x)
{
const unsigned char *gridColumn = game->getGridColumn(x);
int startCol = -1, y;
*/
// fast-forward to next drawable byte
if(const unsigned char *next = (const unsigned char*)memchr(gridColumn + startY, obsType, endY - startY + 1)) // find next byte with correct obs type
{
y = next - gridColumn; // will get incremented right away, which is okay, because we alrady set startCol
startCol = y;
}
else
continue; // nothing do draw in this column
// TODO: keep track of prim index at which a row starts
// then horizontally span only as much prims as are necessary?
for ( ; y < endY; ++y)
{
if (gridColumn[y] != obsType)
{
doRenderGrid(x, startCol, y - 1);
// fast-forward to next drawable byte
if(const unsigned char *next = (const unsigned char*)memchr(gridColumn + y, obsType, endY - y)) // find next byte with correct obs type
{
y = next - gridColumn; // will get incremented right away, which is okay, because we alrady set startCol
startCol = y;
}
else
break;
}
}
if (y == endY)
{
doRenderGrid(x, startCol, y);
}
}
vbo.apply();
glDrawArrays(GL_TRIANGLES, 0, primsToDraw);
}
SongLineRender::SongLineRender()

View file

@ -22,8 +22,10 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#define GRIDRENDER_H
#include "GameEnums.h"
#include "GameStructs.h"
#include "../BBGE/Quad.h"
#include "ActionMapper.h"
#include "VertexBuffer.h"
class GemMover;
struct MinimapIcon;
@ -36,9 +38,18 @@ class GridRender : public RenderObject
{
public:
GridRender(ObsType obsType);
void rebuildBuffers();
void rebuildBuffers(const std::vector<ObsRow>& rows);
void rebuildBuffersIfNecessary();
void rebuildBuffersIfNecessary(const std::vector<ObsRow>& rows);
void markForRebuild() { markedForRebuild = true; }
ObsType getObs() const { return obsType; }
protected:
ObsType obsType;
void onUpdate(float dt) OVERRIDE;
DynamicGPUBuffer vbo;
size_t primsToDraw;
const ObsType obsType;
bool markedForRebuild;
void onRender(const RenderState& rs) const OVERRIDE;
};

View file

@ -1622,6 +1622,7 @@ void SceneEditor::generateLevel()
}
game->reconstructGrid(true);
game->updateGridRender(OT_MASK_BLACK);
maxX--;
maxY--;

View file

@ -87,7 +87,6 @@ GL_FUNC(void,glTexCoord2f,(GLfloat s, GLfloat t),(s,t),)
//GL_FUNC(void,glTexCoord2d,(GLdouble s, GLdouble t),(s,t),)
GL_FUNC(void,glVertex2f,(GLfloat x, GLfloat y),(x,y),)
GL_FUNC(void,glVertex3f,(GLfloat x, GLfloat y, GLfloat z),(x,y,z),)
GL_FUNC(void,glVertex3i,(GLint x, GLint y, GLint z),(x,y,z),)
// stuff GLU needs...
GL_FUNC(void,glGetIntegerv,(GLenum pname, GLint *params),(pname,params),)

View file

@ -29,7 +29,8 @@ enum BufDataType
GPUBUFTYPE_VEC2_TC = 0x00081021, // xyuv xyuv xyuv
GPUBUFTYPE_VEC2_TC_RGBA = 0x10082021, // xyuvrgba xyuvrgba xyuvrgba
// ccoossnt
GPUBUFTYPE_VEC2_TC_RGBA_BUT_NO_COLOR = GPUBUFTYPE_VEC2_TC_RGBA & 0xffffff
GPUBUFTYPE_VEC2_TC_RGBA_BUT_NO_COLOR = GPUBUFTYPE_VEC2_TC_RGBA & 0xffffff,
GPUBUFTYPE_UVEC2 = 0x00000420
};
enum AccessFlags