Supermodel/Src/Graphics/Legacy3D/TextureRefs.cpp

334 lines
8.9 KiB
C++

/**
** Supermodel
** A Sega Model 3 Arcade Emulator.
** Copyright 2011 Bart Trzynadlowski, Nik Henson
**
** This file is part of Supermodel.
**
** Supermodel is free software: you can redistribute it and/or modify it under
** the terms of the GNU General Public License as published by the Free
** Software Foundation, either version 3 of the License, or (at your option)
** any later version.
**
** Supermodel is distributed in the hope that it will be useful, but WITHOUT
** ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
** FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
** more details.
**
** You should have received a copy of the GNU General Public License along
** with Supermodel. If not, see <http://www.gnu.org/licenses/>.
**/
/*
* CTextureRefs.cpp
*
* Class that tracks unique texture references, eg in a cached model.
*
* Texture references are stored internally as a 27-bit field (3 bits for format, 6 bits each for x, y, width & height) to save space.
*
* A pre-allocated array is used for storing up to TEXREFS_ARRAY_SIZE texture references. When that limit is exceeded, it switches
* to using a hashset to store the texture references, but this requires extra memory allocation.
*/
#include "Supermodel.h"
namespace Legacy3D {
CTextureRefs::CTextureRefs() : m_size(0), m_hashCapacity(0), m_hashEntries(NULL)
{
//
}
CTextureRefs::~CTextureRefs()
{
DeleteAllHashEntries();
}
unsigned CTextureRefs::GetSize() const
{
return m_size;
}
void CTextureRefs::Clear()
{
// Delete all hash entries
DeleteAllHashEntries();
m_size = 0;
m_hashCapacity = 0;
m_hashEntries = NULL;
}
bool CTextureRefs::ContainsRef(unsigned fmt, unsigned x, unsigned y, unsigned width, unsigned height)
{
// Pack texture reference into bitfield
unsigned texRef = (fmt&7)<<24|(x&0x7E0)<<13|(y&0x7E0)<<7|(width&0x7E0)<<1|(height&0x7E0)>>5;
// Check if using array or hashset
if (m_size <= TEXREFS_ARRAY_SIZE)
{
// See if texture reference held in array
for (unsigned i = 0; i < m_size; i++)
{
if (texRef == m_array[i])
return true;
}
return false;
}
else
// See if texture reference held in hashset
return HashContains(texRef);
}
bool CTextureRefs::AddRef(unsigned fmt, unsigned x, unsigned y, unsigned width, unsigned height)
{
// Pack texture reference into bitfield
unsigned texRef = (fmt&7)<<24|(x&0x7E0)<<13|(y&0x7E0)<<7|(width&0x7E0)<<1|(height&0x7E0)>>5;
// Check if using array or hashset
if (m_size <= TEXREFS_ARRAY_SIZE)
{
// See if already held in array, if so nothing to do
for (unsigned i = 0; i < m_size; i++)
{
if (texRef == m_array[i])
return true;
}
// If not, check if array is full
if (m_size == TEXREFS_ARRAY_SIZE)
{
// If so, set initial hashset capacity to 47 to initialize it
UpdateHashCapacity(47);
// Copy array into hashset
for (unsigned i = 0; i < TEXREFS_ARRAY_SIZE; i++)
AddToHash(m_array[i]);
// Add texture reference to hashset
AddToHash(texRef);
}
else
{
// Add texture reference to array
m_array[m_size] = texRef;
m_size++;
}
return true;
}
else
// Add texture reference to hashset
return AddToHash(texRef);
}
bool CTextureRefs::RemoveRef(unsigned fmt, unsigned x, unsigned y, unsigned width, unsigned height)
{
// Pack texture reference into bitfield
unsigned texRef = (fmt&7)<<24|(x&0x7E0)<<13|(y&0x7E0)<<7|(width&0x7E0)<<1|(height&0x7E0)>>5;
// Check if using array or hashset
if (m_size <= TEXREFS_ARRAY_SIZE)
{
for (unsigned i = 0; i < m_size; i++)
{
if (texRef == m_array[i])
{
for (unsigned j = i + 1; j < m_size; j++)
m_array[j - 1] = m_array[j];
m_size--;
return true;
}
}
return false;
}
else
{
// Remove texture reference from hashset
bool removed = RemoveFromHash(texRef);
// See if should switch back to array
if (m_size == TEXREFS_ARRAY_SIZE)
{
// Loop through all hash entries and copy texture references into array
unsigned j = 0;
for (unsigned i = 0; i < m_hashCapacity; i++)
{
for (HashEntry *entry = m_hashEntries[i]; entry; entry = entry->nextEntry)
m_array[j++] = entry->texRef;
}
// Delete all hash entries
DeleteAllHashEntries();
}
return removed;
}
}
void CTextureRefs::DecodeAllTextures(CLegacy3D *Render3D)
{
// Check if using array or hashset
if (m_size <= TEXREFS_ARRAY_SIZE)
{
// Loop through elements in array and call CLegacy3D::DecodeTexture
for (unsigned i = 0; i < m_size; i++)
{
// Unpack texture reference from bitfield
unsigned texRef = m_array[i];
unsigned fmt = texRef>>24;
unsigned x = (texRef>>13)&0x7E0;
unsigned y = (texRef>>7)&0x7E0;
unsigned width = (texRef>>1)&0x7E0;
unsigned height = (texRef<<5)&0x7E0;
Render3D->DecodeTexture(fmt, x, y, width, height);
}
}
else
{
// Loop through all hash entriesa and call CLegacy3D::DecodeTexture
for (unsigned i = 0; i < m_hashCapacity; i++)
{
for (HashEntry *entry = m_hashEntries[i]; entry; entry = entry->nextEntry)
{
// Unpack texture reference from bitfield
unsigned texRef = entry->texRef;
unsigned fmt = texRef>>24;
unsigned x = (texRef>>13)&0x7E0;
unsigned y = (texRef>>7)&0x7E0;
unsigned width = (texRef>>1)&0x7E0;
unsigned height = (texRef<<5)&0x7E0;
Render3D->DecodeTexture(fmt, x, y, width, height);
}
}
}
}
bool CTextureRefs::UpdateHashCapacity(unsigned capacity)
{
unsigned oldCapacity = m_hashCapacity;
HashEntry **oldEntries = m_hashEntries;
// Update capacity and create new empty entries array
m_hashCapacity = capacity;
m_hashEntries = new(std::nothrow) HashEntry*[capacity];
if (!m_hashEntries)
return false;
memset(m_hashEntries, NULL, capacity * sizeof(HashEntry*));
if (oldEntries)
{
// Redistribute entries into new entries array
for (unsigned i = 0; i < oldCapacity; i++)
{
HashEntry *entry = oldEntries[i];
while (entry)
{
HashEntry *nextEntry = entry->nextEntry;
unsigned hash = entry->texRef % capacity;
entry->nextEntry = m_hashEntries[hash];
m_hashEntries[hash] = entry;
entry = nextEntry;
}
}
// Delete old entries array
delete[] oldEntries;
}
return true;
}
HashEntry *CTextureRefs::CreateHashEntry(unsigned texRef, bool &hashCapacityUpdated)
{
// Update size and increase hash capacity if required
m_size++;
hashCapacityUpdated = m_size >= m_hashCapacity;
if (hashCapacityUpdated)
{
if (m_hashCapacity < 89)
UpdateHashCapacity(89); // Capacity of 89 gives good sequence of mostly prime capacities (89, 179, 359, 719, 1439, 2879 etc)
else
UpdateHashCapacity(2 * m_hashCapacity + 1);
}
return new(std::nothrow) HashEntry(texRef);
}
void CTextureRefs::DeleteHashEntry(HashEntry *entry)
{
// Update size and delete hash entry
m_size--;
delete entry;
}
void CTextureRefs::DeleteAllHashEntries()
{
if (!m_hashEntries)
return;
// Delete all hash entries and their storage
for (unsigned i = 0; i < m_hashCapacity; i++)
{
HashEntry *entry = m_hashEntries[i];
if (entry)
delete entry;
}
delete[] m_hashEntries;
}
bool CTextureRefs::AddToHash(unsigned texRef)
{
// Convert texture reference to hash value
unsigned hash = texRef % m_hashCapacity;
// Loop through linked list for hash value and see if have texture reference already
HashEntry *headEntry = m_hashEntries[hash];
HashEntry *entry = headEntry;
while (entry && texRef != entry->texRef)
entry = entry->nextEntry;
// If found, nothing to do
if (entry)
return true;
// Otherwise, create new hash entry for texture reference
bool hashCapacityUpdated;
entry = CreateHashEntry(texRef, hashCapacityUpdated);
// If couldn't create entry (ie out of memory), let caller know
if (!entry)
return false;
if (hashCapacityUpdated)
{
// If hash capacity was increased recalculate hash value
hash = texRef % m_hashCapacity;
headEntry = m_hashEntries[hash];
}
// Store hash entry in linked list for hash value
entry->nextEntry = headEntry;
m_hashEntries[hash] = entry;
return true;
}
bool CTextureRefs::RemoveFromHash(unsigned texRef)
{
// Convert texture reference to hash value
unsigned hash = texRef % m_hashCapacity;
// Loop through linked list for hash value and see if have texture reference
HashEntry *entry = m_hashEntries[hash];
HashEntry *prevEntry = NULL;
while (entry && texRef != entry->texRef)
{
prevEntry = entry;
entry = entry->nextEntry;
}
// If not found, nothing to do
if (!entry)
return false;
// Otherwise, remove entry from linked list for hash value
if (prevEntry)
prevEntry->nextEntry = entry->nextEntry;
else
m_hashEntries[hash] = entry->nextEntry;
// Delete hash entry storage
DeleteHashEntry(entry);
return true;
}
bool CTextureRefs::HashContains(unsigned texRef) const
{
// Convert texture reference to hash value
unsigned hash = texRef % m_hashCapacity;
// Loop through linked list for hash value and see if have texture reference
HashEntry *entry = m_hashEntries[hash];
while (entry && texRef != entry->texRef)
entry = entry->nextEntry;
return !!entry;
}
} // Legacy3D