Cache rom models, for better performance.

This commit is contained in:
Ian Curtis 2016-03-26 22:44:26 +00:00
parent 8488f22ac5
commit 4992e59673
8 changed files with 142 additions and 70 deletions

View file

@ -35,7 +35,10 @@ struct R3DPoly
struct Mesh
{
std::shared_ptr<Texture> texture;
// texture
int format, x, y, width, height = 0;
bool mirrorU = false;
bool mirrorV = false;
// attributes
bool doubleSided = false;
@ -49,10 +52,6 @@ struct Mesh
float fogIntensity = 1.0f;
// texture
bool mirrorU = false;
bool mirrorV = false;
// opengl resources
int vboOffset = 0; // this will be calculated later
int triangleCount = 0;
@ -65,14 +64,18 @@ struct SortingMesh : public Mesh // This struct temporarily holds the model dat
struct Model
{
std::vector<Mesh> meshes;
std::shared_ptr<std::vector<Mesh>> meshes; // this reason why this is a shared ptr to an array, is that multiple models might use the same meshes
//which memory are we in
bool dynamic = true;
// texture offsets for model
int textureOffsetX = 0;
int textureOffsetY = 0;
//matrices
float modelMat[16];
float determinant; // we check if the determinant of the matrix is negative, if it is, the matrix will swap the axis order
// misc
int lutIdx = 0;
};
struct Viewport

View file

@ -4,6 +4,9 @@
#include "Vec.h"
#include <cmath> // needed by gcc
#define MAX_RAM_POLYS 100000
#define MAX_ROM_POLYS 500000
#ifndef M_PI
#define M_PI 3.14159265359
#endif
@ -21,7 +24,7 @@ CNew3D::CNew3D()
CNew3D::~CNew3D()
{
m_vboDynamic.Destroy();
m_vbo.Destroy();
}
void CNew3D::AttachMemory(const UINT32 *cullingRAMLoPtr, const UINT32 *cullingRAMHiPtr, const UINT32 *polyRAMPtr, const UINT32 *vromPtr, const UINT16 *textureRAMPtr)
@ -50,8 +53,7 @@ void CNew3D::SetStep(int stepID)
m_vertexFactor = (1.0f / 128.0f); // 17.7
}
m_vboDynamic.Create(GL_ARRAY_BUFFER, GL_DYNAMIC_DRAW, sizeof(Poly)* 100000); // allocate space for 100k polys ~ 10meg
//m_vboStatic.Create(GL_ARRAY_BUFFER, GL_DYNAMIC_DRAW, 0x80000); // 64meg buffer
m_vbo.Create(GL_ARRAY_BUFFER, GL_DYNAMIC_DRAW, sizeof(Poly) * (MAX_RAM_POLYS + MAX_ROM_POLYS));
}
bool CNew3D::Init(unsigned xOffset, unsigned yOffset, unsigned xRes, unsigned yRes, unsigned totalXResParam, unsigned totalYResParam)
@ -99,13 +101,13 @@ void CNew3D::RenderScene(int priority, bool alpha)
bool matrixLoaded = false;
if (m.meshes.empty()) {
if (m.meshes->empty()) {
continue;
}
m_r3dShader.SetModelStates(&m);
for (auto &mesh : m.meshes) {
for (auto &mesh : *m.meshes) {
if (alpha) {
if (!mesh.textureAlpha && !mesh.polyAlpha) {
@ -123,9 +125,12 @@ void CNew3D::RenderScene(int priority, bool alpha)
matrixLoaded = true; // do this here to stop loading matrices we don't need. Ie when rendering non transparent etc
}
if (mesh.texture) {
mesh.texture->BindTexture();
mesh.texture->SetWrapMode(mesh.mirrorU, mesh.mirrorV);
if (mesh.textured) {
auto tex = m_texSheet.BindTexture(m_textureRAM, mesh.format, mesh.mirrorU, mesh.mirrorV, mesh.x + m.textureOffsetX, mesh.y + m.textureOffsetY, mesh.width, mesh.height);
if (tex) {
tex->BindTexture();
tex->SetWrapMode(mesh.mirrorU, mesh.mirrorV);
}
}
m_r3dShader.SetMeshUniforms(&mesh);
@ -141,7 +146,7 @@ void CNew3D::RenderScene(int priority, bool alpha)
void CNew3D::RenderFrame(void)
{
// release any resources from last frame
m_polyBuffer.clear(); // clear dyanmic model memory buffer
m_polyBufferRam.clear(); // clear dyanmic model memory buffer
m_nodes.clear(); // memory will grow during the object life time, that's fine, no need to shrink to fit
m_modelMat.Release(); // would hope we wouldn't need this but no harm in checking
m_nodeAttribs.Reset();
@ -156,9 +161,29 @@ void CNew3D::RenderFrame(void)
RenderViewport(0x800000, pri); // build model structure
}
m_vboDynamic.Bind(true);
m_vboDynamic.BufferSubData(0, m_polyBuffer.size()*sizeof(Poly), m_polyBuffer.data()); // upload all the data to GPU in one go
m_vbo.Bind(true);
m_vbo.BufferSubData(MAX_ROM_POLYS*sizeof(Poly), m_polyBufferRam.size()*sizeof(Poly), m_polyBufferRam.data()); // upload all the dynamic data to GPU in one go
if (m_polyBufferRom.size()) {
// sync rom memory with vbo
int romBytes = (int)m_polyBufferRom.size() * sizeof(Poly);
int vboBytes = m_vbo.GetSize();
int size = romBytes - vboBytes;
if (size) {
//check we haven't blown up the memory buffers
//we will lose rom models for 1 frame is this happens, not the end of the world, as probably won't ever happen anyway
if (m_polyBufferRom.size() >= MAX_ROM_POLYS) {
m_polyBufferRom.clear();
m_vbo.Reset();
}
else {
m_vbo.AppendData(size, &m_polyBufferRom[vboBytes / sizeof(Poly)]);
}
}
}
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_NORMAL_ARRAY);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
@ -179,7 +204,7 @@ void CNew3D::RenderFrame(void)
}
m_r3dShader.SetShader(false); // unbind shader
m_vboDynamic.Bind(false);
m_vbo.Bind(false);
glDisable(GL_CULL_FACE);
glDisableClientState(GL_VERTEX_ARRAY);
@ -232,7 +257,8 @@ const UINT32* CNew3D::TranslateModelAddress(UINT32 modelAddr)
bool CNew3D::DrawModel(UINT32 modelAddr)
{
const UINT32 *modelAddress;
const UINT32* modelAddress;
bool cached = false;
Model* m;
modelAddress = TranslateModelAddress(modelAddr);
@ -240,13 +266,28 @@ bool CNew3D::DrawModel(UINT32 modelAddr)
// create a new model to push onto the vector
m_nodes.back().models.emplace_back(Model());
// get the pointer to the last element in the array
// get the last model in the array
m = &m_nodes.back().models.back();
// copy lutidx - wtf is this
m->lutIdx = modelAddr & 0xFFFFFF;
if (IsVROMModel(modelAddr) && !IsDynamicModel((UINT32*)modelAddress)) {
// copy model matrix
// try to find meshes in the rom cache
if (m_romMap.count(modelAddr)) {
m->meshes = m_romMap[modelAddr];
cached = true;
}
else {
m->meshes = std::make_shared<std::vector<Mesh>>();
m->dynamic = false;
m_romMap[modelAddr] = m->meshes; // store meshes in our rom map here
}
}
else {
m->meshes = std::make_shared<std::vector<Mesh>>();
}
// copy current model matrix
for (int i = 0; i < 16; i++) {
m->modelMat[i] = m_modelMat.currentMatrix[i];
}
@ -254,7 +295,13 @@ bool CNew3D::DrawModel(UINT32 modelAddr)
//calculate determinant
m->determinant = Determinant3x3(m_modelMat);
CacheModel(m, modelAddress);
// update texture offsets
m->textureOffsetX = m_nodeAttribs.currentTexOffsetX;
m->textureOffsetY = m_nodeAttribs.currentTexOffsetY;
if (!cached) {
CacheModel(m, modelAddress);
}
return true;
}
@ -763,7 +810,6 @@ void CNew3D::CacheModel(Model *m, const UINT32 *data)
UINT64 lastHash = -1;
SortingMesh* currentMesh = nullptr;
std::shared_ptr<Texture> tex;
std::map<UINT64, SortingMesh> sMap;
if (data == NULL)
@ -800,7 +846,7 @@ void CNew3D::CacheModel(Model *m, const UINT32 *data)
data += 7; // data will now point to first vertex
// create a hash value based on poly attributes -todo add more attributes
auto hash = ph.Hash(m_nodeAttribs.currentTexOffsetX, m_nodeAttribs.currentTexOffsetY);
auto hash = ph.Hash();
if (hash != lastHash && validPoly) {
@ -815,18 +861,12 @@ void CNew3D::CacheModel(Model *m, const UINT32 *data)
//copy attributes
currentMesh->doubleSided = ph.DoubleSided();
currentMesh->mirrorU = ph.TexUMirror();
currentMesh->mirrorV = ph.TexVMirror();
currentMesh->textured = ph.TexEnabled();
currentMesh->alphaTest = ph.AlphaTest();
currentMesh->textureAlpha = ph.TextureAlpha();
currentMesh->polyAlpha = ph.PolyAlpha();
currentMesh->lighting = ph.LightEnabled();
if (ph.header[6] & 0x10000) {
currentMesh->testBit = true;
}
if (!ph.Luminous()) {
currentMesh->fogIntensity = 1.0f;
}
@ -835,18 +875,17 @@ void CNew3D::CacheModel(Model *m, const UINT32 *data)
}
if (ph.TexEnabled()) {
currentMesh->texture = m_texSheet.BindTexture(m_textureRAM, ph.TexFormat(), ph.TexUMirror(), ph.TexVMirror(), ph.X(m_nodeAttribs.currentTexOffsetX), ph.Y(m_nodeAttribs.currentTexOffsetY), ph.TexWidth(), ph.TexHeight());
currentMesh->format = ph.TexFormat();
currentMesh->x = ph.X();
currentMesh->y = ph.Y();
currentMesh->width = ph.TexWidth();
currentMesh->height = ph.TexHeight();
currentMesh->mirrorU = ph.TexUMirror();
currentMesh->mirrorV = ph.TexVMirror();
}
}
currentMesh = &sMap[hash];
if (ph.TexEnabled()) {
tex = currentMesh->texture;
}
else {
tex = nullptr;
}
}
if (validPoly) {
@ -917,8 +956,8 @@ void CNew3D::CacheModel(Model *m, const UINT32 *data)
float texU, texV = 0;
// tex coords
if (tex) {
tex->GetCoordinates((UINT16)(it >> 16), (UINT16)(it & 0xFFFF), uvScale, texU, texV);
if (validPoly && currentMesh->textured) {
Texture::GetCoordinates(currentMesh->width, currentMesh->height, (UINT16)(it >> 16), (UINT16)(it & 0xFFFF), uvScale, texU, texV);
}
p.v[j].texcoords[0] = texU;
@ -942,21 +981,31 @@ void CNew3D::CacheModel(Model *m, const UINT32 *data)
//sorted the data, now copy to main data structures
// we know how many meshes we have so reserve appropriate space
m->meshes.reserve(sMap.size());
m->meshes->reserve(sMap.size());
for (auto& it : sMap) {
// calculate VBO values for current mesh
it.second.vboOffset = m_polyBuffer.size();
it.second.triangleCount = it.second.polys.size();
//it.second.clockWise = cw;
if (m->dynamic) {
// copy poly data to main buffer
m_polyBuffer.insert(m_polyBuffer.end(), it.second.polys.begin(), it.second.polys.end());
// calculate VBO values for current mesh
it.second.vboOffset = m_polyBufferRam.size() + MAX_ROM_POLYS;
it.second.triangleCount = it.second.polys.size();
// copy poly data to main buffer
m_polyBufferRam.insert(m_polyBufferRam.end(), it.second.polys.begin(), it.second.polys.end());
}
else {
// calculate VBO values for current mesh
it.second.vboOffset = m_polyBufferRom.size();
it.second.triangleCount = it.second.polys.size();
// copy poly data to main buffer
m_polyBufferRom.insert(m_polyBufferRom.end(), it.second.polys.begin(), it.second.polys.end());
}
//copy the temp mesh into the model structure
//this will lose the associated vertex data, which is now copied to the main buffer anyway
m->meshes.push_back(it.second);
m->meshes->push_back(it.second);
}
}
@ -998,4 +1047,9 @@ bool CNew3D::IsDynamicModel(UINT32 *data)
return false;
}
bool CNew3D::IsVROMModel(UINT32 modelAddr)
{
return modelAddr >= 0x100000;
}
} // New3D

View file

@ -172,6 +172,8 @@ private:
void RenderScene(int priority, bool alpha);
float Determinant3x3(const float m[16]);
bool IsDynamicModel(UINT32 *data); // check if the model has a colour palette
bool IsVROMModel(UINT32 modelAddr);
/*
* Data
@ -199,14 +201,15 @@ private:
TextureSheet m_texSheet;
NodeAttributes m_nodeAttribs;
Mat4 m_modelMat; // current modelview matrix
Mat4 m_modelMat; // current modelview matrix
int m_listDepth;
std::vector<Node> m_nodes; // build the scene
std::vector<Poly> m_polyBuffer; // we actually hold the vertex data here, one buffer to send to opengl, instead of 2000+ small ones.
std::vector<Node> m_nodes; // this represents the entire render frame
std::vector<Poly> m_polyBufferRam; // dynamic polys
std::vector<Poly> m_polyBufferRom; // rom polys
std::unordered_map<UINT32, std::shared_ptr<std::vector<Mesh>>> m_romMap; // a hash table for all the ROM models. The meshes don't have model matrices or tex offsets yet
VBO m_vboDynamic; // dynamic data from poly ram, rom polys can go in a different buffer
VBO m_vboStatic; // for ROM models
VBO m_vbo; // large VBO to hold our poly data, start of VBO is ROM data, ram polys follow
R3DShader m_r3dShader;
int m_currentVPPriority;

View file

@ -190,18 +190,18 @@ int PolyHeader::Page()
// header 5
//
int PolyHeader::X(int textureXOffset)
int PolyHeader::X()
{
//====
int x;
//====
x = (32 * (((header[4] & 0x1F) << 1) | ((header[5] >> 7) & 1))) + textureXOffset;
x = (32 * (((header[4] & 0x1F) << 1) | ((header[5] >> 7) & 1)));
x &= 2047;
return x;
}
int PolyHeader::Y(int textureYOffset)
int PolyHeader::Y()
{
//=======
int y;
@ -215,7 +215,7 @@ int PolyHeader::Y(int textureYOffset)
page = 0;
}
y = (32 * (header[5] & 0x1F) + page) + textureYOffset; // if we hit 2nd page add 1024 to y coordinate
y = (32 * (header[5] & 0x1F) + page); // if we hit 2nd page add 1024 to y coordinate
y &= 2047;
return y;
@ -289,15 +289,15 @@ float PolyHeader::LightModifier()
// misc
//
UINT64 PolyHeader::Hash(int textureXOffset, int textureYOffset)
UINT64 PolyHeader::Hash()
{
UINT64 hash = 0;
hash |= (header[2] & 3); // bits 0-1 uv mirror bits
hash |= (UINT64)((header[3] >> 0) & 7) << 2; // bits 2-4 tex height
hash |= (UINT64)((header[3] >> 3) & 7) << 5; // bits 5-7 tex width
hash |= (UINT64)X(textureXOffset) << 8; // bits 8-17 x offset
hash |= (UINT64)Y(textureYOffset) << 18; // bits 18-27 y offset
hash |= (UINT64)X() << 8; // bits 8-17 x offset
hash |= (UINT64)Y() << 18; // bits 18-27 y offset
hash |= (UINT64)TexFormat() << 28; // bits 28-30 tex format
hash |= (UINT64)TexEnabled() << 31; // bits 31 textures enabled
hash |= (UINT64)LightEnabled() << 32; // bits 32 light enabled

View file

@ -108,8 +108,8 @@ public:
int Page();
// header 5
int X(int textureXOffset);
int Y(int textureYOffset);
int X();
int Y();
//header 6
int TexFormat();
@ -126,7 +126,7 @@ public:
float LightModifier();
// misc
UINT64 Hash(int textureXOffset, int textureYOffset); // make a unique hash for sorting by state
UINT64 Hash(); // make a unique hash for sorting by state
//=============

View file

@ -42,8 +42,14 @@ void Texture::BindTexture()
void Texture::GetCoordinates(UINT16 uIn, UINT16 vIn, float uvScale, float& uOut, float& vOut)
{
uOut = ((uIn*uvScale)+0.0f) / m_width;
vOut = ((vIn*uvScale)+0.0f) / m_height;
uOut = (uIn*uvScale) / m_width;
vOut = (vIn*uvScale) / m_height;
}
void Texture::GetCoordinates(int width, int height, UINT16 uIn, UINT16 vIn, float uvScale, float& uOut, float& vOut)
{
uOut = (uIn*uvScale) / width;
vOut = (vIn*uvScale) / height;
}
void Texture::SetWrapMode(bool mirrorU, bool mirrorV)

View file

@ -23,6 +23,8 @@ public:
void GetDetails (int& x, int&y, int& width, int& height, int& format);
void SetWrapMode (bool mirrorU, bool mirrorV);
static void GetCoordinates(int width, int height, UINT16 uIn, UINT16 vIn, float uvScale, float& uOut, float& vOut);
private:
void Reset();

View file

@ -30,6 +30,10 @@ void VBO::BufferSubData(GLintptr offset, GLsizeiptr size, const GLvoid* data)
bool VBO::AppendData(GLsizeiptr size, const GLvoid* data)
{
if (size == 0 || !data) {
return true; // nothing to do
}
if (m_size + size >= m_capacity) {
return false;
}