Implement sub 8x8 tile encoding. Fixes incomplete mipmap chains which previously only went down to 8x8 pixels before. It wasn't known these textures existed before. (Harry Tuttle)

This commit is contained in:
Ian Curtis 2017-12-17 22:25:50 +00:00
parent cede67468c
commit cc28d5f00e
3 changed files with 233 additions and 220 deletions
Src
Graphics/New3D
Model3

View file

@ -306,8 +306,8 @@ UINT32 Texture::UploadTexture(const UINT16* src, UINT8* scratch, int format, boo
int page = y / 1024; int page = y / 1024;
y -= (page * 1024); // remove page from tex y y -= (page * 1024); // remove page from tex y
for (int i = 0; width >= 8 && height >= 8; i++) { for (int i = 0; width > 0 && height > 0; i++) {
int xPos = mipXBase[i] + (x / mipDivisor[i]); int xPos = mipXBase[i] + (x / mipDivisor[i]);
int yPos = mipYBase[i] + (y / mipDivisor[i]); int yPos = mipYBase[i] + (y / mipDivisor[i]);
@ -375,7 +375,7 @@ void Texture::CreateTextureObject(int format, bool mirrorU, bool mirrorV, int x,
int minD = std::min(width, height); int minD = std::min(width, height);
int count = 0; int count = 0;
while (minD > 8) { while (minD > 1) {
minD /= 2; minD /= 2;
count++; count++;
} }

View file

@ -45,6 +45,7 @@
#include "Model3/JTAG.h" #include "Model3/JTAG.h"
#include "Util/BMPFile.h" #include "Util/BMPFile.h"
#include <cstring> #include <cstring>
#include <algorithm>
// Macros that divide memory regions into pages and mark them as dirty when they are written to // Macros that divide memory regions into pages and mark them as dirty when they are written to
#define PAGE_WIDTH 12 #define PAGE_WIDTH 12
@ -259,18 +260,18 @@ uint32_t CReal3D::UpdateSnapshots(bool copyWhole)
void CReal3D::BeginFrame(void) void CReal3D::BeginFrame(void)
{ {
// If multi-threaded, perform now any queued texture uploads to renderer before rendering begins // If multi-threaded, perform now any queued texture uploads to renderer before rendering begins
if (m_gpuMultiThreaded) if (m_gpuMultiThreaded)
{ {
for (const auto &it : queuedUploadTexturesRO) { for (const auto &it : queuedUploadTexturesRO) {
Render3D->UploadTextures(it.level, it.x, it.y, it.width, it.height); Render3D->UploadTextures(it.level, it.x, it.y, it.width, it.height);
} }
// done syncing data // done syncing data
queuedUploadTexturesRO.clear(); queuedUploadTexturesRO.clear();
} }
Render3D->BeginFrame(); Render3D->BeginFrame();
} }
void CReal3D::RenderFrame(void) void CReal3D::RenderFrame(void)
@ -287,241 +288,252 @@ void CReal3D::EndFrame(void)
/****************************************************************************** /******************************************************************************
Texture Uploading and Decoding Texture Uploading and Decoding
******************************************************************************/ ******************************************************************************/
// Mipmap coordinates for each reduction level (within a single 2048x1024 page) // Mipmap coordinates for each reduction level (within a single 2048x1024 page)
static const int mipXBase[11] =
{
1024, // 1024/2
1536, // 512/2
1792, // 256/2
1920, // ...
1984,
2016,
2032,
2040,
2044,
2046,
2047
};
static const int mipYBase[11] = const int mipXBase[] = { 0, 1024, 1536, 1792, 1920, 1984, 2016, 2032, 2040, 2044, 2046, 2047 };
const int mipYBase[] = { 0, 512, 768, 896, 960, 992, 1008, 1016, 1020, 1022, 1023 };
const int mipDivisor[] = { 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024 };
// Tables of texel offsets corresponding to an NxN texel texture tile
static const unsigned decode8x8[64] =
{
1, 0, 5, 4, 9, 8,13,12,
3, 2, 7, 6,11,10,15,14,
17,16,21,20,25,24,29,28,
19,18,23,22,27,26,31,30,
33,32,37,36,41,40,45,44,
35,34,39,38,43,42,47,46,
49,48,53,52,57,56,61,60,
51,50,55,54,59,58,63,62
};
static const unsigned decode8x4[32] =
{
1, 0, 5, 4,
3, 2, 7, 6,
9, 8,13,12,
11,10,15,14,
17,16,21,20,
19,18,23,22,
25,24,29,28,
27,26,31,30
};
static const unsigned decode8x2[16] =
{
1, 0,
3, 2,
5, 4,
7, 6,
9, 8,
11, 10,
13, 12,
15, 14
};
static const unsigned decode8x1[8] =
{
1,
3,
0,
2,
5,
7,
4,
6
};
static void StoreTexelByte(uint16_t *texel, bool writeLSB, bool writeMSB, uint8_t byte)
{ {
512, if (writeLSB) // write to least significant byte
768, *texel = (*texel & 0xFF00) | byte;
896, if (writeMSB) // write to most significant byte
960, *texel = (*texel & 0x00FF) | (uint16_t(byte) << 8);
992, }
1008,
1016, void CReal3D::StoreTexture(unsigned level, unsigned xPos, unsigned yPos, unsigned width, unsigned height, const uint16_t *texData, bool sixteenBit, bool writeLSB, bool writeMSB, uint32_t &texDataOffset)
1020, {
1022, uint32_t tileX = std::min(8u, width);
1023, uint32_t tileY = std::min(8u, height);
0
}; texDataOffset = 0;
// Mipmap reduction factors if (sixteenBit) // 16-bit textures
static const int mipDivisor[9] = { 2, 4, 8, 16, 32, 64, 128, 256, 512 }; {
// Outer 2 loops: NxN tiles
// Table of texel offsets corresponding to an 8x8 texel texture tile for (uint32_t y = yPos; y < (yPos + height); y += tileY)
static const unsigned decode[64] = {
{ for (uint32_t x = xPos; x < (xPos + width); x += tileX)
0, 1, 4, 5, 8, 9,12,13, {
2, 3, 6, 7,10,11,14,15, // Inner 2 loops: NxN texels for the current tile
16,17,20,21,24,25,28,29, uint32_t destOffset = y * 2048 + x;
18,19,22,23,26,27,30,31, for (uint32_t yy = 0; yy < tileY; yy++)
32,33,36,37,40,41,44,45, {
34,35,38,39,42,43,46,47, for (uint32_t xx = 0; xx < tileX; xx++)
48,49,52,53,56,57,60,61, {
50,51,54,55,58,59,62,63 if (m_gpuMultiThreaded)
}; MARK_DIRTY(textureRAMDirty, destOffset * 2);
if (tileX == 1) texData -= tileY;
static void StoreTexelByte(uint16_t *texel, uint32_t byteSelect, uint8_t byte) if (tileY == 1) texData -= tileX;
{ if (tileX == 8)
if ((byteSelect & 1)) // write to LSB textureRAM[destOffset++] = texData[decode8x8[yy * tileX + xx]];
*texel = (*texel & 0xFF00) | byte; else if (tileX == 4)
if ((byteSelect & 2)) // write to MSB textureRAM[destOffset++] = texData[decode8x4[yy * tileX + xx]];
*texel = (*texel & 0x00FF) | (uint16_t(byte) << 8); else if (tileX == 2)
} textureRAM[destOffset++] = texData[decode8x2[yy * tileX + xx]];
else if (tileX == 1)
void CReal3D::StoreTexture(unsigned level, unsigned xPos, unsigned yPos, unsigned width, unsigned height, const uint16_t *texData, uint32_t header) textureRAM[destOffset++] = texData[decode8x1[yy * tileX + xx]];
{ texDataOffset++;
if ((header & 0x00800000)) // 16-bit textures }
{ destOffset += 2048 - tileX; // next line
// Outer 2 loops: 8x8 tiles }
for (uint32_t y = yPos; y < (yPos+height); y += 8) texData += tileY * tileX; // next tile
{ }
for (uint32_t x = xPos; x < (xPos+width); x += 8) }
{ }
// Inner 2 loops: 8x8 texels for the current tile
uint32_t destOffset = y*2048+x;
for (uint32_t yy = 0; yy < 8; yy++)
{
for (uint32_t xx = 0; xx < 8; xx++)
{
if (m_gpuMultiThreaded)
MARK_DIRTY(textureRAMDirty, destOffset * 2);
textureRAM[destOffset++] = texData[decode[(yy*8+xx)^1]];
}
destOffset += 2048-8; // next line
}
texData += 8*8; // next tile
}
}
}
else // 8-bit textures else // 8-bit textures
{ {
/* /*
* 8-bit textures appear to be unpacked into 16-bit words in the * 8-bit textures appear to be unpacked into 16-bit words in the
* texture RAM. Oddly, the rows of the decoding table seem to be * texture RAM. Oddly, the rows of the decoding table seem to be
* swapped. * swapped.
*/ */
uint32_t byteSelect = (header>>21)&3; // which byte to unpack to if (writeLSB && writeMSB) // write to both?
if (byteSelect == 3) // write to both? DebugLog("Observed 8-bit texture with byte_select=3!");
DebugLog("Observed 8-bit texture with byte_select=3!");
// Outer 2 loops: NxN tiles
// Outer 2 loops: 8x8 tiles uint8_t byte1, byte2;
for (uint32_t y = yPos; y < (yPos+height); y += 8) for (uint32_t y = yPos; y < (yPos + height); y += tileY)
{ {
for (uint32_t x = xPos; x < (xPos+width); x += 8) for (uint32_t x = xPos; x < (xPos + width); x += tileX)
{ {
// Inner 2 loops: 8x8 texels for the current tile // Inner 2 loops: NxN texels for the current tile
uint32_t destOffset = y*2048+x; uint32_t destOffset = y * 2048 + x;
for (uint32_t yy = 0; yy < 8; yy++) for (uint32_t yy = 0; yy < tileY; yy++)
{ {
for (uint32_t xx = 0; xx < 8; xx += 2) for (uint32_t xx = 0; xx < tileX; xx += 2)
{ {
uint8_t byte1 = texData[decode[(yy^1)*8+((xx+0)^1)]/2]>>8; if (tileX == 1) texData -= std::max(1u, tileY / 2);
uint8_t byte2 = texData[decode[(yy^1)*8+((xx+1)^1)]/2]&0xFF; if (tileY == 1) texData -= std::max(1u, tileX / 2);
if (m_gpuMultiThreaded) if (tileX == 8) {
MARK_DIRTY(textureRAMDirty, destOffset * 2); byte1 = texData[decode8x8[(yy^1) * tileX + ((xx + 0)^1)] / 2] >> 8;
StoreTexelByte(&textureRAM[destOffset], byteSelect, byte1); byte2 = texData[decode8x8[(yy^1) * tileX + ((xx + 1)^1)] / 2] & 0xFF;
}
if (tileX == 4) {
byte1 = texData[decode8x4[(yy^1) * tileX + ((xx + 0)^1)] / 2] >> 8;
byte2 = texData[decode8x4[(yy^1) * tileX + ((xx + 1)^1)] / 2] & 0xFF;
}
if (tileX == 2) {
byte1 = texData[decode8x2[(yy^1) * tileX + ((xx + 0)^1)] / 2] >> 8;
byte2 = texData[decode8x2[(yy^1) * tileX + ((xx + 1)^1)] / 2] & 0xFF;
}
if (tileX == 1) {
byte1 = texData[decode8x1[(yy^1) * tileX + ((xx + 0)^1)] / 2] >> 8;
byte2 = texData[decode8x1[(yy^1) * tileX + ((xx + 0)^1)] / 2] & 0xFF;
}
if (m_gpuMultiThreaded)
MARK_DIRTY(textureRAMDirty, destOffset * 2);
StoreTexelByte(&textureRAM[destOffset], writeLSB, writeMSB, byte1);
++destOffset; ++destOffset;
if (m_gpuMultiThreaded) if (m_gpuMultiThreaded)
MARK_DIRTY(textureRAMDirty, destOffset * 2); MARK_DIRTY(textureRAMDirty, destOffset * 2);
StoreTexelByte(&textureRAM[destOffset], byteSelect, byte2); StoreTexelByte(&textureRAM[destOffset], writeLSB, writeMSB, byte2);
++destOffset; ++destOffset;
} }
destOffset += 2048-8; destOffset += 2048 - tileX; // next line
} }
texData += 8*8/2; // next tile uint32_t offset = std::max(1u, (tileY * tileX) / 2);
} texData += offset; // next tile
} texDataOffset += offset; // next tile
} }
}
}
// Signal to renderer that textures have changed // Signal to renderer that textures have changed
// TO-DO: mipmaps? What if a game writes non-mipmap textures to mipmap area? // TO-DO: mipmaps? What if a game writes non-mipmap textures to mipmap area?
if (m_gpuMultiThreaded) if (m_gpuMultiThreaded)
{ {
// If multi-threaded, then queue calls to UploadTextures for render thread to perform at beginning of next frame // If multi-threaded, then queue calls to UploadTextures for render thread to perform at beginning of next frame
QueuedUploadTextures upl; QueuedUploadTextures upl;
upl.level = level; upl.level = level;
upl.x = xPos; upl.x = xPos;
upl.y = yPos; upl.y = yPos;
upl.width = width; upl.width = width;
upl.height = height; upl.height = height;
queuedUploadTextures.push_back(upl); queuedUploadTextures.push_back(upl);
} }
else else
Render3D->UploadTextures(level, xPos, yPos, width, height); Render3D->UploadTextures(level, xPos, yPos, width, height);
} }
/*
Texture header:
-------- -------- -------- --xxxxxx X-position
-------- -------- ----xxxx x------- Y-position
-------- -------x xx------ -------- Width
-------- ----xxx- -------- -------- Height
-------- ---x---- -------- -------- Texture page
-------- --x----- -------- -------- Write 8-bit data to the lower byte of texel
-------- -x------ -------- -------- Write 8-bit data to the upper byte of texel
-------- x------- -------- -------- Bitdepth, 0 = 8-bit, 1 = 16-bit
xxxxxxxx -------- -------- -------- Texture type:
0x00 = texture with mipmaps
0x01 = texture without mipmaps
0x02 = only mipmaps
0x80 = possibly gamma table
*/
// Texture data will be in little endian format // Texture data will be in little endian format
void CReal3D::UploadTexture(uint32_t header, const uint16_t *texData) void CReal3D::UploadTexture(uint32_t header, const uint16_t *texData)
{ {
// Position: texture RAM is arranged as 2 2048x1024 texel sheets // Position: texture RAM is arranged as 2 2048x1024 texel sheets
uint32_t x = 32*(header&0x3F); uint32_t x = 32 * (header & 0x3F);
uint32_t y = 32*((header>>7)&0x1F); uint32_t y = 32 * ((header >> 7) & 0x1F);
uint32_t page = (header>>20)&1; uint32_t page = (header >> 20) & 1;
y += page*1024; // treat page as additional Y bit (one 2048x2048 sheet) uint32_t width = 32 << ((header >> 14) & 7);
uint32_t height = 32 << ((header >> 17) & 7);
// Texture size and bit depth uint32_t type = (header >> 24) & 0xFF;
uint32_t width = 32<<((header>>14)&7); bool sixteenBit = (header >> 23) & 0x1;
uint32_t height = 32<<((header>>17)&7); bool writeUpperByte = (header >> 22) & 0x1;
uint32_t bytesPerTexel; bool writeLowerByte = (header >> 21) & 0x1;
if ((header&0x00800000)) // 16 bits per texel uint32_t offset = 0;
bytesPerTexel = 2;
else // 8 bits switch (type)
{
bytesPerTexel = 1;
//printf("8-bit textures!\n");
}
// Mipmaps
uint32_t mipYPos = 32*((header>>7)&0x1F);
// Process texture data
DebugLog("Real3D: Texture upload: pos=(%d,%d) size=(%d,%d), %d-bit\n", x, y, width, height, bytesPerTexel*8);
//printf("Real3D: Texture upload: pos=(%d,%d) size=(%d,%d), %d-bit\n", x, y, width, height, bytesPerTexel*8);
switch ((header>>24)&0xFF)
{ {
case 0x00: // texture w/ mipmaps case 0x00: // texture w/ mipmaps
{
StoreTexture(0, x, y, width, height, texData, header);
uint32_t mipWidth = width;
uint32_t mipHeight = height;
uint32_t mipNum = 0;
while((mipHeight>8) && (mipWidth>8))
{
if (bytesPerTexel == 1)
texData += (mipWidth*mipHeight)/2;
else
texData += (mipWidth*mipHeight);
mipWidth /= 2;
mipHeight /= 2;
uint32_t mipX = mipXBase[mipNum] + (x / mipDivisor[mipNum]);
uint32_t mipY = mipYBase[mipNum] + (mipYPos / mipDivisor[mipNum]);
if(page)
mipY += 1024;
mipNum++;
StoreTexture(mipNum, mipX, mipY, mipWidth, mipHeight, (uint16_t *)texData, header);
}
break;
}
case 0x01: // texture w/out mipmaps case 0x01: // texture w/out mipmaps
StoreTexture(0, x, y, width, height, texData, header); StoreTexture(0, x, y + (page * 1024), width, height, texData, sixteenBit, writeLowerByte, writeUpperByte, offset);
break; texData += offset;
if (type == 0x01) {
break;
}
case 0x02: // mipmaps only case 0x02: // mipmaps only
{ {
uint32_t mipWidth = width; for (int i = 1; width > 0 && height > 0; i++) {
uint32_t mipHeight = height;
uint32_t mipNum = 0; int xPos = mipXBase[i] + (x / mipDivisor[i]);
while((mipHeight>8) && (mipWidth>8)) int yPos = mipYBase[i] + (y / mipDivisor[i]);
{
mipWidth /= 2; width /= 2;
mipHeight /= 2; height /= 2;
uint32_t mipX = mipXBase[mipNum] + (x / mipDivisor[mipNum]);
uint32_t mipY = mipYBase[mipNum] + (mipYPos / mipDivisor[mipNum]); StoreTexture(i, xPos, yPos + (page * 1024), width, height, texData, sixteenBit, writeLowerByte, writeUpperByte, offset);
if(page) texData += offset;
mipY += 1024;
mipNum++;
StoreTexture(mipNum, mipX, mipY, mipWidth, mipHeight, texData, header);
if (bytesPerTexel == 1)
texData += (mipWidth*mipHeight)/2;
else
texData += (mipWidth*mipHeight);
} }
break; break;
} }
case 0x80: // MAME thinks these might be a gamma table case 0x80: // MAME thinks these might be a gamma table (vf3 uploads this as the first texture)
/*
printf("Special texture format 0x80:\n");
for (int i = 0; i < 32*32; i++)
{
printf(" %02x=%02x\n", i, texData[i]);
}
*/
break; break;
default: // unknown default: // unknown
DebugLog("Unknown texture format %02X\n", header>>24); DebugLog("Unknown texture format %02X\n", type);
//printf("unknown texture format %02X\n", header>>24);
break; break;
} }
} }
/****************************************************************************** /******************************************************************************
DMA Device DMA Device
@ -997,7 +1009,7 @@ CReal3D::CReal3D(const Util::Config::Node &config)
CReal3D::~CReal3D(void) CReal3D::~CReal3D(void)
{ {
// Dump memory // Dump memory
#if 0 #if 1
FILE *fp; FILE *fp;
fp = fopen("8c000000", "wb"); fp = fopen("8c000000", "wb");
if (NULL != fp) if (NULL != fp)

View file

@ -403,13 +403,14 @@ public:
private: private:
// Private member functions // Private member functions
void DMACopy(void); void DMACopy(void);
void InsertBit(uint8_t *buf, unsigned bitNum, unsigned bit); void InsertBit(uint8_t *buf, unsigned bitNum, unsigned bit);
void InsertID(uint32_t id, unsigned startBit); void InsertID(uint32_t id, unsigned startBit);
unsigned Shift(uint8_t *data, unsigned numBits); unsigned Shift(uint8_t *data, unsigned numBits);
void StoreTexture(unsigned level, unsigned xPos, unsigned yPos, unsigned width, unsigned height, const uint16_t *texData, uint32_t header); void StoreTexture(unsigned level, unsigned xPos, unsigned yPos, unsigned width, unsigned height, const uint16_t *texData, bool sixteenBit, bool writeLSB, bool writeMSB, uint32_t &texDataOffset);
void UploadTexture(uint32_t header, const uint16_t *texData);
uint32_t UpdateSnapshots(bool copyWhole); void UploadTexture(uint32_t header, const uint16_t *texData);
uint32_t UpdateSnapshot(bool copyWhole, uint8_t *src, uint8_t *dst, unsigned size, uint8_t *dirty); uint32_t UpdateSnapshots(bool copyWhole);
uint32_t UpdateSnapshot(bool copyWhole, uint8_t *src, uint8_t *dst, unsigned size, uint8_t *dirty);
// Config // Config
const Util::Config::Node &m_config; const Util::Config::Node &m_config;