thug/Code/Gfx/NGPS/NX/chars.cpp
2016-02-14 08:39:12 +11:00

756 lines
18 KiB
C++

#include <string.h>
#include <core/defines.h>
#include <core/debug.h>
#include <core/string/stringutils.h>
#include <sys/file/filesys.h>
#include <sifdev.h>
#include <stdlib.h>
#include <gfx/nxfontman.h>
#include "chars.h"
#include "dma.h"
#include "vif.h"
#include "vu1.h"
#include "gif.h"
#include "gs.h"
#include "vu1code.h"
#include "texturemem.h"
#include "render.h"
#include "switches.h"
/*
.fnt file format (by Ryan)
--------------------------
4 File size in bytes
4 Number of characters
4 Default height
4 Default base
? Character table (see below)
? Texture (see below)
Character
2 Baseline (how many pixels down relative to top of image)
2 Ascii value
Texture
4 Size of texture
2 Width
2 Height
2 Bit depth
6 Padding
W*H Raw data
0-3 Padding for uint32 alignment
1K Palette data
4 Number of subtextures
? Subtexture table (see below)
Subtexture
2 X
2 Y
2 W
2 H
font->vram upload format
------------------------
GIFtag PACKED 4
BITBLTBUF ?
TRXPOS 0
TRXREG w,h
TRXDIR 0
GIFtag IMAGE (w*h+15)/16
... (tex data)
GIFtag PACKED 4
BITBLTBUF ?
TRXPOS 0
TRXREG w,h
TRXDIR 0
GIFtag IMAGE 64
... (clut data)
total size in bytes = 12*16+((w*h+15)/16)*16+1024 = 1216+((w*h+15)/16)*16
this will be referenced as follows:
dma ref
NOP
DIRECT 76+(w*h+15)/16
*/
namespace NxPs2
{
float CurrentScale;
uint32 FontVramStart;
uint32 FontVramBase;
SFont *pFontList;
SFont *pButtonsFont = NULL;
// Garrett: Now uses both 648K buffers at once, so we'd need to undo this if we want to
// implement double buffering.
const uint32 FontVramSize = 0xA20 * 2; // 648K * 2
extern TextureMemoryLayout TexMemLayout;
SFont *SText::spOverrideFont = NULL;
void * OpenExtended(const char *Filename)
{
char *ExtendedFilename;
// allocate memory for extended name
ExtendedFilename = (char *)Mem::Malloc(strlen(Filename)+17); // 12 for "fonts/" plus 4 for ".fnt" plus 1 for \0
Dbg_MsgAssert(ExtendedFilename, ("couldn't malloc memory for extended filename fonts/%s", Filename));
// copy and extend name
strcpy(ExtendedFilename, "fonts/");
strcat(ExtendedFilename, Filename);
strcat(ExtendedFilename, ".fnt.ps2");
// open file
void *pFile = File::Open(ExtendedFilename, "rb");
Dbg_MsgAssert(pFile>=0, ("couldn't open file %s", Filename));
// free memory
Mem::Free(ExtendedFilename);
return pFile;
}
SFont * LoadFont(const char *Filename)
{
SFont *pFont;
SChar *pChar;
uint8 *pData;
void *pFile;
int i,j,Len,NumChars,Width,Height,NumQWords,NumBytes;
uint32 TW,TH,TBW;
uint32 temp;
uint8* old_dma_loc = dma::pLoc;
// open the font file
pFile = OpenExtended(Filename);
// allocate memory for the font structure
pFont = (SFont *)Mem::Malloc(sizeof(SFont));
Dbg_MsgAssert(pFont, ("couldn't malloc memory for SFont"));
// allocate a temporary buffer
uint8 FontBuf[2048];
Dbg_MsgAssert(FontBuf, ("couldn't malloc memory for FontBuf"));
// load file header
Len = File::Read(FontBuf, 1, 16, pFile);
Dbg_MsgAssert(Len==16, ("couldn't read file header from font file %s", Filename));
NumChars = ((uint32 *)FontBuf)[1];
pFont->DefaultHeight = ((uint32 *)FontBuf)[2] << 4; // *16 for texel coords
pFont->DefaultBase = ((uint32 *)FontBuf)[3] << 4; // *16 for texel coords
// clear character map to zero
memset(pFont->Map, 0, 256);
memset(pFont->SpecialMap, 0, 32);
// allocate memory for character table
pFont->pChars = (SChar *)Mem::Malloc(NumChars*sizeof(SChar));
Dbg_MsgAssert(pFont->pChars, ("couldn't malloc memory for font character table"));
// load character map and character table
Len = File::Read(FontBuf, 1, NumChars<<2, pFile);
Dbg_MsgAssert(Len==(NumChars<<2), ("couldn't read character table in font file %s", Filename));
for (i=0,pChar=pFont->pChars,pData=FontBuf; i<NumChars; i++,pChar++,pData+=4)
{
pChar->Baseline = ((uint16 *)pData)[0] << 4; // *16 for texel coords
sint16 ascii_val = ((sint16 *)pData)[1];
if (ascii_val >= 0)
pFont->Map[(uint8) ascii_val] = i;
else
{
Dbg_Assert(ascii_val >= -32)
pFont->SpecialMap[(uint8) (-ascii_val - 1)] = i;
}
}
// now, if there is a null character in the font, make characters that could not be found
// in the font display that instead of 'A'
if (pFont->SpecialMap[31] != 0)
{
for (i = 0; i < 256; i++)
{
if (pFont->Map[i] == 0 && i != 'A' && i != 'a') pFont->Map[i] = pFont->SpecialMap[31];
if (i < 31 && pFont->SpecialMap[i] == 0) pFont->SpecialMap[i] = pFont->SpecialMap[31];
}
}
// load texture header
Len = File::Read(FontBuf, 1, 16, pFile);
Dbg_MsgAssert(Len==16, ("couldn't read texture header from font file %s", Filename));
Width = ((uint16 *)FontBuf)[2];
Height = ((uint16 *)FontBuf)[3];
// allocate memory for vif stream
pFont->VifSize = 1216 + (((Width*Height+15)>>4)<<4);
pFont->pVifData = (uint8 *)Mem::Malloc(pFont->VifSize);
Dbg_MsgAssert(pFont->pVifData, ("couldn't malloc memory for vif data"));
Dbg_MsgAssert(((uint32)pFont->pVifData & 15) == 0, ("vif data not qword aligned"));
// build dma data in this buffer
dma::pLoc = pFont->pVifData;
// Calculate memory needed
int min_width = Width, min_height = Height;
TexMemLayout.getMinSize(min_width, min_height, PSMT8);
pFont->m_NumTexVramBlocks = TexMemLayout.getImageSize(min_width, min_height, PSMT8);
pFont->m_NumClutVramBlocks = 4;
// set base pointers and texture buffer width
pFont->m_CBP = FontVramBase;
pFont->m_TBP = FontVramBase + pFont->m_NumClutVramBlocks;
TBW = (Width+63) >> 6;
if (TBW<2)
TBW=2;
// texture upload format:
//
// GIFtag PACKED 4
// BITBLTBUF ?
// TRXPOS 0
// TRXREG w,h
// TRXDIR 0
//
// GIFtag IMAGE (w*h+15)/16
// ... (tex data)
// texture upload header
NumQWords = (Width*Height+15)>>4;
gif::Tag2(gs::A_D,1,PACKED,0,0,0,4);
pFont->mp_TexBitBltBuf = gs::Reg2_pData(gs::BITBLTBUF,PackBITBLTBUF(0,0,0,pFont->m_TBP,TBW,PSMT8));
gs::Reg2(gs::TRXPOS, PackTRXPOS(0,0,0,0,0));
gs::Reg2(gs::TRXREG, PackTRXREG(Width,Height));
gs::Reg2(gs::TRXDIR, 0);
gif::Tag2(0, 0, IMAGE, 0, 0, 0, NumQWords);
// read texture bitmap data
NumBytes = (Width*Height+3)&0xFFFFFFFC;
Len = File::Read(dma::pLoc, 1, NumBytes, pFile);
Dbg_MsgAssert(Len==NumBytes, ("couldn't read texture bitmap from font file %s", Filename));
dma::pLoc += NumQWords<<4;
// clut upload format:
//
// GIFtag PACKED 4
// BITBLTBUF ?
// TRXPOS 0
// TRXREG w,h
// TRXDIR 0
//
// GIFtag IMAGE 64
// ... (clut data)
// clut upload header
gif::Tag2(gs::A_D,1,PACKED,0,0,0,4);
pFont->mp_ClutBitBltBuf = gs::Reg2_pData(gs::BITBLTBUF,PackBITBLTBUF(0,0,0,pFont->m_CBP,1,PSMCT32));
gs::Reg2(gs::TRXPOS, PackTRXPOS(0,0,0,0,0));
gs::Reg2(gs::TRXREG, PackTRXREG(16,16));
gs::Reg2(gs::TRXDIR, 0);
gif::Tag2(0,0,IMAGE,0,0,0,64);
// read clut bitmap data
Len = File::Read(dma::pLoc, 1, 1024, pFile);
Dbg_MsgAssert(Len==1024, ("couldn't read clut bitmap from font file %s", Filename));
// swizzle the clut data
for (i=0; i<256; i+=32)
for (j=0; j<8; j++)
{
temp = ((uint32 *)dma::pLoc)[i+j+8];
((uint32 *)dma::pLoc)[i+j+8] = ((uint32 *)dma::pLoc)[i+j+16];
((uint32 *)dma::pLoc)[i+j+16] = temp;
}
// step dma location
dma::pLoc += 1024;
// skip numsubtextures, and load subtextures
Len = File::Read(FontBuf, 1, (NumChars<<3)+4, pFile);
Dbg_MsgAssert(Len==(NumChars<<3)+4, ("couldn't read subtexture table from font file %s", Filename));
for (i=0,pChar=pFont->pChars,pData=FontBuf+4; i<NumChars; i++,pChar++,pData+=8)
{
pChar->u0 = (((uint16 *)pData)[0] << 4) + 8;
pChar->v0 = (((uint16 *)pData)[1] << 4) + 8;
pChar->u1 = pChar->u0 + (((uint16 *)pData)[2] << 4);
pChar->v1 = pChar->v0 + (((uint16 *)pData)[3] << 4);
}
// set font GS context
for (TW=0; (1<<TW)<Width; TW++)
;
for (TH=0; (1<<TH)<Height; TH++)
;
pFont->RegTEX0 = PackTEX0(pFont->m_TBP,TBW,PSMT8,TW,TH,1,MODULATE,pFont->m_CBP,PSMCT32,0,0,1);
pFont->RegTEX1 = PackTEX1(1,0,LINEAR,LINEAR,0,0,0);
// update vram base pointer
//FontVramBase = (((pFont->TBP<<8) + (TBW << 6)*Height + 0x1FFF) & 0xFFFFE000) >> 8;
FontVramBase += (pFont->m_NumTexVramBlocks + pFont->m_NumClutVramBlocks);
Dbg_MsgAssert(FontVramBase < (FontVramStart + FontVramSize), ("Font: 2D VRAM Buffer Full: %x.", FontVramBase));
// add font to font list
pFont->pNext = pFontList;
pFontList = pFont;
// we're done with the font file now
File::Close(pFile);
// And restore the CurrentDMALoc, which we were just using temporarily.
dma::pLoc = old_dma_loc;
// this will serve as the default spacing
pFont->mSpaceSpacing = (pFont->pChars[pFont->Map['I']].u1 - pFont->pChars[pFont->Map['I']].u0) >> 4;
return pFont;
}
void UnloadFont(SFont *pFont)
{
SFont *pPrevFont;
int found=0;
// find font and unchain from list
if (pFontList == pFont)
{
found=1;
pFontList = pFontList->pNext;
}
else
{
for (pPrevFont=pFontList; pPrevFont->pNext; pPrevFont=pPrevFont->pNext)
{
if (pPrevFont->pNext == pFont)
{
found=1;
pPrevFont->pNext = pFont->pNext;
break;
}
}
}
Dbg_MsgAssert(found, ("attempt to unload font which has not been loaded"));
// free memory
Mem::Free(pFont->pChars);
Mem::Free(pFont->pVifData);
Mem::Free(pFont);
// and re-do VRAM allocation
VRAMNeedsUpdating();
}
/////////////////////////////////////////////////////////////////////////////
//
void SFont::ReallocateVRAM()
{
// calculate TBP
m_TBP = FontVramBase;
FontVramBase += m_NumTexVramBlocks;
// calculate CBP
m_CBP = FontVramBase;
FontVramBase += m_NumClutVramBlocks;
// bail if VRAM would become over-packed
if (FontVramBase > (FontVramStart + FontVramSize))
{
Dbg_MsgAssert(0, ("SingleTexture Realloc: 2D VRAM Buffer Full: %x.", FontVramBase));
}
// Modify the data
gs::ModifyTBP(gs::TEX0_1, (uint32 *) &RegTEX0, m_TBP, m_CBP);
}
/////////////////////////////////////////////////////////////////////////////
//
void SFont::UpdateDMA()
{
// Modify the packets
gs::ModifyTBP(gs::BITBLTBUF, mp_TexBitBltBuf, m_TBP);
if (mp_ClutBitBltBuf)
{
gs::ModifyTBP(gs::BITBLTBUF, mp_ClutBitBltBuf, m_CBP);
}
}
uint32 SFont::GetDefaultHeight() const
{
return DefaultHeight;
}
uint32 SFont::GetDefaultBase() const
{
return DefaultBase;
}
void SFont::QueryString(char *String, float &width, float &height)
{
SChar *pChar;
char *pLetter;
uint16 u0,v0,x0,u1,v1,x1;
x0 = 0;
//sint16 char_spacing = 16 + (mCharSpacing<<4);
sint16 char_spacing = mCharSpacing<<4;
sint16 space_spacing = mSpaceSpacing<<4;
for (pLetter=String;; pLetter++)
{
pChar = NULL;
// may be overridden by the '\b' tag
SFont *p_font = this;
// acount for tags (might be multiple ones in a row)
bool got_char_tag = false; // tag resulting in output of character
while (*pLetter == '\\' && !got_char_tag)
{
pLetter++;
if (*pLetter == '\\')
break;
switch(*pLetter)
{
case '\\':
got_char_tag = true;
break;
case 'c':
case 'C':
pLetter += 2; // skip over "c#"
break;
case 's':
case 'S':
{
pLetter++; // skip "s"
uint digit = Str::DehexifyDigit(pLetter);
pChar = pChars + SpecialMap[digit];
got_char_tag = true;
break;
}
case 'b':
case 'B':
{
pLetter++; // skip "b"
uint digit = Str::DehexifyDigit(pLetter);
// switch over to buttons font, the regular font will be used again on the next character
p_font = pButtonsFont;
Dbg_Assert(p_font);
pChar = p_font->pChars + p_font->SpecialMap[digit];
got_char_tag = true;
break;
}
case 'm':
case 'M':
{
pLetter++; // skip "m"
char button_char = Nx::CFontManager::sMapMetaCharacterToButton(pLetter);
uint digit = Str::DehexifyDigit(&button_char);
p_font = pButtonsFont;
Dbg_Assert(p_font);
pChar = p_font->pChars + p_font->SpecialMap[digit];
got_char_tag = true;
break;
}
default:
Dbg_MsgAssert(0, ("unknown tag %c", *pLetter));
break;
}
} // end while
if (*pLetter == '\0') break;
if (*pLetter != ' ' || pChar)
{
if (!pChar)
pChar = p_font->pChars + p_font->Map[(uint8)*pLetter];
u0 = pChar->u0;
v0 = pChar->v0;
u1 = pChar->u1;
v1 = pChar->v1;
x1 = x0+u1-u0;
}
else
{
x1 = x0 + space_spacing;
}
x0 = x1+char_spacing;
}
width = (float)x0/16.0f;
height = (float)DefaultHeight/16.0f;
}
/////////////////////////////////////////////////////////////////////////////
//
SText::SText(float pri) : SDraw2D(pri, true)
{
}
/////////////////////////////////////////////////////////////////////////////
//
SText::~SText()
{
}
/////////////////////////////////////////////////////////////////////////////
//
void SText::BeginDraw()
{
//printf("Trying to draw: %s with %x at (%f, %f) of scale %f\n", mp_string, m_rgba, m_xpos, m_ypos, m_scale);
// Subsequent processing within Draw() will use this font
// Draw() may call this function to temporarily switch fonts
SFont *p_font = (spOverrideFont) ? spOverrideFont : mp_font;
// dma tag
dma::BeginTag(dma::cnt, 0);
vu1::Buffer = vu1::Loc;
Dbg_Assert(GetScissorWindow());
// GS context
gs::BeginPrim(ABS,0,0);
gs::Reg1(gs::SCISSOR_1, GetScissorWindow()->GetScissorReg());
gs::Reg1(gs::TEX0_1, p_font->RegTEX0);
gs::Reg1(gs::TEX1_1, p_font->RegTEX1);
gs::Reg1(gs::ALPHA_1, PackALPHA(0,1,0,1,0));
gs::Reg1(gs::COLCLAMP, PackCOLCLAMP(1));
gs::Reg1(gs::RGBAQ, (uint64)m_rgba);
gs::EndPrim(0);
// unpack giftag
vif::UNPACK(0, V4_32, 1, ABS, UNSIGNED, 0);
gif::BeginTagImmediate(gs::UV|gs::XYZ2<<4, 2, PACKED, SPRITE|TME|FST|ABE, 1, VU1_ADDR(GSPrim));
// begin unpack for uv/xyz's
vif::BeginUNPACK(0, V4_32, ABS, SIGNED, 1);
}
/////////////////////////////////////////////////////////////////////////////
//
void SText::Draw()
{
SChar *pChar = NULL;
char *pLetter;
uint16 u0,v0,u1,v1;
uint32 x0,y0,x1,y1,yt,z;
Dbg_Assert(GetScissorWindow());
x0 = (int)(m_xpos * s_screen_scale_x * 16.0f) + XOFFSET + ((GetScissorWindow()->GetScissorReg() & 0x7FF) << 4);
y0 = (int)(m_ypos * s_screen_scale_y * 16.0f) + YOFFSET + ((GetScissorWindow()->GetScissorReg() >> 32 & 0x7FF) << 4);
z = s_constant_z_enabled ? s_constant_z : GetZValue();
//sint16 char_spacing = 16 + (mp_font->mCharSpacing<<4);
float char_spacing = (float) (mp_font->mCharSpacing<<4) * m_xscale * s_screen_scale_x;
float space_spacing = (float) (mp_font->mSpaceSpacing<<4) * m_xscale * s_screen_scale_x;
// XXX
//printf(" spacing=%f, %d\n", char_spacing, mp_font->mCharSpacing);
for (pLetter=mp_string;; pLetter++)
{
pChar = NULL;
SFont *p_font = mp_font;
// acount for tags (might be multiple ones in a row)
bool got_char_tag = false; // tag resulting in output of character
while (*pLetter == '\\' && !got_char_tag)
{
pLetter++;
switch(*pLetter)
{
case '\\':
got_char_tag = true;
break;
case 'c':
case 'C':
{
pLetter++; // skip "c"
uint digit = Str::DehexifyDigit(pLetter);
pLetter++; // skip "#"
// BIG MIKE (remove #if below, replace active_color_gs_register_thingy with GS code)
#if 1
// set active color from font
uint32 temp_rgba = m_rgba;
if (digit != 0 && !m_color_override)
{
m_rgba = mp_font->mRGBATab[digit-1];
}
// This method is not optimal, but it's not done very often
// we just turn off text, and turn it on with new colors
EndDraw();
BeginDraw();
m_rgba = temp_rgba;
#endif
break;
}
case 's':
case 'S':
{
pLetter++; // skip "s"
uint digit = Str::DehexifyDigit(pLetter);
pChar = mp_font->pChars + mp_font->SpecialMap[digit];
got_char_tag = true;
break;
}
case 'b':
case 'B':
{
// 'B' stands for button, accesses the button font
pLetter++; // skip "b"
uint digit = Str::DehexifyDigit(pLetter);
// switch to the buttons font!
p_font = pButtonsFont;
Dbg_Assert(p_font);
pChar = p_font->pChars + p_font->SpecialMap[digit];
got_char_tag = true;
spOverrideFont = p_font;
EndDraw();
BeginDraw();
// we can now return to using the regular font (no override)
spOverrideFont = NULL;
break;
}
default:
Dbg_MsgAssert(0, ("unknown tag %c", *pLetter));
break;
}
} // end while
if (*pLetter == '\0') break;
if (*pLetter != ' ' || pChar)
{
if (!pChar)
pChar = p_font->pChars + p_font->Map[(uint8) *pLetter];
yt = y0 + (int)((float)(p_font->DefaultBase - pChar->Baseline) * m_yscale * s_screen_scale_y);
u0 = pChar->u0;
v0 = pChar->v0;
u1 = pChar->u1;
v1 = pChar->v1;
x1 = x0+(int)((float)(u1-u0)*m_xscale * s_screen_scale_x);
y1 = yt+(int)((float)(v1-v0)*m_yscale * s_screen_scale_y);
}
else
{
x0 += (int) (space_spacing + char_spacing);
continue;
}
if (!NeedsCulling() || !((x0 | x1 | y0 | y1) & 0xFFFF0000))
{
vif::StoreV4_32(u0,v0,0,0);
vif::StoreV4_32(x0,yt,z,0);
vif::StoreV4_32(u1,v1,0,0);
vif::StoreV4_32(x1,y1,z,0);
}
x0 = x1 + (int) char_spacing;
// kick and start a new buffer if this one is full
if (((dma::pLoc - gif::pTag)>>4) >= 250)
{
vif::EndUNPACK();
gif::EndTagImmediate(1);
vif::MSCAL(VU1_ADDR(Parser));
vif::UNPACK(0, V4_32, 1, ABS, UNSIGNED, 0);
gif::BeginTagImmediate(gs::UV|gs::XYZ2<<4, 2, PACKED, SPRITE|TME|FST|ABE, 1, VU1_ADDR(GSPrim));
vif::BeginUNPACK(0, V4_32, ABS, SIGNED, 1);
}
if (p_font != mp_font)
{
// we just used the button font, so return to the regular one
EndDraw();
BeginDraw();
}
} // end for
}
/////////////////////////////////////////////////////////////////////////////
//
void SText::EndDraw(void)
{
vif::EndUNPACK();
gif::EndTagImmediate(1);
vif::MSCAL(VU1_ADDR(Parser));
dma::EndTag();
}
} // namespace NxPs2