mirror of
https://github.com/aap/librw.git
synced 2025-01-21 08:11:09 +00:00
implemented PDS plugin and uninstancing in SA pipes
This commit is contained in:
parent
305f93f738
commit
32dfcf0fd2
@ -289,33 +289,36 @@ Geometry::allocateData(void)
|
||||
static int
|
||||
isDegenerate(uint16 *idx)
|
||||
{
|
||||
// TODO: maybe check position instead of index?
|
||||
return idx[0] == idx[1] ||
|
||||
idx[0] == idx[2] ||
|
||||
idx[1] == idx[2];
|
||||
}
|
||||
|
||||
void
|
||||
Geometry::generateTriangles(void)
|
||||
Geometry::generateTriangles(int8 *adc)
|
||||
{
|
||||
MeshHeader *header = this->meshHeader;
|
||||
assert(header != NULL);
|
||||
|
||||
this->numTriangles = 0;
|
||||
Mesh *m = header->mesh;
|
||||
int8 *adcbits = adc;
|
||||
for(uint32 i = 0; i < header->numMeshes; i++){
|
||||
if(m->numIndices < 3){
|
||||
// shouldn't happen but it does
|
||||
adcbits += m->numIndices;
|
||||
m++;
|
||||
continue;
|
||||
}
|
||||
if(header->flags == 1){ // tristrip
|
||||
for(uint32 j = 0; j < m->numIndices-2; j++){
|
||||
if(!isDegenerate(&m->indices[j]))
|
||||
if(!(adc && adcbits[j+2]) &&
|
||||
!isDegenerate(&m->indices[j]))
|
||||
this->numTriangles++;
|
||||
}
|
||||
}else
|
||||
this->numTriangles += m->numIndices/3;
|
||||
adcbits += m->numIndices;
|
||||
m++;
|
||||
}
|
||||
|
||||
@ -324,8 +327,10 @@ Geometry::generateTriangles(void)
|
||||
|
||||
uint16 *f = this->triangles;
|
||||
m = header->mesh;
|
||||
adcbits = adc;
|
||||
for(uint32 i = 0; i < header->numMeshes; i++){
|
||||
if(m->numIndices < 3){
|
||||
adcbits += m->numIndices;
|
||||
m++;
|
||||
continue;
|
||||
}
|
||||
@ -334,7 +339,8 @@ Geometry::generateTriangles(void)
|
||||
this->numMaterials);
|
||||
if(header->flags == 1) // tristrip
|
||||
for(uint32 j = 0; j < m->numIndices-2; j++){
|
||||
if(isDegenerate(&m->indices[j]))
|
||||
if(adc && adcbits[j+2] ||
|
||||
isDegenerate(&m->indices[j]))
|
||||
continue;
|
||||
*f++ = m->indices[j+1 + (j%2)];
|
||||
*f++ = m->indices[j+0];
|
||||
@ -348,6 +354,7 @@ Geometry::generateTriangles(void)
|
||||
*f++ = matid;
|
||||
*f++ = m->indices[j+2];
|
||||
}
|
||||
adcbits += m->numIndices;
|
||||
m++;
|
||||
}
|
||||
}
|
||||
|
431
src/gtaplg.cpp
431
src/gtaplg.cpp
@ -21,7 +21,8 @@ namespace gta {
|
||||
void
|
||||
attachPlugins(void)
|
||||
{
|
||||
rw::ps2::registerPDSPlugin();
|
||||
rw::ps2::registerPDSPlugin(12);
|
||||
gta::registerPDSPipes();
|
||||
|
||||
rw::ps2::registerNativeRaster();
|
||||
rw::xbox::registerNativeRaster();
|
||||
@ -36,12 +37,12 @@ attachPlugins(void)
|
||||
rw::registerHAnimPlugin();
|
||||
gta::registerNodeNamePlugin();
|
||||
rw::registerMatFXPlugin();
|
||||
// rw::registerUVAnimPlugin();
|
||||
rw::registerUVAnimPlugin();
|
||||
rw::ps2::registerADCPlugin();
|
||||
gta::registerExtraNormalsPlugin();
|
||||
gta::registerExtraVertColorPlugin();
|
||||
gta::registerEnvSpecPlugin();
|
||||
// gta::registerBreakableModelPlugin();
|
||||
gta::registerBreakableModelPlugin();
|
||||
gta::registerCollisionPlugin();
|
||||
gta::register2dEffectPlugin();
|
||||
gta::registerPipelinePlugin();
|
||||
@ -300,6 +301,16 @@ registerExtraNormalsPlugin(void)
|
||||
|
||||
int32 extraVertColorOffset;
|
||||
|
||||
void
|
||||
allocateExtraVertColors(Geometry *g)
|
||||
{
|
||||
ExtraVertColors *colordata =
|
||||
PLUGINOFFSET(ExtraVertColors, g, extraVertColorOffset);
|
||||
colordata->nightColors = new uint8[g->numVertices*4];
|
||||
colordata->dayColors = new uint8[g->numVertices*4];
|
||||
colordata->balance = 1.0f;
|
||||
}
|
||||
|
||||
static void*
|
||||
createExtraVertColors(void *object, int32 offset, int32)
|
||||
{
|
||||
@ -759,4 +770,418 @@ registerCollisionPlugin(void)
|
||||
writeCollision, getSizeCollision);
|
||||
}
|
||||
|
||||
/*
|
||||
* PS2
|
||||
*/
|
||||
|
||||
using namespace ps2;
|
||||
|
||||
PipeAttribute saXYZADC = {
|
||||
"saXYZADC",
|
||||
AT_V4_16 | AT_RW
|
||||
};
|
||||
|
||||
PipeAttribute saUV = {
|
||||
"saUV",
|
||||
AT_V2_16 | AT_RW
|
||||
};
|
||||
|
||||
PipeAttribute saUV2 = {
|
||||
"saUV2",
|
||||
AT_V4_16 | AT_RW
|
||||
};
|
||||
|
||||
PipeAttribute saRGBA = {
|
||||
"saRGBA",
|
||||
AT_V4_8 | AT_UNSGN | AT_RW
|
||||
};
|
||||
|
||||
PipeAttribute saRGBA2 = {
|
||||
"saRGBA2",
|
||||
AT_V4_16 | AT_UNSGN | AT_RW
|
||||
};
|
||||
|
||||
PipeAttribute saNormal = {
|
||||
"saNormal",
|
||||
AT_V4_8 | AT_RW
|
||||
};
|
||||
|
||||
PipeAttribute saWeights = {
|
||||
"saWeights",
|
||||
AT_V4_32 | AT_RW
|
||||
};
|
||||
|
||||
static bool hasTex2(uint32 id)
|
||||
{
|
||||
return id == 0x53f2008b;
|
||||
}
|
||||
static bool hasNormals(uint32 id)
|
||||
{
|
||||
return id == 0x53f20085 || id == 0x53f20087 || id == 0x53f20089 ||
|
||||
id == 0x53f2008b || id == 0x53f2008d || id == 0x53f2008f;
|
||||
}
|
||||
static bool hasColors(uint32 id)
|
||||
{
|
||||
return id == 0x53f20081 || id == 0x53f20083 || id == 0x53f2008d || id == 0x53f2008f;
|
||||
}
|
||||
static bool hasColors2(uint32 id)
|
||||
{
|
||||
return id == 0x53f20083 || id == 0x53f2008f;
|
||||
}
|
||||
|
||||
struct SaVert {
|
||||
float32 p[3];
|
||||
float32 n[3];
|
||||
float32 t0[2];
|
||||
float32 t1[2];
|
||||
uint8 c0[4];
|
||||
uint8 c1[4];
|
||||
float32 w[4];
|
||||
uint8 i[4];
|
||||
};
|
||||
|
||||
static void
|
||||
saPreCB(MatPipeline *p, Geometry *geo)
|
||||
{
|
||||
// allocate ADC, extra colors, skin
|
||||
allocateADC(geo);
|
||||
if(hasColors2(p->pluginData) && extraVertColorOffset)
|
||||
allocateExtraVertColors(geo);
|
||||
if(p->pluginData == 0x53f20089)
|
||||
skinPreCB(p, geo);
|
||||
}
|
||||
|
||||
static void
|
||||
saPostCB(MatPipeline *p, Geometry *geo)
|
||||
{
|
||||
skinPostCB(p, geo);
|
||||
}
|
||||
|
||||
int32
|
||||
findSAVertex(Geometry *g, uint32 flags[], uint32 mask, SaVert *v)
|
||||
{
|
||||
Skin *skin = *PLUGINOFFSET(Skin*, g, skinGlobals.offset);
|
||||
float32 *wghts = NULL;
|
||||
uint8 *inds = NULL;
|
||||
if(skin){
|
||||
wghts = skin->weights;
|
||||
inds = skin->indices;
|
||||
}
|
||||
float32 *verts = g->morphTargets[0].vertices;
|
||||
float32 *tex0 = g->texCoords[0];
|
||||
float32 *tex1 = g->texCoords[1];
|
||||
float32 *norms = g->morphTargets[0].normals;
|
||||
uint8 *cols0 = g->colors;
|
||||
uint8 *cols1 = NULL;
|
||||
if(extraVertColorOffset)
|
||||
cols1 = PLUGINOFFSET(ExtraVertColors, g, extraVertColorOffset)->nightColors;
|
||||
|
||||
for(int32 i = 0; i < g->numVertices; i++){
|
||||
if(mask & flags[i] & 0x1 &&
|
||||
!(verts[0] == v->p[0] && verts[1] == v->p[1] && verts[2] == v->p[2]))
|
||||
goto cont;
|
||||
if(mask & flags[i] & 0x10 &&
|
||||
!(norms[0] == v->n[0] && norms[1] == v->n[1] && norms[2] == v->n[2]))
|
||||
goto cont;
|
||||
if(mask & flags[i] & 0x100 &&
|
||||
!(cols0[0] == v->c0[0] && cols0[1] == v->c0[1] &&
|
||||
cols0[2] == v->c0[2] && cols0[3] == v->c0[3]))
|
||||
goto cont;
|
||||
if(mask & flags[i] & 0x200 &&
|
||||
!(cols1[0] == v->c1[0] && cols1[1] == v->c1[1] &&
|
||||
cols1[2] == v->c1[2] && cols1[3] == v->c1[3]))
|
||||
goto cont;
|
||||
if(mask & flags[i] & 0x1000 &&
|
||||
!(tex0[0] == v->t0[0] && tex0[1] == v->t0[1]))
|
||||
goto cont;
|
||||
if(mask & flags[i] & 0x2000 &&
|
||||
!(tex1[0] == v->t1[0] && tex1[1] == v->t1[1]))
|
||||
goto cont;
|
||||
if(mask & flags[i] & 0x10000 &&
|
||||
!(wghts[0] == v->w[0] && wghts[1] == v->w[1] &&
|
||||
wghts[2] == v->w[2] && wghts[3] == v->w[3] &&
|
||||
inds[0] == v->i[0] && inds[1] == v->i[1] &&
|
||||
inds[2] == v->i[2] && inds[3] == v->i[3]))
|
||||
goto cont;
|
||||
return i;
|
||||
cont:
|
||||
verts += 3;
|
||||
tex0 += 2;
|
||||
tex1 += 2;
|
||||
norms += 3;
|
||||
cols0 += 4;
|
||||
cols1 += 4;
|
||||
wghts += 4;
|
||||
inds += 4;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void
|
||||
insertSAVertex(Geometry *geo, int32 i, uint32 mask, SaVert *v)
|
||||
{
|
||||
insertVertex(geo, i, mask, v->p, v->t0, v->t1, v->c0, v->n);
|
||||
if(mask & 0x200 && extraVertColorOffset){
|
||||
uint8 *cols1 =
|
||||
&PLUGINOFFSET(ExtraVertColors, geo, extraVertColorOffset)->nightColors[i*4];
|
||||
cols1[0] = v->c1[0];
|
||||
cols1[1] = v->c1[1];
|
||||
cols1[2] = v->c1[2];
|
||||
cols1[3] = v->c1[3];
|
||||
}
|
||||
if(mask & 0x10000 && skinGlobals.offset){
|
||||
Skin *skin = *PLUGINOFFSET(Skin*, geo, skinGlobals.offset);
|
||||
memcpy(&skin->weights[i*4], v->w, 16);
|
||||
memcpy(&skin->indices[i*4], v->i, 4);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
saUninstanceCB(ps2::MatPipeline *pipe, Geometry *geo, uint32 flags[], Mesh *mesh, uint8 *data[])
|
||||
{
|
||||
uint32 id = pipe->pluginData;
|
||||
int16 *verts = (int16*)data[0];
|
||||
int16 *texcoords = (int16*)data[1];
|
||||
uint8 *colors = (uint8*)data[2];
|
||||
int8 *norms = (int8*)data[id == 0x53f20089 ? 2 : 3];
|
||||
uint32 *wghts = (uint32*)data[3];
|
||||
float vertScale = 1.0f/128.0f;
|
||||
if(id == 0x53f20085 || id == 0x53f20087 ||
|
||||
id == 0x53f20089 || id == 0x53f2008b)
|
||||
vertScale = 1.0f/1024.0f;
|
||||
uint32 mask = 0x1; // vertices
|
||||
int cinc = 4;
|
||||
int tinc = 2;
|
||||
if((geo->geoflags & Geometry::NORMALS) && hasNormals(id))
|
||||
mask |= 0x10;
|
||||
if((geo->geoflags & Geometry::PRELIT) && hasColors(id))
|
||||
mask |= 0x100;
|
||||
if(hasColors2(id)){
|
||||
mask |= 0x200;
|
||||
cinc *= 2;
|
||||
}
|
||||
if(geo->numTexCoordSets > 0)
|
||||
mask |= 0x1000;
|
||||
if(geo->numTexCoordSets > 0 && hasTex2(id)){
|
||||
mask |= 0x2000;
|
||||
tinc *= 2;
|
||||
}
|
||||
if(id == 0x53f20089)
|
||||
mask |= 0x10000;
|
||||
SaVert v;
|
||||
int32 idxstart = 0;
|
||||
for(Mesh *m = geo->meshHeader->mesh; m < mesh; m++)
|
||||
idxstart += m->numIndices;
|
||||
int8 *adc = PLUGINOFFSET(ADCData, geo, adcOffset)->adcBits;
|
||||
for(uint32 i = 0; i < mesh->numIndices; i++){
|
||||
v.p[0] = verts[0]*vertScale;
|
||||
v.p[1] = verts[1]*vertScale;
|
||||
v.p[2] = verts[2]*vertScale;
|
||||
if(mask & 0x10){
|
||||
v.n[0] = norms[0]/127.0f;
|
||||
v.n[1] = norms[1]/127.0f;
|
||||
v.n[2] = norms[2]/127.0f;
|
||||
}
|
||||
if(mask & 0x200){
|
||||
v.c0[0] = colors[0];
|
||||
v.c0[1] = colors[2];
|
||||
v.c0[2] = colors[4];
|
||||
v.c0[3] = colors[6];
|
||||
v.c1[0] = colors[1];
|
||||
v.c1[1] = colors[3];
|
||||
v.c1[2] = colors[5];
|
||||
v.c1[3] = colors[7];
|
||||
}else if(mask & 0x100){
|
||||
v.c0[0] = colors[0];
|
||||
v.c0[1] = colors[1];
|
||||
v.c0[2] = colors[2];
|
||||
v.c0[3] = colors[3];
|
||||
}
|
||||
if(mask & 0x1000){
|
||||
v.t0[0] = texcoords[0]/4096.0f;
|
||||
v.t0[1] = texcoords[1]/4096.0f;
|
||||
}
|
||||
if(mask & 0x2000){
|
||||
v.t1[0] = texcoords[2]/4096.0f;
|
||||
v.t1[1] = texcoords[3]/4096.0f;
|
||||
}
|
||||
if(mask & 0x10000){
|
||||
for(int j = 0; j < 4; j++){
|
||||
((uint32*)v.w)[j] = wghts[j] & ~0x3FF;
|
||||
v.i[j] = (wghts[j] & 0x3FF) >> 2;
|
||||
if(v.i[j]) v.i[j]--;
|
||||
if(v.w[j] == 0.0f) v.i[j] = 0;
|
||||
}
|
||||
}
|
||||
int32 idx = findSAVertex(geo, flags, mask, &v);
|
||||
if(idx < 0)
|
||||
idx = geo->numVertices++;
|
||||
mesh->indices[i] = idx;
|
||||
adc[idxstart+i] = !!verts[3];
|
||||
flags[idx] = mask;
|
||||
insertSAVertex(geo, idx, mask, &v);
|
||||
|
||||
verts += 4;
|
||||
texcoords += tinc;
|
||||
colors += cinc;
|
||||
norms += 4;
|
||||
wghts += 4;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static void
|
||||
saInstanceCB(MatPipeline *, Geometry *g, Mesh *m, uint8 **data, int32 n)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
registerPDSPipes(void)
|
||||
{
|
||||
Pipeline *pipe;
|
||||
MatPipeline *mpipe;
|
||||
|
||||
// Atomic pipes
|
||||
|
||||
pipe = new ps2::ObjPipeline(PLATFORM_PS2);
|
||||
pipe->pluginID = ID_PDS;
|
||||
pipe->pluginData = 0x53f20080;
|
||||
ps2::registerPDSPipe(pipe);
|
||||
|
||||
pipe = new ps2::ObjPipeline(PLATFORM_PS2);
|
||||
pipe->pluginID = ID_PDS;
|
||||
pipe->pluginData = 0x53f20082;
|
||||
ps2::registerPDSPipe(pipe);
|
||||
|
||||
pipe = new ps2::ObjPipeline(PLATFORM_PS2);
|
||||
pipe->pluginID = ID_PDS;
|
||||
pipe->pluginData = 0x53f20084;
|
||||
ps2::registerPDSPipe(pipe);
|
||||
|
||||
pipe = new ps2::ObjPipeline(PLATFORM_PS2);
|
||||
pipe->pluginID = ID_PDS;
|
||||
pipe->pluginData = 0x53f20088;
|
||||
ps2::registerPDSPipe(pipe);
|
||||
|
||||
// Material pipes
|
||||
|
||||
mpipe = new MatPipeline(PLATFORM_PS2);
|
||||
mpipe->pluginID = ID_PDS;
|
||||
mpipe->pluginData = 0x53f20081;
|
||||
mpipe->attribs[0] = &saXYZADC;
|
||||
mpipe->attribs[1] = &saUV;
|
||||
mpipe->attribs[2] = &saRGBA;
|
||||
uint32 vertCount = MatPipeline::getVertCount(VU_Lights, 3, 3, 2);
|
||||
mpipe->setTriBufferSizes(3, vertCount);
|
||||
mpipe->vifOffset = mpipe->inputStride*vertCount;
|
||||
mpipe->instanceCB = saInstanceCB;
|
||||
mpipe->preUninstCB = saPreCB;
|
||||
mpipe->uninstanceCB = saUninstanceCB;
|
||||
ps2::registerPDSPipe(mpipe);
|
||||
|
||||
mpipe = new MatPipeline(PLATFORM_PS2);
|
||||
mpipe->pluginID = ID_PDS;
|
||||
mpipe->pluginData = 0x53f20083;
|
||||
mpipe->attribs[0] = &saXYZADC;
|
||||
mpipe->attribs[1] = &saUV;
|
||||
mpipe->attribs[2] = &saRGBA2;
|
||||
vertCount = MatPipeline::getVertCount(VU_Lights, 3, 3, 2);
|
||||
mpipe->setTriBufferSizes(3, vertCount);
|
||||
mpipe->vifOffset = mpipe->inputStride*vertCount;
|
||||
mpipe->instanceCB = saInstanceCB;
|
||||
mpipe->preUninstCB = saPreCB;
|
||||
mpipe->uninstanceCB = saUninstanceCB;
|
||||
ps2::registerPDSPipe(mpipe);
|
||||
|
||||
mpipe = new MatPipeline(PLATFORM_PS2);
|
||||
mpipe->pluginID = ID_PDS;
|
||||
mpipe->pluginData = 0x53f20085;
|
||||
mpipe->attribs[0] = &saXYZADC;
|
||||
mpipe->attribs[1] = &saUV;
|
||||
mpipe->attribs[3] = &saNormal;
|
||||
vertCount = MatPipeline::getVertCount(VU_Lights, 4, 3, 2);
|
||||
mpipe->setTriBufferSizes(4, vertCount);
|
||||
mpipe->vifOffset = mpipe->inputStride*vertCount;
|
||||
mpipe->instanceCB = saInstanceCB;
|
||||
mpipe->preUninstCB = saPreCB;
|
||||
mpipe->uninstanceCB = saUninstanceCB;
|
||||
ps2::registerPDSPipe(mpipe);
|
||||
|
||||
mpipe = new MatPipeline(PLATFORM_PS2);
|
||||
mpipe->pluginID = ID_PDS;
|
||||
mpipe->pluginData = 0x53f20087;
|
||||
mpipe->attribs[0] = &saXYZADC;
|
||||
mpipe->attribs[1] = &saUV;
|
||||
mpipe->attribs[3] = &saNormal;
|
||||
vertCount = MatPipeline::getVertCount(0x3BD, 4, 3, 3);
|
||||
mpipe->setTriBufferSizes(4, vertCount);
|
||||
mpipe->vifOffset = mpipe->inputStride*vertCount;
|
||||
mpipe->instanceCB = saInstanceCB;
|
||||
mpipe->preUninstCB = saPreCB;
|
||||
mpipe->uninstanceCB = saUninstanceCB;
|
||||
ps2::registerPDSPipe(mpipe);
|
||||
|
||||
mpipe = new MatPipeline(PLATFORM_PS2);
|
||||
mpipe->pluginID = ID_PDS;
|
||||
mpipe->pluginData = 0x53f20089;
|
||||
mpipe->attribs[0] = &saXYZADC;
|
||||
mpipe->attribs[1] = &saUV;
|
||||
mpipe->attribs[2] = &saNormal;
|
||||
mpipe->attribs[3] = &saWeights;
|
||||
// these values give vertCount = 0x33 :/
|
||||
// vertCount = MatPipeline::getVertCount(0x2D0, 4, 3, 2);
|
||||
vertCount = 0x30;
|
||||
mpipe->setTriBufferSizes(4, vertCount);
|
||||
mpipe->vifOffset = mpipe->inputStride*vertCount;
|
||||
mpipe->instanceCB = saInstanceCB;
|
||||
mpipe->preUninstCB = saPreCB;
|
||||
mpipe->uninstanceCB = saUninstanceCB;
|
||||
mpipe->postUninstCB = saPostCB;
|
||||
ps2::registerPDSPipe(mpipe);
|
||||
|
||||
mpipe = new MatPipeline(PLATFORM_PS2);
|
||||
mpipe->pluginID = ID_PDS;
|
||||
mpipe->pluginData = 0x53f2008b;
|
||||
mpipe->attribs[0] = &saXYZADC;
|
||||
mpipe->attribs[1] = &saUV2;
|
||||
mpipe->attribs[3] = &saNormal;
|
||||
vertCount = MatPipeline::getVertCount(0x3BD, 4, 3, 3);
|
||||
mpipe->setTriBufferSizes(4, vertCount);
|
||||
mpipe->vifOffset = mpipe->inputStride*vertCount;
|
||||
mpipe->instanceCB = saInstanceCB;
|
||||
mpipe->preUninstCB = saPreCB;
|
||||
mpipe->uninstanceCB = saUninstanceCB;
|
||||
ps2::registerPDSPipe(mpipe);
|
||||
|
||||
mpipe = new MatPipeline(PLATFORM_PS2);
|
||||
mpipe->pluginID = ID_PDS;
|
||||
mpipe->pluginData = 0x53f2008d;
|
||||
mpipe->attribs[0] = &saXYZADC;
|
||||
mpipe->attribs[1] = &saUV;
|
||||
mpipe->attribs[2] = &saRGBA;
|
||||
mpipe->attribs[3] = &saNormal;
|
||||
vertCount = MatPipeline::getVertCount(0x3BD, 4, 3, 3);
|
||||
mpipe->setTriBufferSizes(4, vertCount);
|
||||
mpipe->vifOffset = mpipe->inputStride*vertCount;
|
||||
mpipe->instanceCB = saInstanceCB;
|
||||
mpipe->preUninstCB = saPreCB;
|
||||
mpipe->uninstanceCB = saUninstanceCB;
|
||||
ps2::registerPDSPipe(mpipe);
|
||||
|
||||
mpipe = new MatPipeline(PLATFORM_PS2);
|
||||
mpipe->pluginID = ID_PDS;
|
||||
mpipe->pluginData = 0x53f2008f;
|
||||
mpipe->attribs[0] = &saXYZADC;
|
||||
mpipe->attribs[1] = &saUV;
|
||||
mpipe->attribs[2] = &saRGBA2;
|
||||
mpipe->attribs[3] = &saNormal;
|
||||
vertCount = MatPipeline::getVertCount(0x3BD, 4, 3, 3);
|
||||
mpipe->setTriBufferSizes(4, vertCount);
|
||||
mpipe->vifOffset = mpipe->inputStride*vertCount;
|
||||
mpipe->instanceCB = saInstanceCB;
|
||||
mpipe->preUninstCB = saPreCB;
|
||||
mpipe->uninstanceCB = saUninstanceCB;
|
||||
ps2::registerPDSPipe(mpipe);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -116,4 +116,8 @@ extern int32 collisionOffset;
|
||||
|
||||
void registerCollisionPlugin(void);
|
||||
|
||||
// PDS pipes
|
||||
|
||||
void registerPDSPipes(void);
|
||||
|
||||
}
|
||||
|
193
src/ps2.cpp
193
src/ps2.cpp
@ -58,6 +58,7 @@ readNativeData(Stream *stream, int32, void *object, int32, int32)
|
||||
assert(a % 0x10 == 0);
|
||||
#endif
|
||||
stream->read(instance->data, instance->dataSize);
|
||||
instance->material = geometry->meshHeader->mesh[i].material;
|
||||
// sizedebug(instance);
|
||||
}
|
||||
}
|
||||
@ -189,28 +190,6 @@ unfixDmaOffsets(InstanceData *inst)
|
||||
|
||||
// Pipeline
|
||||
|
||||
enum PS2Attribs {
|
||||
AT_V2_32 = 0x64000000,
|
||||
AT_V2_16 = 0x65000000,
|
||||
AT_V2_8 = 0x66000000,
|
||||
AT_V3_32 = 0x68000000,
|
||||
AT_V3_16 = 0x69000000,
|
||||
AT_V3_8 = 0x6A000000,
|
||||
AT_V4_32 = 0x6C000000,
|
||||
AT_V4_16 = 0x6D000000,
|
||||
AT_V4_8 = 0x6E000000,
|
||||
AT_UNSGN = 0x00004000,
|
||||
|
||||
AT_RW = 0x6
|
||||
};
|
||||
|
||||
enum PS2AttibTypes {
|
||||
AT_XYZ = 0,
|
||||
AT_UV = 1,
|
||||
AT_RGBA = 2,
|
||||
AT_NORMAL = 3
|
||||
};
|
||||
|
||||
PipeAttribute attribXYZ = {
|
||||
"XYZ",
|
||||
AT_V3_32
|
||||
@ -254,10 +233,10 @@ static uint32
|
||||
getBatchSize(MatPipeline *pipe, uint32 vertCount)
|
||||
{
|
||||
PipeAttribute *a;
|
||||
uint32 size = 1;
|
||||
uint32 size = 1; // ITOP &c. at the end
|
||||
for(uint i = 0; i < nelem(pipe->attribs); i++)
|
||||
if((a = pipe->attribs[i]) && (a->attrib & AT_RW) == 0){
|
||||
size++;
|
||||
size++; // UNPACK &c.
|
||||
size += QWC(vertCount*attribSize(a->attrib));
|
||||
}
|
||||
return size;
|
||||
@ -345,7 +324,7 @@ instanceNormal(uint32 *wp, Geometry *g, Mesh *m, uint32 idx, uint32 n)
|
||||
|
||||
MatPipeline::MatPipeline(uint32 platform)
|
||||
: rw::Pipeline(platform), instanceCB(NULL), uninstanceCB(NULL),
|
||||
allocateCB(NULL), finishCB(NULL)
|
||||
preUninstCB(NULL), postUninstCB(NULL)
|
||||
{
|
||||
for(int i = 0; i < 10; i++)
|
||||
this->attribs[i] = NULL;
|
||||
@ -436,7 +415,7 @@ getInstMeshInfo(MatPipeline *pipe, Geometry *g, Mesh *m)
|
||||
if(im.numBrokenAttribs == 0)
|
||||
im.size = 1 + im.batchSize*(im.numBatches-1) + im.lastBatchSize;
|
||||
else
|
||||
im.size = 2*im.numBatches +
|
||||
im.size = 2*im.numBrokenAttribs*im.numBatches +
|
||||
(1+im.batchSize)*(im.numBatches-1) + 1+im.lastBatchSize;
|
||||
|
||||
/* figure out size and addresses of broken out sections */
|
||||
@ -492,8 +471,11 @@ MatPipeline::instance(Geometry *g, InstanceData *inst, Mesh *m)
|
||||
*p++ = im.attribPos[i];
|
||||
*p++ = 0x01000100 |
|
||||
this->inputStride; // STCYCL
|
||||
// Round up nverts so UNPACK will fit exactly into the DMA packet
|
||||
// (can't pad with zeroes in broken out sections).
|
||||
// TODO: check for clash with vifOffset somewhere
|
||||
*p++ = (a->attrib&0xFF004000)
|
||||
| 0x8000 | nverts << 16 | i; // UNPACK
|
||||
| 0x8000 | (QWC(nverts*atsz)<<4)/atsz << 16 | i; // UNPACK
|
||||
|
||||
*p++ = 0x10000000;
|
||||
*p++ = 0x0;
|
||||
@ -572,10 +554,7 @@ MatPipeline::collectData(Geometry *g, InstanceData *inst, Mesh *m, uint8 *data[]
|
||||
}
|
||||
|
||||
uint8 *datap[nelem(this->attribs)];
|
||||
for(uint i = 0; i < nelem(this->attribs); i++){
|
||||
datap[i] = data[i];
|
||||
//printf("%p %x, %x\n", datap[i], datap[i]-datap[0], im.attribPos[i]*0x10);
|
||||
}
|
||||
memcpy(datap, data, sizeof(datap));
|
||||
|
||||
uint32 overlap = g->meshHeader->flags == 1 ? 2 : 0;
|
||||
uint32 *p = (uint32*)inst->data;
|
||||
@ -630,6 +609,7 @@ ObjPipeline::instance(Atomic *atomic)
|
||||
if(m == NULL)
|
||||
m = defaultMatPipe;
|
||||
m->instance(geo, instance, mesh);
|
||||
instance->material = mesh->material;
|
||||
}
|
||||
geo->geoflags |= Geometry::NATIVE;
|
||||
}
|
||||
@ -638,6 +618,9 @@ void
|
||||
printVertCounts(InstanceData *inst, int flag)
|
||||
{
|
||||
uint32 *d = (uint32*)inst->data;
|
||||
uint32 id = 0;
|
||||
if(inst->material->pipeline)
|
||||
id = inst->material->pipeline->pluginData;
|
||||
int stride;
|
||||
if(inst->arePointersFixed){
|
||||
d += 4;
|
||||
@ -646,15 +629,16 @@ printVertCounts(InstanceData *inst, int flag)
|
||||
d += 4 + 4*QWC(attribSize(d[3])*((d[3]>>16)&0xFF));
|
||||
}
|
||||
if(d[2] == 0)
|
||||
printf("ITOP %x %d (%d)\n", *d, stride, flag);
|
||||
printf("ITOP %x %d (%d) %x\n", *d, stride, flag, id);
|
||||
}else{
|
||||
while((*d&0x70000000) == 0x30000000){
|
||||
stride = d[2]&0xFF;
|
||||
printf("UNPACK %x %d (%d) %x\n", d[3], stride, flag, id);
|
||||
d += 8;
|
||||
}
|
||||
if((*d&0x70000000) == 0x10000000){
|
||||
d += (*d&0xFFFF)*4;
|
||||
printf("ITOP %x %d (%d)\n", *d, stride, flag);
|
||||
printf("ITOP %x %d (%d) %x\n", *d, stride, flag, id);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -667,6 +651,7 @@ ObjPipeline::uninstance(Atomic *atomic)
|
||||
return;
|
||||
assert(geo->instData != NULL);
|
||||
assert(geo->instData->platform == PLATFORM_PS2);
|
||||
InstanceDataHeader *header = (InstanceDataHeader*)geo->instData;
|
||||
// highest possible number of vertices
|
||||
geo->numVertices = geo->meshHeader->totalIndices;
|
||||
geo->geoflags &= ~Geometry::NATIVE;
|
||||
@ -675,18 +660,24 @@ ObjPipeline::uninstance(Atomic *atomic)
|
||||
uint32 *flags = new uint32[geo->numVertices];
|
||||
memset(flags, 0, 4*geo->numVertices);
|
||||
memset(geo->meshHeader->mesh[0].indices, 0, 2*geo->meshHeader->totalIndices);
|
||||
geo->numVertices = 0;
|
||||
InstanceDataHeader *header = (InstanceDataHeader*)geo->instData;
|
||||
for(uint32 i = 0; i < header->numMeshes; i++){
|
||||
Mesh *mesh = &geo->meshHeader->mesh[i];
|
||||
InstanceData *instance = &header->instanceMeshes[i];
|
||||
|
||||
MatPipeline *m;
|
||||
m = this->groupPipeline ?
|
||||
this->groupPipeline :
|
||||
(MatPipeline*)mesh->material->pipeline;
|
||||
if(m == NULL) m = defaultMatPipe;
|
||||
if(m->allocateCB) m->allocateCB(m, geo);
|
||||
if(m->preUninstCB) m->preUninstCB(m, geo);
|
||||
}
|
||||
geo->numVertices = 0;
|
||||
for(uint32 i = 0; i < header->numMeshes; i++){
|
||||
Mesh *mesh = &geo->meshHeader->mesh[i];
|
||||
InstanceData *instance = &header->instanceMeshes[i];
|
||||
MatPipeline *m;
|
||||
m = this->groupPipeline ?
|
||||
this->groupPipeline :
|
||||
(MatPipeline*)mesh->material->pipeline;
|
||||
if(m == NULL) m = defaultMatPipe;
|
||||
|
||||
uint8 *data[nelem(m->attribs)] = { NULL };
|
||||
uint8 *raw = m->collectData(geo, instance, mesh, data);
|
||||
@ -701,21 +692,28 @@ ObjPipeline::uninstance(Atomic *atomic)
|
||||
this->groupPipeline :
|
||||
(MatPipeline*)mesh->material->pipeline;
|
||||
if(m == NULL) m = defaultMatPipe;
|
||||
if(m->finishCB) m->finishCB(m, geo);
|
||||
if(m->postUninstCB) m->postUninstCB(m, geo);
|
||||
}
|
||||
|
||||
geo->generateTriangles();
|
||||
int8 *bits = NULL;
|
||||
if(adcOffset){
|
||||
ADCData *adc = PLUGINOFFSET(ADCData, geo, adcOffset);
|
||||
if(adc->adcFormatted)
|
||||
bits = adc->adcBits;
|
||||
}
|
||||
geo->generateTriangles(bits);
|
||||
delete[] flags;
|
||||
destroyNativeData(geo, 0, 0);
|
||||
geo->instData = NULL;
|
||||
|
||||
/* for(uint32 i = 0; i < header->numMeshes; i++){
|
||||
Mesh *mesh = &geometry->meshHeader->mesh[i];
|
||||
/*
|
||||
for(uint32 i = 0; i < header->numMeshes; i++){
|
||||
Mesh *mesh = &geo->meshHeader->mesh[i];
|
||||
InstanceData *instance = &header->instanceMeshes[i];
|
||||
// printf("numIndices: %d\n", mesh->numIndices);
|
||||
// printDMA(instance);
|
||||
printVertCounts(instance, geometry->meshHeader->flags);
|
||||
}*/
|
||||
printVertCounts(instance, geo->meshHeader->flags);
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
int32
|
||||
@ -832,8 +830,6 @@ makeDefaultPipeline(void)
|
||||
|
||||
static void skinInstanceCB(MatPipeline*, Geometry*, Mesh*, uint8**, int32);
|
||||
static void skinUninstanceCB(MatPipeline*, Geometry*, uint32*, Mesh*, uint8**);
|
||||
static void skinAllocateCB(MatPipeline*, Geometry*);
|
||||
static void skinFinishCB(MatPipeline*, Geometry*);
|
||||
|
||||
ObjPipeline*
|
||||
makeSkinPipeline(void)
|
||||
@ -851,8 +847,8 @@ makeSkinPipeline(void)
|
||||
pipe->vifOffset = pipe->inputStride*vertCount;
|
||||
pipe->instanceCB = skinInstanceCB;
|
||||
pipe->uninstanceCB = skinUninstanceCB;
|
||||
pipe->allocateCB = skinAllocateCB;
|
||||
pipe->finishCB = skinFinishCB;
|
||||
pipe->preUninstCB = skinPreCB;
|
||||
pipe->postUninstCB = skinPostCB;
|
||||
|
||||
ObjPipeline *opipe = new ObjPipeline(PLATFORM_PS2);
|
||||
opipe->pluginID = ID_SKIN;
|
||||
@ -1108,15 +1104,12 @@ skinUninstanceCB(MatPipeline *pipe, Geometry *geo, uint32 flags[], Mesh *mesh, u
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
skinAllocateCB(MatPipeline*, Geometry *geo)
|
||||
void
|
||||
skinPreCB(MatPipeline*, Geometry *geo)
|
||||
{
|
||||
Skin *skin = *PLUGINOFFSET(Skin*, geo, skinGlobals.offset);
|
||||
// If weight/index data is allocated don't do it again as this function
|
||||
// can be called multiple times per geometry.
|
||||
if(skin == NULL || skin->weights)
|
||||
if(skin == NULL)
|
||||
return;
|
||||
|
||||
uint8 *data = skin->data;
|
||||
float *invMats = skin->inverseMatrices;
|
||||
// meshHeader->totalIndices is highest possible number of vertices again
|
||||
@ -1125,8 +1118,8 @@ skinAllocateCB(MatPipeline*, Geometry *geo)
|
||||
delete[] data;
|
||||
}
|
||||
|
||||
static void
|
||||
skinFinishCB(MatPipeline*, Geometry *geo)
|
||||
void
|
||||
skinPostCB(MatPipeline*, Geometry *geo)
|
||||
{
|
||||
Skin *skin = *PLUGINOFFSET(Skin*, geo, skinGlobals.offset);
|
||||
skin->findNumWeights(geo->numVertices);
|
||||
@ -1135,6 +1128,8 @@ skinFinishCB(MatPipeline*, Geometry *geo)
|
||||
|
||||
// ADC
|
||||
|
||||
int32 adcOffset;
|
||||
|
||||
// TODO: look at PC SA rccam.dff bloodrb.dff, Xbox csbigbear.dff
|
||||
|
||||
static void
|
||||
@ -1217,6 +1212,17 @@ debugadc(Geometry *g, MeshHeader *mh, ADCData *adc)
|
||||
return n;
|
||||
}
|
||||
|
||||
void
|
||||
allocateADC(Geometry *geo)
|
||||
{
|
||||
ADCData *adc = PLUGINOFFSET(ADCData, geo, adcOffset);
|
||||
adc->adcFormatted = 1;
|
||||
adc->numBits = geo->meshHeader->totalIndices;
|
||||
int32 size = adc->numBits+3 & ~3;
|
||||
adc->adcBits = new int8[size];
|
||||
memset(adc->adcBits, 0, size);
|
||||
}
|
||||
|
||||
static void*
|
||||
createADC(void *object, int32 offset, int32)
|
||||
{
|
||||
@ -1311,8 +1317,8 @@ getSizeADC(void *object, int32 offset, int32)
|
||||
void
|
||||
registerADCPlugin(void)
|
||||
{
|
||||
Geometry::registerPlugin(sizeof(ADCData), ID_ADC,
|
||||
createADC, destroyADC, copyADC);
|
||||
adcOffset = Geometry::registerPlugin(sizeof(ADCData), ID_ADC,
|
||||
createADC, destroyADC, copyADC);
|
||||
Geometry::registerPluginStream(ID_ADC,
|
||||
readADC,
|
||||
writeADC,
|
||||
@ -1322,29 +1328,50 @@ registerADCPlugin(void)
|
||||
|
||||
// PDS plugin
|
||||
|
||||
struct PdsGlobals
|
||||
{
|
||||
Pipeline **pipes;
|
||||
int32 maxPipes;
|
||||
int32 numPipes;
|
||||
};
|
||||
PdsGlobals pdsGlobals;
|
||||
|
||||
Pipeline*
|
||||
getPDSPipe(uint32 data)
|
||||
{
|
||||
for(int32 i = 0; i < pdsGlobals.numPipes; i++)
|
||||
if(pdsGlobals.pipes[i]->pluginData == data)
|
||||
return pdsGlobals.pipes[i];
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void
|
||||
registerPDSPipe(Pipeline *pipe)
|
||||
{
|
||||
assert(pdsGlobals.numPipes < pdsGlobals.maxPipes);
|
||||
pdsGlobals.pipes[pdsGlobals.numPipes++] = pipe;
|
||||
}
|
||||
|
||||
static void
|
||||
atomicPDSRights(void *object, int32, int32, uint32 data)
|
||||
{
|
||||
Atomic *a = (Atomic*)object;
|
||||
// TODO: lookup pipeline by data
|
||||
a->pipeline = new ObjPipeline(PLATFORM_PS2);
|
||||
a->pipeline->pluginID = ID_PDS;
|
||||
a->pipeline->pluginData = data;
|
||||
a->pipeline = (ObjPipeline*)getPDSPipe(data);
|
||||
}
|
||||
|
||||
static void
|
||||
materialPDSRights(void *object, int32, int32, uint32 data)
|
||||
{
|
||||
Material *m = (Material*)object;
|
||||
// TODO: lookup pipeline by data
|
||||
m->pipeline = new Pipeline(PLATFORM_PS2);
|
||||
m->pipeline->pluginID = ID_PDS;
|
||||
m->pipeline->pluginData = data;
|
||||
m->pipeline = (ObjPipeline*)getPDSPipe(data);
|
||||
}
|
||||
|
||||
void
|
||||
registerPDSPlugin(void)
|
||||
registerPDSPlugin(int32 n)
|
||||
{
|
||||
pdsGlobals.maxPipes = n;
|
||||
pdsGlobals.numPipes = 0;
|
||||
pdsGlobals.pipes = new Pipeline*[n];
|
||||
Atomic::registerPlugin(0, ID_PDS, NULL, NULL, NULL);
|
||||
Atomic::setStreamRightsCallback(ID_PDS, atomicPDSRights);
|
||||
|
||||
@ -1381,36 +1408,6 @@ printDMA(InstanceData *inst)
|
||||
}
|
||||
}
|
||||
|
||||
/* Function to specifically walk geometry chains */
|
||||
void
|
||||
walkDMA(InstanceData *inst, void (*f)(uint32 *data, int32 size))
|
||||
{
|
||||
if(inst->arePointersFixed == 2)
|
||||
return;
|
||||
uint32 *base = (uint32*)inst->data;
|
||||
uint32 *tag = (uint32*)inst->data;
|
||||
for(;;){
|
||||
switch(tag[0]&0x70000000){
|
||||
// DMAcnt
|
||||
case 0x10000000:
|
||||
f(tag+2, 2+(tag[0]&0xFFFF)*4);
|
||||
tag += (1+(tag[0]&0xFFFF))*4;
|
||||
break;
|
||||
|
||||
// DMAref
|
||||
case 0x30000000:
|
||||
f(base + tag[1]*4, (tag[0]&0xFFFF)*4);
|
||||
tag += 4;
|
||||
break;
|
||||
|
||||
// DMAret
|
||||
case 0x60000000:
|
||||
f(tag+2, 2+(tag[0]&0xFFFF)*4);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
sizedebug(InstanceData *inst)
|
||||
{
|
||||
|
@ -333,7 +333,7 @@ struct Geometry : PluginBase<Geometry>, Object
|
||||
uint32 streamGetSize(void);
|
||||
void addMorphTargets(int32 n);
|
||||
void allocateData(void);
|
||||
void generateTriangles(void);
|
||||
void generateTriangles(int8 *adc = NULL);
|
||||
|
||||
enum Flags
|
||||
{
|
||||
|
41
src/rwps2.h
41
src/rwps2.h
@ -23,6 +23,28 @@ enum {
|
||||
VU_Lights = 0x3d0
|
||||
};
|
||||
|
||||
enum PS2Attribs {
|
||||
AT_V2_32 = 0x64000000,
|
||||
AT_V2_16 = 0x65000000,
|
||||
AT_V2_8 = 0x66000000,
|
||||
AT_V3_32 = 0x68000000,
|
||||
AT_V3_16 = 0x69000000,
|
||||
AT_V3_8 = 0x6A000000,
|
||||
AT_V4_32 = 0x6C000000,
|
||||
AT_V4_16 = 0x6D000000,
|
||||
AT_V4_8 = 0x6E000000,
|
||||
AT_UNSGN = 0x00004000,
|
||||
|
||||
AT_RW = 0x6
|
||||
};
|
||||
|
||||
enum PS2AttibTypes {
|
||||
AT_XYZ = 0,
|
||||
AT_UV = 1,
|
||||
AT_RGBA = 2,
|
||||
AT_NORMAL = 3
|
||||
};
|
||||
|
||||
void *destroyNativeData(void *object, int32, int32);
|
||||
void readNativeData(Stream *stream, int32 len, void *object, int32, int32);
|
||||
void writeNativeData(Stream *stream, int32 len, void *object, int32, int32);
|
||||
@ -30,7 +52,6 @@ int32 getSizeNativeData(void *object, int32, int32);
|
||||
void registerNativeDataPlugin(void);
|
||||
|
||||
void printDMA(InstanceData *inst);
|
||||
void walkDMA(InstanceData *inst, void (*f)(uint32 *data, int32 size));
|
||||
void sizedebug(InstanceData *inst);
|
||||
|
||||
// only RW_PS2
|
||||
@ -47,8 +68,8 @@ public:
|
||||
PipeAttribute *attribs[10];
|
||||
void (*instanceCB)(MatPipeline*, Geometry*, Mesh*, uint8**, int32);
|
||||
void (*uninstanceCB)(MatPipeline*, Geometry*, uint32*, Mesh*, uint8**);
|
||||
void (*allocateCB)(MatPipeline*, Geometry*);
|
||||
void (*finishCB)(MatPipeline*, Geometry*);
|
||||
void (*preUninstCB)(MatPipeline*, Geometry*);
|
||||
void (*postUninstCB)(MatPipeline*, Geometry*);
|
||||
|
||||
static uint32 getVertCount(uint32 top, uint32 inAttribs,
|
||||
uint32 outAttribs, uint32 outBufs) {
|
||||
@ -72,6 +93,8 @@ public:
|
||||
virtual void uninstance(Atomic *atomic);
|
||||
};
|
||||
|
||||
void insertVertex(Geometry *geo, int32 i, uint32 mask, float *v, float *t0, float *t1, uint8 *c, float *n);
|
||||
|
||||
extern ObjPipeline *defaultObjPipe;
|
||||
extern MatPipeline *defaultMatPipe;
|
||||
|
||||
@ -86,6 +109,9 @@ void readNativeSkin(Stream *stream, int32, void *object, int32 offset);
|
||||
void writeNativeSkin(Stream *stream, int32 len, void *object, int32 offset);
|
||||
int32 getSizeNativeSkin(void *object, int32 offset);
|
||||
|
||||
void skinPreCB(MatPipeline*, Geometry*);
|
||||
void skinPostCB(MatPipeline*, Geometry*);
|
||||
|
||||
// ADC plugin
|
||||
|
||||
// Each element in adcBits corresponds to an index in Mesh->indices,
|
||||
@ -101,9 +127,11 @@ struct ADCData
|
||||
int8 *adcBits;
|
||||
int32 numBits;
|
||||
};
|
||||
|
||||
extern int32 adcOffset;
|
||||
void registerADCPlugin(void);
|
||||
|
||||
void allocateADC(Geometry *geo);
|
||||
|
||||
// PDS plugin
|
||||
|
||||
// IDs used by SA
|
||||
@ -117,8 +145,9 @@ void registerADCPlugin(void);
|
||||
// 4640 53f20084 53f2008b // vehicles
|
||||
// 418 53f20088 53f20089 // peds
|
||||
|
||||
|
||||
void registerPDSPlugin(void);
|
||||
Pipeline *getPDSPipe(uint32 data);
|
||||
void registerPDSPipe(Pipeline *pipe);
|
||||
void registerPDSPlugin(int32 n);
|
||||
|
||||
// Native Texture and Raster
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user