mirror of
https://github.com/RetroDECK/Supermodel.git
synced 2025-02-16 17:35:39 +00:00
Cache rom models, for better performance.
This commit is contained in:
parent
8488f22ac5
commit
4992e59673
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
//=============
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue