/*
Copyright 2022 flyinghead
This file is part of Flycast.
Flycast 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 2 of the License, or
(at your option) any later version.
Flycast 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 Flycast. If not, see .
*/
/*
* VideoLogic custom transformation & lighting (T&L) chip (codenamed: ELAN)
* 32 MB RAM
* Clock: 100 MHz
* 16 light sources per polygon
* ambient, parallel, point or spot (Fog lights and alpha lights also exist)
* Perspective conversion
* Near, far and side clipping, offscreen and backface culling
* bump mapping, environmental mapping
* dynamic & static model processing
* model cache system
*
* Each PVR2 chip renders half the screen (rectangular, stripes, and checker board options)
* so textures have to be duplicated in each vram
*
* Area 0:
* 005f6800 - 005f7cff asic A regs
* 005f8000 - 005f9fff CLXA regs
* 025f6800 - 025f7cff asic B regs
* 025f8000 - 025f9fff CLXB regs
*
* Area 1:
* 05000000 - 06ffffff CLXA vram
* 07000000 - 08ffffff CLXB vram
*
* Area 2:
* 085f6800 - 085f7cff write both asic regs
* 085f8000 - 085f9fff write both PVR regs
* 08800000 - 088000ff? elan regs
* 09000000 - ? elan command buffer
* 0A000000 - 0bfffffff elan RAM
*/
#include "elan.h"
#include "hw/mem/_vmem.h"
#include "pvr_mem.h"
#include "ta.h"
#include "ta_ctx.h"
#include "hw/holly/holly_intc.h"
#include "hw/holly/sb.h"
#include "hw/pvr/Renderer_if.h"
#include "hw/sh4/sh4_sched.h"
#include "emulator.h"
#include "serialize.h"
#include "elan_struct.h"
#include
#include
#include
namespace elan {
static _vmem_handler elanRegHandler;
static _vmem_handler elanCmdHandler;
static _vmem_handler elanRamHandler;
static u8 *elanRAM;
constexpr u32 ELAN_RAM_SIZE = 32 * 1024 * 1024;
static u32 reg10;
static u32 reg74;
static u32 reg30 = 0x31;
static u32 elanCmd[32 / 4];
template
T DYNACALL read_elanreg(u32 paddr)
{
verify(sizeof(T) == 4);
u32 addr = paddr & 0x01ffffff;
switch (addr >> 16)
{
case 0x5F:
if (addr >= 0x005F6800 && addr <= 0x005F7CFF)
{
// 5F6908: Tests for errors 4, 8, 10, 2 and 1 (render isp buf ovf, render hazard, ISP param ovf, ob list ptr ovf, ta ill param)
// 5f6900: then int 4 and 40 (EoR TSP, EoT YUV)
return (T)sb_ReadMem(paddr, sizeof(T));
}
else if (addr >= 0x005F8000 && addr <= 0x005F9FFF)
{
if (sizeof(T) != 4)
// House of the Dead 2
return 0;
return (T)pvr_ReadReg(paddr);
}
else
{
INFO_LOG(MEMORY, "Read from area2 not implemented [Unassigned], addr=%x", addr);
return 0;
}
default:
// if ((addr & 0xFF) != 0x74)
DEBUG_LOG(PVR, "ELAN read(%d) %08x [pc %08x]", (u32)sizeof(T), addr, p_sh4rcb->cntx.pc);
switch (addr & 0xFF)
{
case 0: // magic number
return (T)0xe1ad0000;
case 4: // revision
return 0x1; // 1 or x10
// 10 breaks vstriker?
case 0xc:
// command queue size
// loops until < 2 (v1) or 3 (v10)
return 1;
case 0x10: // sh4 if control?
// b0 broadcast on cs1
// b1 elan channel 2
// b2 enable pvr #2
// rewritten by bios as reg10 & ~1
return reg10;
case 0x14: // SDRAM refresh (never read?)
return (T)0x2029; //default 0x1429
case 0x1c: // SDRAM CFG
return (T)0x87320961;
case 0x30: // Macro tiler config
// 0 0 l l l l l l t t t t 0 0 r r r r r r b b b b 0 0 V H 0 0 0 T
// lllll: left tile
// tttt: top tile
// rrrrrr: right tile
// bbbb: bottom tile
// V: tile vertically
// H: tile horizontally
// T: tiler enabled
return reg30;
case 0x74:
// b0 dma completed
// b1 cmd completed
// b2-b3 geometry timeouts
// b4-b6 errors?
return reg74;
case 0x78: // IRQ MASK
// 6 bits?
return 0;
default:
return (T)0;
}
}
}
template
void DYNACALL write_elanreg(u32 paddr, T data)
{
verify(sizeof(T) == 4);
u32 addr = paddr & 0x01ffffff;
switch (addr >> 16)
{
case 0x5F:
if (addr>= 0x005F6800 && addr <= 0x005F7CFF)
sb_WriteMem(paddr, data, sizeof(T));
else if (addr >= 0x005F8000 && addr <= 0x005F9FFF)
{
if (addr == 0x5F8040 && data == 0xFF00FF)
{
ERROR_LOG(PVR, "ELAN SCREWED pr %x pc %x", p_sh4rcb->cntx.pr, p_sh4rcb->cntx.pc);
throw FlycastException("Boot aborted");
}
else if ((addr & 0x1fff) == SOFTRESET_addr && data == 0)
reg74 &= 3;
else if ((addr & 0x1fff) == STARTRENDER_addr)
reg74 &= 3;
//if ((paddr & 0x1c000000) == 0x08000000 && (addr & 0x1fff) == SOFTRESET_addr && data == 0)
// reg74 |= 2;
pvr_WriteReg(paddr, data);
}
else
INFO_LOG(COMMON, "Write to area2 not implemented [Unassigned], addr=%x,data=%x,size=%d", addr, data, (u32)sizeof(T));
break;
default:
// if ((addr & 0xFF) != 0x74)
DEBUG_LOG(PVR, "ELAN write(%d) %08x = %x", (u32)sizeof(T), addr, data);
switch (addr & 0xFF)
{
case 0x0:
// 0 multiple times (_kmtlifAbortDisplayListProcessing)
break;
// 0x4: _kmtlifAbortDisplayListProcessing: 0
case 0x8: // write-only. reset ?
// 1 then 0
// bios: 5
// _kmtlifAbortDisplayListProcessing: 5 then 0
// _kmtlifHandleDMATimeout: 1, 0, 4, 0...
if (data == 0)
reg74 = 0;
break;
case 0xc:
// 0
break;
case 0x10: // sh4 if control?
reg10 = data;
break;;
case 0x14: // SDRAM refresh
// x2029
break;
case 0x1c: // SDRAM CFG
break;
case 0x30:
reg30 = data;
break;
case 0x74: // IRQ STAT
reg74 &= ~data;
break;
// _kmtlifSetupElanInts:
// 78 = 3f
// 7C = 0
// 80 = 17
// 84 = 2b
// 88 = 0
case 0xd0: // _kmtlifSetCullingRegister
// 6
break;;
default:
break;
}
}
}
template
T DYNACALL read_elancmd(u32 addr)
{
DEBUG_LOG(PVR, "ELAN cmd READ! (%d) %08x", (u32)sizeof(T), addr);
return 0;
}
static GMP *curGmp;
static glm::mat4x4 curMatrix;
static glm::mat4x4 lightMatrix;
static glm::mat4 projectionMatrix;
static LightModel *curLightModel;
static ElanBase *curLights[MAX_LIGHTS];
static float near = 0.001f;
static float far = 100000.f;
struct State
{
static constexpr u32 Null = 0xffffffff;
int listType = -1;
u32 gmp = Null;
u32 matrix = Null;
u32 projMatrix = Null;
int userClip = 0;
u32 lightModel = Null;
u32 lights[MAX_LIGHTS] = {
Null, Null, Null, Null, Null, Null, Null, Null,
Null, Null, Null, Null, Null, Null, Null, Null
};
void reset()
{
listType = -1;
gmp = Null;
matrix = Null;
projMatrix = Null;
userClip = 0;
lightModel = Null;
for (auto& light : lights)
light = Null;
update();
}
void setMatrix(void *p)
{
matrix = elanRamAddress(p);
updateMatrix();
}
void updateMatrix()
{
if (matrix == Null)
return;
Matrix *mat = (Matrix *)&elanRAM[matrix];
DEBUG_LOG(PVR, "Matrix %f %f %f %f\n %f %f %f %f\n %f %f %f %f\nLight: %f %f %f\n %f %f %f",
-mat->tm00, -mat->tm01, -mat->tm02, -mat->mat03,
mat->tm10, mat->tm11, mat->tm12, mat->mat13,
mat->tm20, mat->tm21, mat->tm22, -mat->mat23,
mat->lm00, mat->lm01, mat->lm02,
mat->lm10, mat->lm11, mat->lm12);
// DEBUG_LOG(PVR, "Matrix proj4 %f %f %f %f %f",
// mat->proj4, mat->proj5, mat->mproj6, mat->proj7, mat->proj8);
curMatrix = glm::mat4x4{
-mat->tm00, mat->tm10, mat->tm20, 0,
-mat->tm01, mat->tm11, mat->tm21, 0,
-mat->tm02, mat->tm12, mat->tm22, 0,
-mat->mat03, mat->mat13, -mat->mat23, 1
};
lightMatrix = glm::mat4x4{
-mat->lm00, mat->lm10, mat->tm20, 0,
-mat->lm01, mat->lm11, mat->tm21, 0,
-mat->lm02, mat->lm12, mat->tm22, 0,
-mat->mat03, mat->mat13, -mat->mat23, 1
};
near = mat->proj4;
far = mat->proj5;
}
void setProjectionMatrix(void *p)
{
projMatrix = elanRamAddress(p);
updateProjectionMatrix();
}
void updateProjectionMatrix()
{
if (projMatrix == Null)
return;
ProjMatrix *pm = (ProjMatrix *)&elanRAM[projMatrix];
DEBUG_LOG(PVR, "Proj matrix x: %f %f y: %f %f", pm->fx, pm->tx, pm->fy, pm->ty);
projectionMatrix = glm::mat4(
-pm->fx, 0, 0, 0,
0, pm->fy, 0, 0,
-pm->tx, -pm->ty, -1, -1,
0, 0, 0, 0
);
}
void setGMP(void *p)
{
gmp = elanRamAddress(p);
updateGMP();
}
void updateGMP() {
if (gmp == Null)
curGmp = nullptr;
else
{
curGmp = (GMP *)&elanRAM[gmp];
DEBUG_LOG(PVR, "GMP paramSelect %x clip %d", curGmp->paramSelect.full, curGmp->pcw.userClip);
}
}
void setLightModel(void *p)
{
lightModel = elanRamAddress(p);
updateLightModel();
}
void updateLightModel() {
if (lightModel == Null)
curLightModel = nullptr;
else
{
curLightModel = (LightModel *)&elanRAM[lightModel];
DEBUG_LOG(PVR, "Light model mask: diffuse %04x specular %04x, ambient base %08x offset %08x", curLightModel->diffuseMask0, curLightModel->specularMask0,
curLightModel->ambientBase0, curLightModel->ambientOffset0);
}
}
void setLight(int lightId, void *p)
{
lights[lightId] = elanRamAddress(p);
updateLight(lightId);
}
void updateLight(int lightId)
{
if (lights[lightId] == Null)
{
elan::curLights[lightId] = nullptr;
return;
}
Instance *instance = (Instance *)&elanRAM[lights[lightId]];
if (instance->pcw.parallelLight)
{
ParallelLight *light = (ParallelLight *)instance;
DEBUG_LOG(PVR, " Parallel light %d: col %d %d %d dir %d %d %d", light->lightId, light->red, light->green, light->blue,
light->dirX, light->dirY, light->dirZ);
}
else
{
PointLight *light = (PointLight *)instance;
DEBUG_LOG(PVR, " Point light %d: dattenmode %d col %d %d %d dir %d %d %d pos %f %f %f routing %d dist %f %f angle %f %f",
light->lightId, light->dattenmode,
light->red, light->green, light->blue,
light->dirX, light->dirY, light->dirZ,
light->posX, light->posY, light->posZ,
light->routing, light->attnMinDistance(), light->attnMaxDistance(),
light->attnMinAngle(), light->attnMaxAngle());
}
elan::curLights[lightId] = instance;
}
void update()
{
updateMatrix();
updateProjectionMatrix();
updateGMP();
updateLightModel();
for (u32 i = 0; i < MAX_LIGHTS; i++)
updateLight(i);
}
static u32 elanRamAddress(void *p)
{
if ((u8 *)p < elanRAM || (u8 *)p >= elanRAM + ELAN_RAM_SIZE)
return Null;
else
return (u32)((u8 *)p - elanRAM);
}
};
static State state;
template
static void setCoords(T& vtx, float x, float y, float z)
{
glm::vec4 v(x, y, z, 1);
v = projectionMatrix * curMatrix * v;
v.x /= v.w;
v.y /= v.w;
vtx.xyz[0] = v.x;
vtx.xyz[1] = v.y;
vtx.xyz[2] = 1 / v.w;
}
template
static void setUV(const Ts& vs, Td& vd)
{
vd.u = vs.uv.u;
vd.v = vs.uv.v;
}
glm::vec4 unpackColor(u32 color)
{
return glm::vec4((float)((color >> 16) & 0xff) / 255.f,
(float)((color >> 8) & 0xff) / 255.f,
(float)(color & 0xff) / 255.f,
(float)(color >> 24) / 255.f);
}
glm::vec4 unpackColor(u8 red, u8 green, u8 blue, u8 alpha = 0)
{
return glm::vec4((float)red / 255.f, (float)green / 255.f, (float)blue / 255.f, (float)alpha / 255.f);
}
u32 packColor(const glm::vec4& color)
{
return (int)(std::max(0.f, std::min(1.f, color.a)) * 255.f) << 24
| (int)(std::max(0.f, std::min(1.f, color.r)) * 255.f) << 16
| (int)(std::max(0.f, std::min(1.f, color.g)) * 255.f) << 8
| (int)(std::max(0.f, std::min(1.f, color.b)) * 255.f);
}
static void computeColors(glm::vec4& baseCol, glm::vec4& offsetCol, const glm::vec4& pos, const glm::vec4& normal)
{
if (curLightModel == nullptr)
return;
glm::vec4 ambient{};
glm::vec4 diffuse{};
glm::vec4 specular{};
float diffuseAlphaDiff = 0;
float specularAlphaDiff = 0;
for (u32 lightId = 0; lightId < MAX_LIGHTS; lightId++)
{
const ElanBase *base = curLights[lightId];
if (base == nullptr)
continue;
if (!curLightModel->isDiffuse(lightId) && !curLightModel->isSpecular(lightId))
continue;
glm::vec4 lightDir; // direction to the light
int routing;
int dmode;
int smode = N2_LMETHOD_SINGLE_SIDED;
glm::vec4 lightColor;
if (base->pcw.parallelLight)
{
ParallelLight *light = (ParallelLight *)base;
lightDir = glm::normalize(glm::vec4((int8_t)light->dirX, -(int8_t)light->dirY, -(int8_t)light->dirZ, 0));
lightColor = unpackColor(light->red, light->green, light->blue);
routing = light->routing;
dmode = light->dmode;
}
else
{
PointLight *light = (PointLight *)base;
glm::vec4 lightPos(light->posX, light->posY, light->posZ, 1);
lightDir = glm::normalize(lightPos - pos);
lightColor = unpackColor(light->red, light->green, light->blue);
routing = light->routing;
if (light->isAttnDist())
{
float distance = glm::length(lightPos - pos);
lightColor *= light->attnDist(distance);
}
if (light->isAttnAngle())
{
glm::vec4 spotDir = glm::normalize(glm::vec4((int8_t)light->dirX, (int8_t)light->dirY, (int8_t)light->dirZ, 0));
float cosAngle = glm::max(0.f, glm::dot(-lightDir, spotDir));
lightColor *= light->attnAngle(cosAngle);
}
dmode = light->dmode;
smode = light->smode;
}
verify(dmode == N2_LMETHOD_SINGLE_SIDED || dmode == N2_LMETHOD_SPECIAL_EFFECT || dmode == N2_LMETHOD_DOUBLE_SIDED);
verify(smode == N2_LMETHOD_SINGLE_SIDED || smode == N2_LMETHOD_SPECIAL_EFFECT || smode == N2_LMETHOD_DOUBLE_SIDED);
if (!(routing == N2_LFUNC_BASEDIFF_BASESPEC_ADD || routing == N2_LFUNC_BASEDIFF_OFFSSPEC_ADD
|| routing == N2_LFUNC_OFFSDIFF_BASESPEC_ADD || routing == N2_LFUNC_OFFSDIFF_OFFSSPEC_ADD
|| routing == N2_LFUNC_ALPHADIFF_SUB))
WARN_LOG(PVR, "routing = %d dmode %d smode %d lightCol %f %f %f %f", routing ,dmode, smode, lightColor.r, lightColor.g, lightColor.b, lightColor.a);
if (curLightModel->isDiffuse(lightId) && curGmp->paramSelect.d0)
{
float factor;
switch (dmode)
{
case N2_LMETHOD_SINGLE_SIDED:
factor = glm::max(glm::dot(normal, lightDir), 0.f);
break;
case N2_LMETHOD_DOUBLE_SIDED:
factor = glm::abs(glm::dot(normal, lightDir));
break;
case N2_LMETHOD_SPECIAL_EFFECT:
default:
factor = 1;
break;
}
if (routing == N2_LFUNC_ALPHADIFF_SUB)
// FIXME probably need to substract from baseCol.a/OffsetCol.a instead? still not working...
//diffuse.a = std::max(0.f, diffuse.a - lightColor.r * factor);
diffuseAlphaDiff -= lightColor.r * factor;
else if (routing == N2_LFUNC_BASEDIFF_BASESPEC_ADD || routing == N2_LFUNC_BASEDIFF_OFFSSPEC_ADD)
diffuse += lightColor * factor;
if (routing == N2_LFUNC_OFFSDIFF_BASESPEC_ADD || routing == N2_LFUNC_OFFSDIFF_OFFSSPEC_ADD)
specular += lightColor * factor;
}
if (curLightModel->isSpecular(lightId) && curGmp->paramSelect.s0)
{
glm::vec4 reflectDir = glm::reflect(-lightDir, normal);
float factor;
switch (smode)
{
case N2_LMETHOD_SINGLE_SIDED:
factor = glm::pow(glm::max(glm::dot(glm::normalize(-pos), reflectDir), 0.f), curGmp->gloss.getCoef0());
break;
case N2_LMETHOD_DOUBLE_SIDED:
factor = glm::pow(glm::abs(glm::dot(glm::normalize(-pos), reflectDir)), curGmp->gloss.getCoef0());
break;
case N2_LMETHOD_SPECIAL_EFFECT:
default:
factor = 1;
break;
}
if (routing == N2_LFUNC_ALPHADIFF_SUB)
//specular.a = std::max(0.f, specular.a - lightColor.r * factor);
specularAlphaDiff -= lightColor.r * factor;
else if (routing == N2_LFUNC_OFFSDIFF_OFFSSPEC_ADD || routing == N2_LFUNC_BASEDIFF_OFFSSPEC_ADD)
specular += lightColor * factor;
if (routing == N2_LFUNC_BASEDIFF_BASESPEC_ADD || routing == N2_LFUNC_OFFSDIFF_BASESPEC_ADD)
diffuse += lightColor * factor;
}
}
if (curGmp->paramSelect.a0) // ambient0 TODO check
{
if (curLightModel->useAmbientBase0)
diffuse += unpackColor(curLightModel->ambientBase0);
if (curLightModel->useAmbientOffset0)
specular += unpackColor(curLightModel->ambientOffset0);
}
baseCol *= diffuse;
offsetCol *= specular;
if (curGmp->paramSelect.a0)
{
if (!curLightModel->useAmbientBase0)
baseCol += unpackColor(curLightModel->ambientBase0);
if (!curLightModel->useAmbientOffset0)
offsetCol += unpackColor(curLightModel->ambientOffset0);
}
baseCol.a = std::max(0.f, baseCol.a + diffuseAlphaDiff);
offsetCol.a = std::max(0.f, offsetCol.a + specularAlphaDiff);
if (curLightModel->useBaseOver)
{
glm::vec4 overflow = glm::max(glm::vec4(0), baseCol - glm::vec4(1));
offsetCol += overflow;
}
}
template
glm::vec4 getNormal(const T& vtx)
{
return glm::normalize(lightMatrix * glm::vec4((int8_t)vtx.header.nx / 127.f, (int8_t)vtx.header.ny / 127.f, (int8_t)vtx.header.nz / 127.f, 0));
}
template<>
glm::vec4 getNormal(const N2_VERTEX_VNU& vtx)
{
return glm::normalize(lightMatrix * glm::vec4(vtx.normal.nx, vtx.normal.ny, vtx.normal.nz, 0));
}
template
static void convertVertex(const T& vs, TA_VertexParam& vd);
template<>
void convertVertex(const Vertex& vs, TA_VertexParam& vd)
{
setCoords(vd.vtx0, vs.x, vs.y, vs.z);
glm::vec4 baseCol;
glm::vec4 offsetCol;
if (curGmp != nullptr)
{
baseCol = unpackColor(curGmp->diffuse0);
offsetCol = unpackColor(curGmp->specular0);
computeColors(baseCol, offsetCol, curMatrix * glm::vec4(vs.x, vs.y, vs.z, 1), getNormal(vs));
}
else
{
baseCol = glm::vec4(0);
offsetCol = glm::vec4(0);
}
vd.vtx0.BaseCol = packColor(baseCol + offsetCol);
}
template<>
void convertVertex(const N2_VERTEX_VR& vs, TA_VertexParam& vd)
{
setCoords(vd.vtx0, vs.x, vs.y, vs.z);
glm::vec4 baseCol = unpackColor(vs.rgb.argb0);
glm::vec4 offsetCol = baseCol;
if (curGmp != nullptr)
{
// Not sure about offset but vf4 needs base addition
baseCol += unpackColor(curGmp->diffuse0);
offsetCol += unpackColor(curGmp->specular0);
computeColors(baseCol, offsetCol, curMatrix * glm::vec4(vs.x, vs.y, vs.z, 1), getNormal(vs));
}
vd.vtx0.BaseCol = packColor(baseCol + offsetCol);
}
template<>
void convertVertex(const N2_VERTEX_VU& vs, TA_VertexParam& vd)
{
setCoords(vd.vtx3, vs.x, vs.y, vs.z);
setUV(vs, vd.vtx3);
glm::vec4 baseCol;
glm::vec4 offsetCol;
if (curGmp != nullptr)
{
baseCol = unpackColor(curGmp->diffuse0);
offsetCol = unpackColor(curGmp->specular0);
computeColors(baseCol, offsetCol, curMatrix * glm::vec4(vs.x, vs.y, vs.z, 1), getNormal(vs));
}
else
{
baseCol = glm::vec4(0);
offsetCol = glm::vec4(0);
}
vd.vtx3.BaseCol = packColor(baseCol);
vd.vtx3.OffsCol = packColor(offsetCol);
}
template<>
void convertVertex(const N2_VERTEX_VUR& vs, TA_VertexParam& vd)
{
setCoords(vd.vtx3, vs.x, vs.y, vs.z);
setUV(vs, vd.vtx3);
glm::vec4 baseCol = unpackColor(vs.rgb.argb0);
glm::vec4 offsetCol = baseCol;
if (curGmp != nullptr)
{
// Not sure about offset but vf4 needs base addition
baseCol += unpackColor(curGmp->diffuse0);
offsetCol += unpackColor(curGmp->specular0);
computeColors(baseCol, offsetCol, curMatrix * glm::vec4(vs.x, vs.y, vs.z, 1), getNormal(vs));
}
vd.vtx3.BaseCol = packColor(baseCol);
vd.vtx3.OffsCol = packColor(offsetCol);
}
template<>
void convertVertex(const N2_VERTEX_VUB& vs, TA_VertexParam& vd)
{
// TODO
setCoords(vd.vtx3, vs.x, vs.y, vs.z);
setUV(vs, vd.vtx3);
glm::vec4 baseCol;
glm::vec4 offsetCol;
if (curGmp != nullptr)
{
baseCol = unpackColor(curGmp->diffuse0);
offsetCol = unpackColor(curGmp->specular0);
computeColors(baseCol, offsetCol, curMatrix * glm::vec4(vs.x, vs.y, vs.z, 1), getNormal(vs));
}
else
{
baseCol = glm::vec4(0);
offsetCol = glm::vec4(0);
}
vd.vtx3.BaseCol = packColor(baseCol);
vd.vtx3.OffsCol = packColor(offsetCol);
}
template
static void sendVertices(const ICHList *list, const T* vtx)
{
alignas(32) TA_VertexParam taVtx;
taVtx.pcw.ParaType = 7;
verify(list->vertexSize() > 0);
alignas(32) TA_VertexParam fanCenterVtx{};
alignas(32) TA_VertexParam fanLastVtx{};
for (u32 i = 0; i < list->vtxCount; i++)
{
taVtx.pcw.EndOfStrip = vtx->header.endOfStrip;
convertVertex(*vtx, taVtx);
if (fanCenterVtx.pcw.ParaType == 0)
{
// Center vertex if triangle fan
//verify(vtx->header.isFirstOrSecond()); This fails for some strips: strip=1 fan=0 (soul surfer)
memcpy(&fanCenterVtx, &taVtx, sizeof(SQBuffer));
}
else if (vtx->header.isThird())
{
// End of strip if triangle fan
if (i + 1 < list->vtxCount && vtx[1].header.isFan())
taVtx.pcw.EndOfStrip = 1;
}
else if (vtx->header.isFan())
{
// Triangle fan
ta_vtx_data32((SQBuffer *)&fanCenterVtx);
ta_vtx_data32((SQBuffer *)&fanLastVtx);
taVtx.pcw.EndOfStrip = 1;
}
ta_vtx_data32((SQBuffer *)&taVtx);
memcpy(&fanLastVtx, &taVtx, sizeof(SQBuffer));
fanLastVtx.pcw.EndOfStrip = 0;
if (vtx->header.endOfStrip)
fanCenterVtx.pcw.ParaType = 0;
vtx++;
}
}
template
static void sendMVVertices(const ICHList *list, const T* vtx)
{
SQBuffer sqb[2]{};
TA_VertexParam& taVtx = *(TA_VertexParam *)&sqb[0];
taVtx.mvolA.pcw.ParaType = 7;
taVtx.mvolA.pcw.EndOfStrip = 1;
verify(list->vertexSize() > 0);
glm::vec4 vtx0{};
glm::vec4 vtx1{};
u32 stripStart = 0;
for (u32 i = 0; i < list->vtxCount; i++)
{
glm::vec4 v(vtx->x, vtx->y, vtx->z, 1);
v = projectionMatrix * curMatrix * v;
v.x /= v.w;
v.y /= v.w;
// printf("MV %f %f %f - strip %d fan %d eos %d _res %x\n", v.x, v.y, 1 / v.w, vtx->header.strip, vtx->header.fan, vtx->header.endOfStrip, vtx->header._res);
u32 triIdx = i - stripStart;
if (triIdx >= 2)
{
if (triIdx & 1)
{
taVtx.mvolA.x1 = vtx0.x;
taVtx.mvolA.y1 = vtx0.y;
taVtx.mvolA.z1 = 1 / vtx0.w;
taVtx.mvolA.x0 = vtx1.x;
taVtx.mvolA.y0 = vtx1.y;
taVtx.mvolA.z0 = 1 / vtx1.w;
}
else
{
taVtx.mvolA.x0 = vtx0.x;
taVtx.mvolA.y0 = vtx0.y;
taVtx.mvolA.z0 = 1 / vtx0.w;
taVtx.mvolA.x1 = vtx1.x;
taVtx.mvolA.y1 = vtx1.y;
taVtx.mvolA.z1 = 1 / vtx1.w;
}
taVtx.mvolA.x2 = v.x;
taVtx.mvolB.y2 = v.y;
taVtx.mvolB.z2 = 1 / v.w;
ta_vtx_data32(&sqb[0]);
ta_vtx_data32(&sqb[1]);
}
if (vtx->header.endOfStrip)
stripStart = i + 1;
vtx0 = vtx1;
vtx1 = v;
vtx++;
}
}
static void sendPolygon(ICHList *list)
{
switch (list->flags)
{
case ICHList::VTX_TYPE_V:
{
Vertex *vtx = (Vertex *)((u8 *)list + sizeof(ICHList));
if (state.listType & 1)
{
TA_ModVolParam pp{};
pp.pcw.ParaType = 4;
pp.pcw.ListType = state.listType ;
pp.pcw.User_Clip = state.userClip;
pp.pcw.Volume = list->pcw.volume;
pp.isp = list->isp;
pp.isp.CullMode = 0; // FIXME required for closed volumes and not set properly
pp.isp.DepthMode &= 3;
ta_vtx_data32((const SQBuffer *)&pp);
//for (int i = 0; i < list->vtxCount; i++)
// printf("MV %f %f %f strip %d fan %d eos %d _res %x\n", vtx[i].x, vtx[i].y, vtx[i].z, vtx[i].header.strip, vtx[i].header.fan, vtx[i].header.endOfStrip, vtx[i].header._res);
sendMVVertices(list, vtx);
}
else
{
// poly 0, vtx 0
TA_PolyParam0 pp{};
pp.pcw.ParaType = 4;
pp.pcw.ListType = state.listType ;
pp.pcw.User_Clip = state.userClip;
pp.pcw.Shadow = list->pcw.shadow;
pp.pcw.Gouraud = list->pcw.gouraud;
pp.isp = list->isp;
pp.tsp = list->tsp0;
ta_vtx_data32((const SQBuffer *)&pp);
sendVertices(list, vtx);
}
}
break;
case ICHList::VTX_TYPE_VU:
{
N2_VERTEX_VU *vtx = (N2_VERTEX_VU *)((u8 *)list + sizeof(ICHList));
if (state.listType & 1)
{
TA_ModVolParam pp{};
pp.pcw.ParaType = 4;
pp.pcw.ListType = state.listType ;
pp.pcw.User_Clip = state.userClip;
pp.pcw.Volume = list->pcw.volume;
pp.isp = list->isp;
pp.isp.CullMode = 0; // FIXME required for closed volumes and not set properly
pp.isp.DepthMode &= 3;
ta_vtx_data32((const SQBuffer *)&pp);
//for (int i = 0; i < list->vtxCount; i++)
// printf("MV %f %f %f strip %d fan %d eos %d _res %x\n", vtx[i].x, vtx[i].y, vtx[i].z, vtx[i].header.strip, vtx[i].header.fan, vtx[i].header.endOfStrip, vtx[i].header._res);
sendMVVertices(list, vtx);
}
else
{
TA_PolyParam0 pp{};
pp.pcw.ParaType = 4;
pp.pcw.ListType = state.listType ;
pp.pcw.User_Clip = state.userClip;
pp.pcw.Shadow = list->pcw.shadow;
pp.pcw.Texture = 1;
pp.pcw.Offset = list->pcw.offset;
pp.pcw.Gouraud = list->pcw.gouraud;
pp.isp = list->isp;
pp.tsp = list->tsp0;
pp.tcw = list->tcw0;
if (state.listType == 2)
pp.tsp.UseAlpha = 1; // FIXME alpha light volumes need manual settings of params?
// pp.tsp.ShadInstr = 3; // FIXME
// if (state.listType == 2) // FIXME
// {
// pp.tsp.SrcInstr = 4;
// pp.tsp.DstInstr = 5;
// }
ta_vtx_data32((const SQBuffer *)&pp);
sendVertices(list, vtx);
}
}
break;
case ICHList::VTX_TYPE_VUR:
{
TA_PolyParam0 pp{};
pp.pcw.ParaType = 4;
pp.pcw.ListType = state.listType ;
pp.pcw.User_Clip = state.userClip;
pp.pcw.Shadow = list->pcw.shadow;
pp.pcw.Texture = 1;
pp.pcw.Offset = list->pcw.offset;
pp.pcw.Gouraud = list->pcw.gouraud;
pp.isp = list->isp;
pp.tsp = list->tsp0;
pp.tcw = list->tcw0;
if (state.listType == 2)
pp.tsp.UseAlpha = 1; // FIXME alpha light volumes need manual settings of params?
// pp.tsp.ShadInstr = 3; // FIXME
// if (state.listType == 2) // FIXME
// {
// pp.tsp.SrcInstr = 4;
// pp.tsp.DstInstr = 5;
// }
ta_vtx_data32((const SQBuffer *)&pp);
N2_VERTEX_VUR *vtx = (N2_VERTEX_VUR *)((u8 *)list + sizeof(ICHList));
sendVertices(list, vtx);
}
break;
case ICHList::VTX_TYPE_VR:
{
// poly 0, vtx 0
TA_PolyParam0 pp{};
pp.pcw.ParaType = 4;
pp.pcw.ListType = state.listType ;
pp.pcw.User_Clip = state.userClip;
pp.pcw.Shadow = list->pcw.shadow;
pp.pcw.Gouraud = list->pcw.gouraud;
pp.isp = list->isp;
pp.tsp = list->tsp0;
if (state.listType == 2)
pp.tsp.UseAlpha = 1; // FIXME alpha light volumes need manual settings of params?
ta_vtx_data32((const SQBuffer *)&pp);
N2_VERTEX_VR *vtx = (N2_VERTEX_VR *)((u8 *)list + sizeof(ICHList));
sendVertices(list, vtx);
}
break;
case ICHList::VTX_TYPE_VUB:
{
// TODO
TA_PolyParam0 pp{};
pp.pcw.ParaType = 4;
pp.pcw.ListType = state.listType ;
pp.pcw.User_Clip = state.userClip;
pp.pcw.Shadow = list->pcw.shadow;
pp.pcw.Texture = 1;
pp.pcw.Offset = 1;
pp.pcw.Gouraud = list->pcw.gouraud;
pp.isp = list->isp;
pp.tsp = list->tsp0;
pp.tcw = list->tcw0;
//ta_vtx_data32((const SQBuffer *)&pp);
//N2_VERTEX_VUB *vtx = (N2_VERTEX_VUB *)((u8 *)list + sizeof(ICHList));
//sendVertices(list, vtx);
INFO_LOG(PVR, "Unhandled poly format VTX_TYPE_VUB");
}
break;
default:
WARN_LOG(PVR, "Unhandled poly format %x", list->flags);
die("Unsupported");
break;
}
}
static void executeCommand(u8 *data, int size)
{
verify(size >= 0);
verify(size < (int)ELAN_RAM_SIZE);
// if (0x2b00 == (u32)(data - elanRAM))
// for (int i = 0; i < size; i += 4)
// DEBUG_LOG(PVR, "Elan Parse %08x: %08x", (u32)(&data[i] - elanRAM), *(u32 *)&data[i]);
while (size >= 32)
{
const int oldSize = size;
ElanBase *cmd = (ElanBase *)data;
if (cmd->pcw.naomi2)
{
switch(cmd->pcw.n2Command)
{
case PCW::null:
size -= 32;
break;
case PCW::matrix:
state.setMatrix(data);
size -= sizeof(Matrix);
break;
case PCW::projMatrix:
state.setProjectionMatrix(data);
size -= sizeof(ProjMatrix);
break;
case PCW::instance:
{
Instance *instance = (Instance *)data;
if (instance->isModelInstance())
{
DEBUG_LOG(PVR, "Model instance offset %x size %x", instance->offset & 0x1ffffff8, instance->size);
//FIXME instance? model? executeCommand(&elanRAM[instance->offset & 0x1ffffff8], instance->size);
}
else if (instance->id1 & 0x10)
{
state.setLightModel(data);
}
else if ((instance->id2 & 0x40000000) || (instance->id1 & 0xffffff00)) // FIXME what are these lights without id2|0x40000000? vf4
{
if (instance->pcw.parallelLight)
{
ParallelLight *light = (ParallelLight *)data;
state.setLight(light->lightId, data);
}
else
{
PointLight *light = (PointLight *)data;
state.setLight(light->lightId, data);
}
}
else
{
INFO_LOG(PVR, "Other instance %08x %08x", instance->id1, instance->id2);
for (int i = 0; i < 32; i += 4)
INFO_LOG(PVR, " %08x: %08x", (u32)(&data[i] - elanRAM), *(u32 *)&data[i]);
}
size -= sizeof(Instance);
}
break;
case PCW::model:
{
// FIXME instance and model are switched? this is used for nl_set_light_instance()
// or static vs. dynamic?
Model *model = (Model *)data;
// TODO fails rt66 start verify(model->id1 == 0x18000000 || model->id1 == 0x10000000);
state.userClip = model->pcw.userClip;
DEBUG_LOG(PVR, "Model offset %x size %x clip %d", model->offset, model->size, model->pcw.userClip);
executeCommand(&elanRAM[model->offset & 0x1ffffff8], model->size);
size -= sizeof(Model);
}
break;
case PCW::registerWait:
{
RegisterWait *wait = (RegisterWait *)data;
if (wait->offset != (u32)-1 && wait->mask != 0)
{
DEBUG_LOG(PVR, "Register wait %x mask %x", wait->offset, wait->mask);
// wait for interrupt
HollyInterruptID inter;
switch (wait->mask)
{
case 0x80:
inter = holly_OPAQUE;
break;
case 0x100:
inter = holly_OPAQUEMOD;
break;
case 0x200:
inter = holly_TRANS;
break;
case 0x400:
inter = holly_TRANSMOD;
break;
case 0x200000:
inter = holly_PUNCHTHRU;
break;
default:
WARN_LOG(PVR, "Unknown interrupt mask %x", wait->mask);
die("unexpected");
inter = holly_OPAQUE;
break;
}
//bad reg74 |= 0x3c;
asic_RaiseInterrupt(inter);
TA_ITP_CURRENT += 32;
state.reset();
}
size -= sizeof(RegisterWait);
}
break;
case PCW::link:
{
Link *link = (Link *)data;
DEBUG_LOG(PVR, "Link to %x (%x)", link->offset & 0x1ffffff8, link->size);
executeCommand(&elanRAM[link->offset & 0x1ffffff8], link->size);
size -= sizeof(Link);
}
break;
case PCW::gmp:
state.setGMP(data);
size -= sizeof(GMP);
break;
case PCW::ich:
{
ICHList *ich = (ICHList *)data;
DEBUG_LOG(PVR, "ICH flags %x, %d verts", ich->flags, ich->vtxCount);
sendPolygon(ich);
size -= sizeof(ICHList) + ich->vertexSize() * ich->vtxCount;
}
break;
default:
DEBUG_LOG(PVR, "Unhandled Elan command %x", cmd->pcw.n2Command);
size -= 32;
break;
}
}
else
{
u32 pcw = *(u32 *)data;
if ((pcw & 0xd0ffff00) == 0x808c0000) // display list
{
DEBUG_LOG(PVR, "Display list type %d", (pcw >> 24) & 0xf);
state.listType = (pcw >> 24) & 0xf;
// TODO is this the right place for this?
SQBuffer eol{};
ta_vtx_data32(&eol);
size -= 24 * 4;
}
else if ((pcw & 0xd0fcff00) == 0x80800000) // User clipping
{
state.userClip = ((PCW&)pcw).userClip;
DEBUG_LOG(PVR, "User clip type %d", state.userClip);
size -= 0xE0;
}
else if ((pcw & 0xd0ffff00) == 0x80000000) // geometry follows or linked?
{
// FIXME this matches TA polys such as a2000009
// no possible disambiguation since 80000000 is a valid OP poly pcw (poly type 0 / vtx 0)
DEBUG_LOG(PVR, "Geometry type %d - %08x", (pcw >> 24) & 0xf, pcw);
size -= 32;
SQBuffer *sqb = (SQBuffer *)data + 1;
while (size > 32)
{
DEBUG_LOG(PVR, "vtx data %p", sqb);
ta_vtx_data32(sqb);
sqb++;
size -= 32;
}
}
else if (pcw == 0x20000000)
{
// User clipping
DEBUG_LOG(PVR, "User clipping %d,%d - %d,%d", ((u32 *)data)[4] * 32, ((u32 *)data)[5] * 32,
((u32 *)data)[6] * 32, ((u32 *)data)[7] * 32);
ta_vtx_data32((SQBuffer *)data);
size -= 32;
}
else
{
if (pcw != 0)
INFO_LOG(PVR, "Unhandled command %x", pcw);
for (int i = 0; i < 32; i += 4)
DEBUG_LOG(PVR, " %08x: %08x", (u32)(&data[i] - elanRAM), *(u32 *)&data[i]);
size -= 32;
}
}
data += oldSize - size;
}
}
template
void DYNACALL write_elancmd(u32 addr, T data)
{
verify(sizeof(T) == 4);
// DEBUG_LOG(PVR, "ELAN cmd %08x = %x", addr, data);
addr &= 0xff;
verify(addr < 0x20);
*(T *)&((u8 *)elanCmd)[addr] = data;
if (addr == 0x1c)
{
executeCommand((u8 *)elanCmd, sizeof(elanCmd));
reg74 |= 2;
reg74 &= ~0x3c;
}
}
template
T DYNACALL read_elanram(u32 addr)
{
return *(T *)&elanRAM[addr & (ELAN_RAM_SIZE - 1)];
}
template
void DYNACALL write_elanram(u32 addr, T data)
{
*(T *)&elanRAM[addr & (ELAN_RAM_SIZE - 1)] = data;
}
void init()
{
elanRAM = (u8 *)allocAligned(PAGE_SIZE, ELAN_RAM_SIZE);
}
void reset(bool hard)
{
if (hard)
{
memset(elanRAM, 0, ELAN_RAM_SIZE);
state.reset();
}
}
void term()
{
freeAligned(elanRAM);
elanRAM = nullptr;
}
void vmem_init()
{
elanRegHandler = _vmem_register_handler_Template(read_elanreg, write_elanreg);
elanCmdHandler = _vmem_register_handler_Template(read_elancmd, write_elancmd);
elanRamHandler = _vmem_register_handler_Template(read_elanram, write_elanram);
}
void vmem_map(u32 base)
{
_vmem_map_handler(elanRegHandler, base | 8, base | 8);
_vmem_map_handler(elanCmdHandler, base | 9, base | 9);
_vmem_map_handler(elanRamHandler, base | 0xA, base | 0xB);
_vmem_map_block(elanRAM, base | 0xA, base | 0xB, ELAN_RAM_SIZE - 1);
}
void serialize(Serializer& ser)
{
if (settings.platform.system != DC_PLATFORM_NAOMI2)
return;
ser << reg10;
ser << reg74;
ser << elanCmd;
if (!ser.rollback())
ser.serialize(elanRAM, ELAN_RAM_SIZE);
}
void deserialize(Deserializer& deser)
{
if (settings.platform.system != DC_PLATFORM_NAOMI2)
return;
deser >> reg10;
deser >> reg74;
deser >> elanCmd;
if (!deser.rollback())
deser.deserialize(elanRAM, ELAN_RAM_SIZE);
state.reset();
}
}