daedalus/Source/HLEGraphics/BaseRenderer.cpp
2024-10-06 15:22:22 +02:00

2216 lines
74 KiB
C++

/*
Copyright (C) 2001 StrmnNrmn
This program 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.
This program 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 this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#include "Base/Types.h"
#include "Core/Memory.h" // We access the memory buffers
#include "Core/ROM.h"
#include "Debug/Dump.h"
#include "Debug/DBGConsole.h"
#include "Graphics/NativeTexture.h"
#include "Graphics/GraphicsContext.h"
#include "HLEGraphics/BaseRenderer.h"
#include "HLEGraphics/TextureCache.h"
#include "HLEGraphics/RDPStateManager.h"
#include "HLEGraphics/DLDebug.h"
#include "Math/Math.h" // VFPU Math
#include "Utility/MathUtil.h"
#include "Ultra/ultra_gbi.h"
#include "Ultra/ultra_os.h" // System type
#include "Utility/Profiler.h"
#include <vector>
#include <random>
#ifdef DAEDALUS_CTR
struct ScePspFMatrix4
{
float m[16];
};
extern void sceGuSetMatrix(int type, const ScePspFMatrix4 * mtx);
#define GU_PROJECTION GL_PROJECTION
#endif
// Vertex allocation.
// AllocVerts/FreeVerts:
// Allocate vertices whose lifetime must extend beyond the current scope.
// On OSX we just use malloc, though we could use a scratch allocator to simplify.
// On PSP we again use sceGuGetMemory.
struct TempVerts
{
TempVerts()
: Verts(NULL)
, Count(0)
{
}
~TempVerts()
{
#if defined(DAEDALUS_GL) || defined(DAEDALUS_CTR)
free(Verts);
#endif
}
DaedalusVtx * Alloc(u32 count)
{
u32 bytes = count * sizeof(DaedalusVtx);
#ifdef DAEDALUS_PSP
Verts = static_cast<DaedalusVtx*>(sceGuGetMemory(bytes));
#endif
#if defined(DAEDALUS_GL) || defined(DAEDALUS_CTR)
Verts = static_cast<DaedalusVtx*>(malloc(bytes));
#endif
Count = count;
return Verts;
}
DaedalusVtx * Verts;
u32 Count;
};
extern "C"
{
void _TnLVFPU( const Matrix4x4 * world_matrix, const Matrix4x4 * projection_matrix, const FiddledVtx * p_in, const DaedalusVtx4 * p_out, u32 num_vertices, const TnLParams * params );
void _TnLVFPU_Plight( const Matrix4x4 * world_matrix, const Matrix4x4 * projection_matrix, const FiddledVtx * p_in, const DaedalusVtx4 * p_out, u32 num_vertices, const TnLParams * params );
void _TnLVFPUDKR( u32 num_vertices, const Matrix4x4 * projection_matrix, const FiddledVtx * p_in, const DaedalusVtx4 * p_out );
void _TnLVFPUDKRB( u32 num_vertices, const Matrix4x4 * projection_matrix, const FiddledVtx * p_in, const DaedalusVtx4 * p_out );
void _TnLVFPUCBFD( const Matrix4x4 * world_matrix, const Matrix4x4 * projection_matrix, const FiddledVtx * p_in, const DaedalusVtx4 * p_out, u32 num_vertices, const TnLParams * params, const s8 * model_norm, u32 v0 );
void _TnLVFPUPD( const Matrix4x4 * world_matrix, const Matrix4x4 * projection_matrix, const FiddledVtxPD * p_in, const DaedalusVtx4 * p_out, u32 num_vertices, const TnLParams * params, const u8 * model_norm );
void _ConvertVertice( DaedalusVtx * dest, const DaedalusVtx4 * source );
void _ConvertVerticesIndexed( DaedalusVtx * dest, const DaedalusVtx4 * source, u32 num_vertices, const u16 * indices );
u32 _ClipToHyperPlane( DaedalusVtx4 * dest, const DaedalusVtx4 * source, const v4 * plane, u32 num_verts );
}
#define GL_TRUE 1
#define GL_FALSE 0
#undef min
#undef max
extern bool gRumblePakActive;
extern u32 gAuxAddr;
static f32 fViWidth = 320.0f;
static f32 fViHeight = 240.0f;
u32 uViWidth = 320;
u32 uViHeight = 240;
f32 gZoomX=1.0; //Default is 1.0f
#ifdef DAEDALUS_DEBUG_DISPLAYLIST
// General purpose variable used for debugging
f32 TEST_VARX = 0.0f;
f32 TEST_VARY = 0.0f;
#endif
//*****************************************************************************
//
//*****************************************************************************
BaseRenderer::BaseRenderer()
: mN64ToScreenScale( 2.0f, 2.0f )
, mN64ToScreenTranslate( 0.0f, 0.0f )
, mMux( 0 )
, mTextureTile(0)
, mPrimDepth( 0.0f )
, mPrimLODFraction( 0.f )
, mFogColour(0x00ffffff) // NB top bits not set. Intentional?
, mPrimitiveColour(0xffffffff)
, mEnvColour(0xffffffff)
, mBlendColour(255, 255, 255, 0)
, mFillColour(0xffffffff)
, mModelViewTop(0)
, mWorldProjectValid(false)
, mReloadProj(true)
, mWPmodified(false)
, mScreenWidth(0.f)
, mScreenHeight(0.f)
, mNumIndices(0)
, mVtxClipFlagsUnion( 0 )
#ifdef DAEDALUS_DEBUG_DISPLAYLIST
, mNumTrisRendered( 0 )
, mNumTrisClipped( 0 )
, mNumRect( 0 )
, mNastyTexture(false)
#endif
{
DAEDALUS_ASSERT( IsPointerAligned( &mTnL, 16 ), "Oops, mTnL should be 16-byte aligned" );
for ( u32 i = 0; i < kNumBoundTextures; i++ )
{
mTileTopLeft[i].s = 0;
mTileTopLeft[i].t = 0;
mTexWrap[i].u = 0;
mTexWrap[i].v = 0;
mActiveTile[i] = 0;
}
memset( &mTnL, 0, sizeof(mTnL) );
mTnL.Flags._u32 = 0;
mTnL.NumLights = 0;
mTnL.TextureScaleX = 1.0f;
mTnL.TextureScaleY = 1.0f;
}
//*****************************************************************************
//
//*****************************************************************************
BaseRenderer::~BaseRenderer()
{
}
//*****************************************************************************
//
//*****************************************************************************
void BaseRenderer::SetVIScales()
{
u32 ScaleX = Memory_VI_GetRegister( VI_X_SCALE_REG ) & 0xFFF;
u32 ScaleY = Memory_VI_GetRegister( VI_Y_SCALE_REG ) & 0xFFF;
f32 fScaleX = (f32)ScaleX / 1024.0f;
f32 fScaleY = (f32)ScaleY / 2048.0f;
u32 HStartReg = Memory_VI_GetRegister( VI_H_START_REG );
u32 VStartReg = Memory_VI_GetRegister( VI_V_START_REG );
u32 hstart = HStartReg >> 16;
u32 hend = HStartReg & 0xffff;
u32 vstart = VStartReg >> 16;
u32 vend = VStartReg & 0xffff;
u32 width = Memory_VI_GetRegister( VI_WIDTH_REG );
// Sometimes HStartReg can be zero.. ex PD, Lode Runner, Cyber Tiger
if (hend == hstart)
{
hend = (u32)((f32)width / fScaleX);
}
f32 vi_width = (hend-hstart) * fScaleX;
f32 vi_height = (vend-vstart) * fScaleY * (g_ROM.TvType == OS_TV_PAL ? 1.0041841f : 1.0126582f);
//printf("width[%d] ViWidth[%f] ViHeight[%f]\n", width, vi_width, vi_height);
//This corrects height in various games ex : Megaman 64, Cyber Tiger. 40Winks need width >= ((u32)vi_width << 1) for menus //Corn
if (width > 768 || width >= ((u32)vi_width * 2))
{
vi_height *= 2;
}
// Avoid a divide by zero in the viewport code.
if (vi_width == 0.0f) vi_width = 320.0f;
if (vi_height == 0.0f) vi_height = 240.0f;
fViWidth = vi_width;
fViHeight = vi_height;
//Used to set a limit on Scissors //Corn
uViWidth = (u32)vi_width;
uViHeight = (u32)vi_height;
}
//*****************************************************************************
// Reset for a new frame
//*****************************************************************************
void BaseRenderer::Reset()
{
mNumIndices = 0;
mVtxClipFlagsUnion = 0;
#ifdef DAEDALUS_DEBUG_DISPLAYLIST
mNumTrisRendered = 0;
mNumTrisClipped = 0;
mNumRect = 0;
#endif
}
//*****************************************************************************
//
//*****************************************************************************
void BaseRenderer::BeginScene()
{
CGraphicsContext::Get()->BeginFrame();
RestoreRenderStates();
#ifdef DAEDALUS_DEBUG_DISPLAYLIST
ResetDebugState();
#endif
InitViewport();
}
//*****************************************************************************
//
//*****************************************************************************
void BaseRenderer::EndScene()
{
CGraphicsContext::Get()->EndFrame();
//
// Clear this, to ensure we're force to check for updates to it on the next frame
for( u32 i = 0; i < kNumBoundTextures; i++ )
{
mBoundTextureInfo[ i ] = TextureInfo();
mBoundTexture[ i ] = NULL;
}
}
//*****************************************************************************
//
//*****************************************************************************
void BaseRenderer::InitViewport()
{
// Init the N64 viewport.
mVpScale = v2( 640.f*0.25f, 480.f*0.25f );
mVpTrans = v2( 640.f*0.25f, 480.f*0.25f );
std::default_random_engine FastRand;
// Get the current display dimensions. This might change frame by frame e.g. if the window is resized.
u32 display_width = 0;
u32 display_height = 0;
CGraphicsContext::Get()->ViewportType(&display_width, &display_height);
#ifdef DAEDALUS_ENABLE_ASSERTS
DAEDALUS_ASSERT( display_width && display_height, "Unhandled viewport type" );
#endif
mScreenWidth = (f32)display_width;
mScreenHeight = (f32)display_height;
#ifdef DAEDALUS_PSP
// Centralise the viewport in the display.
u32 frame_width = gGlobalPreferences.TVEnable ? 720 : 480;
u32 frame_height = gGlobalPreferences.TVEnable ? 480 : 272;
s32 display_x = (s32)(frame_width - display_width) / 2;
s32 display_y = (s32)(frame_height - display_height) / 2;
#else
s32 display_x = 0, display_y = 0;
#endif
mN64ToScreenScale.x = gZoomX * mScreenWidth / fViWidth;
mN64ToScreenScale.y = gZoomX * mScreenHeight / fViHeight;
mN64ToScreenTranslate.x = (f32)display_x - roundf(0.55f * (gZoomX - 1.0f) * fViWidth);
mN64ToScreenTranslate.y = (f32)display_y - roundf(0.55f * (gZoomX - 1.0f) * fViHeight);
#ifndef DAEDALUS_CTR
if (gRumblePakActive)
{
mN64ToScreenTranslate.x += (FastRand() & 3);
mN64ToScreenTranslate.y += (FastRand() & 3);
}
#endif
#if defined(DAEDALUS_GL) || defined(DAEDALUS_CTR)
f32 w = mScreenWidth;
f32 h = mScreenHeight;
mScreenToDevice = Matrix4x4(
2.f / w, 0.f, 0.f, 0.f,
0.f, -2.f / h, 0.f, 0.f,
0.f, 0.f, 1.f, 0.f,
-1.0f, 1.f, 0.f, 1.f
);
#endif
UpdateViewport();
}
//*****************************************************************************
//
//*****************************************************************************
void BaseRenderer::SetN64Viewport( const v2 & scale, const v2 & trans )
{
// Only Update viewport when it actually changed, this happens rarely
//
if( mVpScale.x == scale.x && mVpScale.y == scale.y &&
mVpTrans.x == trans.x && mVpTrans.y == trans.y )
return;
mVpScale.x = scale.x;
mVpScale.y = scale.y;
mVpTrans.x = trans.x;
mVpTrans.y = trans.y;
UpdateViewport();
}
//*****************************************************************************
//
//*****************************************************************************
void BaseRenderer::UpdateViewport()
{
v2 n64_min( mVpTrans.x - mVpScale.x, mVpTrans.y - mVpScale.y );
v2 n64_max( mVpTrans.x + mVpScale.x, mVpTrans.y + mVpScale.y );
v2 psp_min;
v2 psp_max;
ConvertN64ToScreen( n64_min, psp_min );
ConvertN64ToScreen( n64_max, psp_max );
s32 vp_x = s32( psp_min.x );
s32 vp_y = s32( psp_min.y );
s32 vp_w = s32( psp_max.x - psp_min.x );
s32 vp_h = s32( psp_max.y - psp_min.y );
//DBGConsole_Msg(0, "[WViewport Changed (%d) (%d)]",vp_w,vp_h );
#if defined(DAEDALUS_PSP)
const u32 vx = 2048;
const u32 vy = 2048;
sceGuOffset(vx - (vp_w/2),vy - (vp_h/2));
sceGuViewport(vx + vp_x, vy + vp_y, vp_w, vp_h);
#elif defined(DAEDALUS_GL) || defined(DAEDALUS_CTR)
glViewport(vp_x, (s32)mScreenHeight - (vp_h + vp_y), vp_w, vp_h);
#ifdef DAEDALUS_ENABLE_ASSERTS
#else
DAEDALUS_ERROR("Code to set viewport not implemented on this platform");
#endif
#endif
}
//*****************************************************************************
// Returns true if bounding volume is visible within NDC box, false if culled
//*****************************************************************************
bool BaseRenderer::TestVerts(u32 v0, u32 vn) const
{
if ((vn + v0) >= kMaxN64Vertices)
{
DAEDALUS_ERROR("Vertex index is out of bounds (%d)", (vn + v0));
return false;
}
if (vn < v0)
std::swap(vn, v0);
// Swap< u32 >( vn, v0 );
u32 flags = mVtxProjected[v0].ClipFlags;
for (u32 i = (v0+1); i <= vn; i++)
{
flags &= mVtxProjected[i].ClipFlags;
if (flags == 0)
return true;
}
return false;
}
//*****************************************************************************
// Returns true if triangle visible and rendered, false otherwise
//*****************************************************************************
bool BaseRenderer::AddTri(u32 v0, u32 v1, u32 v2)
{
//DAEDALUS_PROFILE( "BaseRenderer::AddTri" );
if (v0 >= kMaxN64Vertices || v1 >= kMaxN64Vertices || v2 >= kMaxN64Vertices)
{
DAEDALUS_ERROR("Vertex index is out of bounds (v0: %d) (v1: %d) (v2: %d)", v0, v1, v2);
return false;
}
const u32 & f0 = mVtxProjected[v0].ClipFlags;
const u32 & f1 = mVtxProjected[v1].ClipFlags;
const u32 & f2 = mVtxProjected[v2].ClipFlags;
if ( f0 & f1 & f2 )
{
DL_PF(" Tri: %d,%d,%d (Culled -> NDC box)", v0, v1, v2);
#ifdef DAEDALUS_DEBUG_DISPLAYLIST
mNumTrisClipped++;
#endif
return false;
}
//
//Cull BACK or FRONT faceing tris early in the pipeline //Corn
//
if( mTnL.Flags.TriCull )
{
#ifdef DAEDALUS_PSP_USE_VFPU
const s32 sign = vfpu_TriNormSign((u8*)&mVtxProjected[0], v0, v1, v2);
if( sign <= 0 )
#else
const v4 & A = mVtxProjected[v0].ProjectedPos;
const v4 & B = mVtxProjected[v1].ProjectedPos;
const v4 & C = mVtxProjected[v2].ProjectedPos;
//Avoid using 1/w, will use five more mults but save three divides //Corn
//Precalc reused w combos so compiler does a proper job
const f32 ABw = A.w * B.w;
const f32 ACw = A.w * C.w;
const f32 BCw = B.w * C.w;
const f32 AxBC = A.x * BCw;
const f32 AyBC = A.y * BCw;
const f32 cross = (B.x * ACw - AxBC) * (C.y * ABw - AyBC) - (C.x * ABw - AxBC) * (B.y * ACw - AyBC);
const f32 sign = cross * ABw * C.w;
if( sign <= 0.0f )
#endif
{
if( mTnL.Flags.CullBack )
{
DL_PF(" Tri: %d,%d,%d (Culled -> Back Face)", v0, v1, v2);
#ifdef DAEDALUS_DEBUG_DISPLAYLIST
mNumTrisClipped++;
#endif
return false;
}
}
else if( !mTnL.Flags.CullBack )
{
DL_PF(" Tri: %d,%d,%d (Culled -> Front Face)", v0, v1, v2);
#ifdef DAEDALUS_DEBUG_DISPLAYLIST
mNumTrisClipped++;
#endif
return false;
}
}
DL_PF(" Tri: %d,%d,%d (Rendered)", v0, v1, v2);
#ifdef DAEDALUS_DEBUG_DISPLAYLIST
mNumTrisRendered++;
#endif
if (mNumIndices + 3 <= kMaxIndices)
{
mIndexBuffer[mNumIndices++] = (u16)v0;
mIndexBuffer[mNumIndices++] = (u16)v1;
mIndexBuffer[mNumIndices++] = (u16)v2;
}
else
{
DAEDALUS_ERROR("Array overflow, too many Indices");
}
mVtxClipFlagsUnion |= f0 | f1 | f2;
return true;
}
//*****************************************************************************
//
//*****************************************************************************
void BaseRenderer::FlushTris()
{
DAEDALUS_PROFILE( "BaseRenderer::FlushTris" );
DAEDALUS_ASSERT( mNumIndices, "Call to FlushTris() with nothing to render" );
TempVerts temp_verts;
// If any bit is set here it means we have to clip the trianlges since PSP HW clipping sux!
if(mVtxClipFlagsUnion != 0)
{
PrepareTrisClipped( &temp_verts );
}
else
{
PrepareTrisUnclipped( &temp_verts );
}
// No vertices to render? //Corn
if( temp_verts.Count == 0 )
{
mNumIndices = 0;
mVtxClipFlagsUnion = 0;
return;
}
//
// Check for depth source, this is for Nascar games, hopefully won't mess up anything
DAEDALUS_ASSERT( !gRDPOtherMode.depth_source, " Warning : Using depth source in flushtris" );
//
// Render out our vertices
RenderTriangles( temp_verts.Verts, temp_verts.Count, gRDPOtherMode.depth_source ? true : false );
mNumIndices = 0;
mVtxClipFlagsUnion = 0;
}
//*****************************************************************************
//
// The following clipping code was taken from The Irrlicht Engine.
// See http://irrlicht.sourceforge.net/ for more information.
// Copyright (C) 2002-2006 Nikolaus Gebhardt/Alten Thomas
//
//Croping triangles just outside the NDC box and let PSP HW do the final crop
//improves quality but fails in some games (Rocket Robot/Lego racers)//Corn
//*****************************************************************************
// ALIGNED_TYPE(const v4, NDCPlane[6], 16) =
std::array<const v4, 6> NDCPlane =
{
v4( 0.f, 0.f, -1.f, -1.f ), // near
v4( 0.f, 0.f, 1.f, -1.f ), // far
v4( 1.f, 0.f, 0.f, -1.f ), // left
v4( -1.f, 0.f, 0.f, -1.f ), // right
v4( 0.f, 1.f, 0.f, -1.f ), // bottom
v4( 0.f, -1.f, 0.f, -1.f ) // top
};
//*****************************************************************************
//VFPU tris clip(fast)
//*****************************************************************************
#ifdef DAEDALUS_PSP_USE_VFPU
u32 clip_tri_to_frustum( DaedalusVtx4 * v0, DaedalusVtx4 * v1 )
{
u32 vOut( 3 );
vOut = _ClipToHyperPlane( v1, v0, &NDCPlane[0], vOut ); if( vOut < 3 ) return vOut; // near
vOut = _ClipToHyperPlane( v0, v1, &NDCPlane[1], vOut ); if( vOut < 3 ) return vOut; // far
vOut = _ClipToHyperPlane( v1, v0, &NDCPlane[2], vOut ); if( vOut < 3 ) return vOut; // left
vOut = _ClipToHyperPlane( v0, v1, &NDCPlane[3], vOut ); if( vOut < 3 ) return vOut; // right
vOut = _ClipToHyperPlane( v1, v0, &NDCPlane[4], vOut ); if( vOut < 3 ) return vOut; // bottom
vOut = _ClipToHyperPlane( v0, v1, &NDCPlane[5], vOut ); // top
return vOut;
}
/*void BaseRenderer::TestVFPUVerts( u32 v0, u32 num, const FiddledVtx * verts, const Matrix4x4 & mat_world )
{
bool env_map( (mTnL.Flags._u32 & (TNL_LIGHT|TNL_TEXGEN)) == (TNL_LIGHT|TNL_TEXGEN) );
u32 vend( v0 + num );
for (u32 i = v0; i < vend; i++)
{
const FiddledVtx & vert = verts[i - v0];
const v4 & projected( mVtxProjected[i].ProjectedPos );
if (mTnL.Flags.Fog)
{
float eyespace_z = projected.z / projected.w;
float fog_coeff = (eyespace_z * mTnL.FogMult) + mTnL.FogOffset;
// Set the alpha
f32 value = std::clamp< f32 >( fog_coeff, 0.0f, 1.0f );
if( Abs( value - mVtxProjected[i].Colour.w ) > 0.01f )
{
printf( "Fog wrong: %f != %f\n", mVtxProjected[i].Colour.w, value );
}
}
if (mTnL.Flags.Texture)
{
// Update texture coords n.b. need to divide tu/tv by bogus scale on addition to buffer
// If the vert is already lit, then there is no normal (and hence we
// can't generate tex coord)
float tx, ty;
if (env_map)
{
v3 vecTransformedNormal; // Used only when TNL_LIGHT set
v3 model_normal(f32( vert.norm_x ), f32( vert.norm_y ), f32( vert.norm_z ) );
vecTransformedNormal = mat_world.TransformNormal( model_normal );
vecTransformedNormal.Normalise();
const v3 & norm = vecTransformedNormal;
// Assign the spheremap's texture coordinates
tx = (0.5f * ( 1.0f + ( norm.x*mat_world.m11 +
norm.y*mat_world.m21 +
norm.z*mat_world.m31 ) ));
ty = (0.5f * ( 1.0f - ( norm.x*mat_world.m12 +
norm.y*mat_world.m22 +
norm.z*mat_world.m32 ) ));
}
else
{
tx = (float)vert.tu * mTnL.TextureScaleX;
ty = (float)vert.tv * mTnL.TextureScaleY;
}
if( Abs(tx - mVtxProjected[i].Texture.x ) > 0.0001f ||
Abs(ty - mVtxProjected[i].Texture.y ) > 0.0001f )
{
printf( "tx/y wrong : %f,%f != %f,%f (%s)\n", mVtxProjected[i].Texture.x, mVtxProjected[i].Texture.y, tx, ty, env_map ? "env" : "scale" );
}
}
//
// Initialise the clipping flags (always done on the VFPU, so skip here)
//
//u32 flags = CalcClipFlags( projected );
//if( flags != mVtxProjected[i].ClipFlags )
//{
// printf( "flags wrong: %02x != %02x\n", mVtxProjected[i].ClipFlags, flags );
//}
}
}*/
#else // FPU/CPU(slower)
//*****************************************************************************
//CPU interpolate line parameters
//*****************************************************************************
void DaedalusVtx4::Interpolate( const DaedalusVtx4 & lhs, const DaedalusVtx4 & rhs, float factor )
{
ProjectedPos = lhs.ProjectedPos + (rhs.ProjectedPos - lhs.ProjectedPos) * factor;
TransformedPos = lhs.TransformedPos + (rhs.TransformedPos - lhs.TransformedPos) * factor;
Colour = lhs.Colour + (rhs.Colour - lhs.Colour) * factor;
Texture = lhs.Texture + (rhs.Texture - lhs.Texture) * factor;
ClipFlags = 0;
}
//*****************************************************************************
//CPU line clip to plane
//*****************************************************************************
static u32 clipToHyperPlane( DaedalusVtx4 * dest, const DaedalusVtx4 * source, u32 inCount, const v4 &plane )
{
u32 outCount(0);
DaedalusVtx4 * out(dest);
const DaedalusVtx4 * a;
const DaedalusVtx4 * b(source);
f32 bDotPlane = b->ProjectedPos.Dot( plane );
for( u32 i = 1; i < inCount + 1; ++i)
{
//a = &source[i%inCount];
const s32 condition = i - inCount;
const s32 index = (( ( condition >> 31 ) & ( i ^ condition ) ) ^ condition );
a = &source[index];
f32 aDotPlane = a->ProjectedPos.Dot( plane );
// current point inside
if ( aDotPlane <= 0.f )
{
// last point outside
if ( bDotPlane > 0.f )
{
// intersect line segment with plane
out->Interpolate( *b, *a, bDotPlane / (b->ProjectedPos - a->ProjectedPos).Dot( plane ) );
out++;
outCount++;
}
// copy current to out
*out = *a;
b = out;
out++;
outCount++;
}
else
{
// current point outside
if ( bDotPlane <= 0.f )
{
// previous was inside, intersect line segment with plane
out->Interpolate( *b, *a, bDotPlane / (b->ProjectedPos - a->ProjectedPos).Dot( plane ) );
out++;
outCount++;
}
b = a;
}
bDotPlane = aDotPlane;
}
return outCount;
}
//*****************************************************************************
//CPU tris clip to frustum
//*****************************************************************************
static u32 clip_tri_to_frustum( DaedalusVtx4 * v0, DaedalusVtx4 * v1 )
{
u32 vOut = 3;
vOut = clipToHyperPlane( v1, v0, vOut, NDCPlane[0] ); if ( vOut < 3 ) return vOut; // near
vOut = clipToHyperPlane( v0, v1, vOut, NDCPlane[1] ); if ( vOut < 3 ) return vOut; // far
vOut = clipToHyperPlane( v1, v0, vOut, NDCPlane[2] ); if ( vOut < 3 ) return vOut; // left
vOut = clipToHyperPlane( v0, v1, vOut, NDCPlane[3] ); if ( vOut < 3 ) return vOut; // right
vOut = clipToHyperPlane( v1, v0, vOut, NDCPlane[4] ); if ( vOut < 3 ) return vOut; // bottom
vOut = clipToHyperPlane( v0, v1, vOut, NDCPlane[5] ); // top
return vOut;
}
//*****************************************************************************
// Set Clipflags
//*****************************************************************************
static u32 set_clip_flags(const v4 & projected)
{
u32 clip_flags = 0;
if (projected.x < -projected.w) clip_flags |= X_POS;
else if (projected.x > projected.w) clip_flags |= X_NEG;
if (projected.y < -projected.w) clip_flags |= Y_POS;
else if (projected.y > projected.w) clip_flags |= Y_NEG;
if (projected.z < -projected.w) clip_flags |= Z_POS;
else if (projected.z > projected.w) clip_flags |= Z_NEG;
return clip_flags;
}
#endif // CPU clip
//*****************************************************************************
//
//*****************************************************************************
namespace
{
// std::array<DaedalusVtx4, 8> temp_a;
// std::array<DaedalusVtx4, 8> temp_b;
DaedalusVtx4 temp_a[ 8 ];
DaedalusVtx4 temp_b[ 8 ];
// Flying Dragon clips more than 256
const u32 MAX_CLIPPED_VERTS = 320;
DaedalusVtx clip_vtx[MAX_CLIPPED_VERTS];
// std::array<DaedalusVtx, MAX_CLIPPED_VERTS> clip_vtx;
}
//*****************************************************************************
//
//*****************************************************************************
void BaseRenderer::PrepareTrisClipped( TempVerts * temp_verts ) const
{
#ifdef DAEDALUS_ENABLE_PROFILING
DAEDALUS_PROFILE( "BaseRenderer::PrepareTrisClipped" );
#endif
//
// At this point all vertices are lit/projected and have both transformed and projected
// vertex positions. For the best results we clip against the projected vertex positions,
// but use the resulting intersections to interpolate the transformed positions.
// The clipping is more efficient in normalised device coordinates, but rendering these
// directly prevents the PSP performing perspective correction. We could invert the projection
// matrix and use this to back-project the clip planes into world coordinates, but this
// suffers from various precision issues. Carrying around both sets of coordinates gives
// us the best of both worlds :)
//
// Convert directly to PSP hardware format, that way we only copy 24 bytes instead of 64 bytes //Corn
//
u32 num_vertices = 0;
for(u32 i = 0; i < (mNumIndices - 2);)
{
const u32 & idx0 = mIndexBuffer[ i++ ];
const u32 & idx1 = mIndexBuffer[ i++ ];
const u32 & idx2 = mIndexBuffer[ i++ ];
//Check if any of the vertices are outside the clipbox (NDC), if so we need to clip the triangle
if(mVtxProjected[idx0].ClipFlags | mVtxProjected[idx1].ClipFlags | mVtxProjected[idx2].ClipFlags)
{
temp_a[ 0 ] = mVtxProjected[ idx0 ];
temp_a[ 1 ] = mVtxProjected[ idx1 ];
temp_a[ 2 ] = mVtxProjected[ idx2 ];
u32 out = clip_tri_to_frustum( temp_a, temp_b );
//If we have less than 3 vertices left after the clipping
//we can't make a triangle so we bail and skip rendering it.
#ifdef DAEDALUS_ENABLE_PROFILING
DL_PF(" Clip & re-tesselate [%d,%d,%d] with %d vertices", i-3, i-2, i-1, out);
DL_PF(" %#5.3f, %#5.3f, %#5.3f", mVtxProjected[ idx0 ].ProjectedPos.x/mVtxProjected[ idx0 ].ProjectedPos.w, mVtxProjected[ idx0 ].ProjectedPos.y/mVtxProjected[ idx0 ].ProjectedPos.w, mVtxProjected[ idx0 ].ProjectedPos.z/mVtxProjected[ idx0 ].ProjectedPos.w);
DL_PF(" %#5.3f, %#5.3f, %#5.3f", mVtxProjected[ idx1 ].ProjectedPos.x/mVtxProjected[ idx1 ].ProjectedPos.w, mVtxProjected[ idx1 ].ProjectedPos.y/mVtxProjected[ idx1 ].ProjectedPos.w, mVtxProjected[ idx1 ].ProjectedPos.z/mVtxProjected[ idx1 ].ProjectedPos.w);
DL_PF(" %#5.3f, %#5.3f, %#5.3f", mVtxProjected[ idx2 ].ProjectedPos.x/mVtxProjected[ idx2 ].ProjectedPos.w, mVtxProjected[ idx2 ].ProjectedPos.y/mVtxProjected[ idx2 ].ProjectedPos.w, mVtxProjected[ idx2 ].ProjectedPos.z/mVtxProjected[ idx2 ].ProjectedPos.w);
#endif
if( out < 3 )
continue;
// Retesselate
u32 new_num_vertices( num_vertices + (out - 3) * 3 );
if( new_num_vertices > MAX_CLIPPED_VERTS )
{
#ifdef DAEDALUS_DEBUG_CONSOLE
DAEDALUS_ERROR( "Too many clipped verts: %d", new_num_vertices );
#endif
break;
}
//Make new triangles from the vertices we got back from clipping the original triangle
for( u32 j = 0; j <= out - 3; ++j)
{
#ifdef DAEDALUS_PSP_USE_VFPU
_ConvertVertice( &clip_vtx[ num_vertices++ ], &temp_a[ 0 ]);
_ConvertVertice( &clip_vtx[ num_vertices++ ], &temp_a[ j + 1 ]);
_ConvertVertice( &clip_vtx[ num_vertices++ ], &temp_a[ j + 2 ]);
#else
clip_vtx[ num_vertices ].Texture = temp_a[ 0 ].Texture;
clip_vtx[ num_vertices ].Colour = c32( temp_a[ 0 ].Colour );
clip_vtx[ num_vertices ].Position.x = temp_a[ 0 ].TransformedPos.x;
clip_vtx[ num_vertices ].Position.y = temp_a[ 0 ].TransformedPos.y;
clip_vtx[ num_vertices++ ].Position.z = temp_a[ 0 ].TransformedPos.z;
clip_vtx[ num_vertices ].Texture = temp_a[ j + 1 ].Texture;
clip_vtx[ num_vertices ].Colour = c32( temp_a[ j + 1 ].Colour );
clip_vtx[ num_vertices ].Position.x = temp_a[ j + 1 ].TransformedPos.x;
clip_vtx[ num_vertices ].Position.y = temp_a[ j + 1 ].TransformedPos.y;
clip_vtx[ num_vertices++ ].Position.z = temp_a[ j + 1 ].TransformedPos.z;
clip_vtx[ num_vertices ].Texture = temp_a[ j + 2 ].Texture;
clip_vtx[ num_vertices ].Colour = c32( temp_a[ j + 2 ].Colour );
clip_vtx[ num_vertices ].Position.x = temp_a[ j + 2 ].TransformedPos.x;
clip_vtx[ num_vertices ].Position.y = temp_a[ j + 2 ].TransformedPos.y;
clip_vtx[ num_vertices++ ].Position.z = temp_a[ j + 2 ].TransformedPos.z;
#endif
}
}
else //Triangle is inside the clipbox so we just add it as it is.
{
if( num_vertices > (MAX_CLIPPED_VERTS - 3) )
{
#ifdef DAEDALUS_DEBUG_CONSOLE
DAEDALUS_ERROR( "Too many clipped verts: %d", num_vertices + 3 );
#endif
break;
}
#ifdef DAEDALUS_PSP_USE_VFPU
_ConvertVertice( &clip_vtx[ num_vertices++ ], &mVtxProjected[ idx0 ]);
_ConvertVertice( &clip_vtx[ num_vertices++ ], &mVtxProjected[ idx1 ]);
_ConvertVertice( &clip_vtx[ num_vertices++ ], &mVtxProjected[ idx2 ]);
#else
clip_vtx[ num_vertices ].Texture = mVtxProjected[ idx0 ].Texture;
clip_vtx[ num_vertices ].Colour = c32( mVtxProjected[ idx0 ].Colour );
clip_vtx[ num_vertices ].Position.x = mVtxProjected[ idx0 ].TransformedPos.x;
clip_vtx[ num_vertices ].Position.y = mVtxProjected[ idx0 ].TransformedPos.y;
clip_vtx[ num_vertices++ ].Position.z = mVtxProjected[ idx0 ].TransformedPos.z;
clip_vtx[ num_vertices ].Texture = mVtxProjected[ idx1 ].Texture;
clip_vtx[ num_vertices ].Colour = c32( mVtxProjected[ idx1 ].Colour );
clip_vtx[ num_vertices ].Position.x = mVtxProjected[ idx1 ].TransformedPos.x;
clip_vtx[ num_vertices ].Position.y = mVtxProjected[ idx1 ].TransformedPos.y;
clip_vtx[ num_vertices++ ].Position.z = mVtxProjected[ idx1 ].TransformedPos.z;
clip_vtx[ num_vertices ].Texture = mVtxProjected[ idx2 ].Texture;
clip_vtx[ num_vertices ].Colour = c32( mVtxProjected[ idx2 ].Colour );
clip_vtx[ num_vertices ].Position.x = mVtxProjected[ idx2 ].TransformedPos.x;
clip_vtx[ num_vertices ].Position.y = mVtxProjected[ idx2 ].TransformedPos.y;
clip_vtx[ num_vertices++ ].Position.z = mVtxProjected[ idx2 ].TransformedPos.z;
#endif
}
}
//
// Now the vertices have been clipped we need to write them into
// a buffer we obtain this from the display list.
if (num_vertices > 0)
{
DaedalusVtx * p_vertices = temp_verts->Alloc(num_vertices);
// std::copy(clip_vtx.begin(), clip_vtx.end(),)
memcpy( p_vertices, clip_vtx, num_vertices * sizeof(DaedalusVtx) ); //std memcpy() is as fast as VFPU here!
}
}
//*****************************************************************************
//
//*****************************************************************************
void BaseRenderer::PrepareTrisUnclipped( TempVerts * temp_verts ) const
{
#ifdef DAEDALUS_ENABLE_ASSERTS
DAEDALUS_PROFILE( "BaseRenderer::PrepareTrisUnclipped" );
DAEDALUS_ASSERT( mNumIndices > 0, "The number of indices should have been checked" );
#endif
const u32 num_vertices = mNumIndices;
DaedalusVtx * p_vertices = temp_verts->Alloc(num_vertices);
//
// Previously this code set up an index buffer to avoid processing the
// same vertices more than once - we avoid this now as there is apparently
// quite a large performance penalty associated with using these on the PSP.
//
// http://forums.ps2dev.org/viewtopic.php?t=4703
//
//DAEDALUS_STATIC_ASSERT( MAX_CLIPPED_VERTS > std::size(mIndexBuffer) );
#ifdef DAEDALUS_PSP_USE_VFPU
_ConvertVerticesIndexed( p_vertices, mVtxProjected, num_vertices, mIndexBuffer );
#else
//
// Now we just shuffle all the data across directly (potentially duplicating verts)
//
for( u32 i = 0; i < num_vertices; ++i )
{
u32 index = mIndexBuffer[ i ];
p_vertices[ i ].Texture = mVtxProjected[ index ].Texture;
p_vertices[ i ].Colour = c32( mVtxProjected[ index ].Colour );
p_vertices[ i ].Position.x = mVtxProjected[ index ].TransformedPos.x;
p_vertices[ i ].Position.y = mVtxProjected[ index ].TransformedPos.y;
p_vertices[ i ].Position.z = mVtxProjected[ index ].TransformedPos.z;
}
#endif
}
#ifndef DAEDALUS_PSP_USE_VFPU
//*****************************************************************************
//
//*****************************************************************************
v3 BaseRenderer::LightVert( const v3 & norm ) const
{
const v3 & col = mTnL.Lights[mTnL.NumLights].Colour;
v3 result( col.x, col.y, col.z );
for ( u32 l = 0; l < mTnL.NumLights; l++ )
{
f32 fCosT = norm.Dot( mTnL.Lights[l].Direction );
if (fCosT > 0.0f)
{
result.x += mTnL.Lights[l].Colour.x * fCosT;
result.y += mTnL.Lights[l].Colour.y * fCosT;
result.z += mTnL.Lights[l].Colour.z * fCosT;
}
}
//Clamp to 1.0
if( result.x > 1.0f ) result.x = 1.0f;
if( result.y > 1.0f ) result.y = 1.0f;
if( result.z > 1.0f ) result.z = 1.0f;
return result;
}
//*****************************************************************************
//
//*****************************************************************************
v3 BaseRenderer::LightPointVert( const v4 & w ) const
{
const v3 & col = mTnL.Lights[mTnL.NumLights].Colour;
v3 result( col.x, col.y, col.z );
for ( u32 l = 0; l < mTnL.NumLights; l++ )
{
if ( mTnL.Lights[l].SkipIfZero )
{
v3 distance_vec( mTnL.Lights[l].Position.x-w.x, mTnL.Lights[l].Position.y-w.y, mTnL.Lights[l].Position.z-w.z );
f32 light_qlen = distance_vec.LengthSq();
f32 light_llen = sqrtf( light_qlen );
f32 at = mTnL.Lights[l].ca + mTnL.Lights[l].la * light_llen + mTnL.Lights[l].qa * light_qlen;
if (at > 0.0f)
{
f32 fCosT = 1.0f/at;
result.x += mTnL.Lights[l].Colour.x * fCosT;
result.y += mTnL.Lights[l].Colour.y * fCosT;
result.z += mTnL.Lights[l].Colour.z * fCosT;
}
}
}
//Clamp to 1.0
if( result.x > 1.0f ) result.x = 1.0f;
if( result.y > 1.0f ) result.y = 1.0f;
if( result.z > 1.0f ) result.z = 1.0f;
return result;
}
#endif
//*****************************************************************************
// Standard rendering pipeline using FPU/CPU
//*****************************************************************************
void BaseRenderer::SetNewVertexInfo(u32 address, u32 v0, u32 n)
{
UpdateWorldProject();
const Matrix4x4 & mat_world_project = mWorldProject;
const Matrix4x4 & mat_world = mModelViewStack[mModelViewTop];
DL_PF( " Ambient color RGB[%f][%f][%f] Texture scale X[%f] Texture scale Y[%f]", mTnL.Lights[mTnL.NumLights].Colour.x, mTnL.Lights[mTnL.NumLights].Colour.y, mTnL.Lights[mTnL.NumLights].Colour.z, mTnL.TextureScaleX, mTnL.TextureScaleY);
DL_PF( " Light[%d %s] Texture[%s] EnvMap[%s] Fog[%s]", mTnL.NumLights, (mTnL.Flags.Light)? (mTnL.Flags.PointLight)? "Point":"Normal":"Off", (mTnL.Flags.Texture)? "On":"Off", (mTnL.Flags.TexGen)? (mTnL.Flags.TexGenLin)? "Linear":"Spherical":"Off", (mTnL.Flags.Fog)? "On":"Off");
const FiddledVtx * pVtxBase = (const FiddledVtx*)(g_pu8RamBase + address);
#ifdef DAEDALUS_PSP_USE_VFPU
if ( !mTnL.Flags.PointLight )
{
//Normal rendering
_TnLVFPU( &mat_world, &mat_world_project, pVtxBase, &mVtxProjected[v0], n, &mTnL );
}
else
{
//Point light for Zelda MM
_TnLVFPU_Plight( &mat_world, &mat_world_project, pVtxBase, &mVtxProjected[v0], n, &mTnL );
}
#else
// Transform and Project + Lighting or Transform and Project with Colour
//
for (u32 i = v0; i < v0 + n; i++)
{
const FiddledVtx & vert = pVtxBase[i - v0];
// VTX Transform
//
v4 w( f32( vert.x ), f32( vert.y ), f32( vert.z ), 1.0f );
v4 & projected( mVtxProjected[i].ProjectedPos );
projected = mat_world_project.Transform( w );
mVtxProjected[i].TransformedPos = mat_world.Transform( w );
// Initialise the clipping flags
//
mVtxProjected[i].ClipFlags = set_clip_flags( projected );
// LIGHTING OR COLOR
//
if ( mTnL.Flags.Light )
{
v3 model_normal(f32( vert.norm_x ), f32( vert.norm_y ), f32( vert.norm_z ) );
v3 vecTransformedNormal;
vecTransformedNormal = mat_world.TransformNormal( model_normal );
vecTransformedNormal.Normalise();
v3 col;
if ( mTnL.Flags.PointLight )
{//POINT LIGHT
col = LightPointVert(w); // Majora's Mask uses this
}
else
{//NORMAL LIGHT
col = LightVert(vecTransformedNormal);
}
mVtxProjected[i].Colour.x = col.x;
mVtxProjected[i].Colour.y = col.y;
mVtxProjected[i].Colour.z = col.z;
mVtxProjected[i].Colour.w = vert.rgba_a * (1.0f / 255.0f);
// ENV MAPPING
//
if ( mTnL.Flags.TexGen )
{
// Update texture coords n.b. need to divide tu/tv by bogus scale on addition to buffer
// If the vert is already lit, then there is no normal (and hence we can't generate tex coord)
#if 1 // 1->Lets use mat_world_project instead of mat_world for nicer effect (see SSV space ship) //Corn
vecTransformedNormal = mat_world_project.TransformNormal( model_normal );
vecTransformedNormal.Normalise();
#endif
const v3 & norm = vecTransformedNormal;
if( mTnL.Flags.TexGenLin )
{
mVtxProjected[i].Texture.x = 0.5f * ( 1.0f + norm.x );
mVtxProjected[i].Texture.y = 0.5f * ( 1.0f + norm.y );
}
else
{
//Cheap way to do Acos(x)/Pi (abs() fixes star in SM64, sort of) //Corn
f32 NormX = fabsf( norm.x );
f32 NormY = fabsf( norm.y );
mVtxProjected[i].Texture.x = 0.5f - 0.25f * NormX - 0.25f * NormX * NormX * NormX;
mVtxProjected[i].Texture.y = 0.5f - 0.25f * NormY - 0.25f * NormY * NormY * NormY;
}
}
else
{
//Set Texture coordinates
mVtxProjected[i].Texture.x = (float)vert.tu * mTnL.TextureScaleX;
mVtxProjected[i].Texture.y = (float)vert.tv * mTnL.TextureScaleY;
}
}
else
{
//if( mTnL.Flags.Shade )
{// FLAT shade
mVtxProjected[i].Colour = v4( vert.rgba_r * (1.0f / 255.0f), vert.rgba_g * (1.0f / 255.0f), vert.rgba_b * (1.0f / 255.0f), vert.rgba_a * (1.0f / 255.0f) );
}
/*else
{// PRIM shade, SSV uses this, doesn't seem to do anything????
mVtxProjected[i].Colour = mPrimitiveColour.GetColourV4();
}*/
//Set Texture coordinates
mVtxProjected[i].Texture.x = (float)vert.tu * mTnL.TextureScaleX;
mVtxProjected[i].Texture.y = (float)vert.tv * mTnL.TextureScaleY;
}
#ifdef DAEDALUS_PSP
//Fog
if ( mTnL.Flags.Fog )
{
if(projected.w > 0.0f) //checking for positive w fixes near plane fog errors //Corn
{
f32 eye_z = projected.z / projected.w;
f32 fog_alpha = eye_z * mTnL.FogMult + mTnL.FogOffs;
//f32 fog_alpha = eye_z * 20.0f - 19.0f; //Fog test line
mVtxProjected[i].Colour.w = std::clamp< f32 >( fog_alpha, 0.0f, 1.0f );
}
else
{
mVtxProjected[i].Colour.w = 0.0f;
}
}
#endif // DAEDALUS_PSP
}
#endif // DAEDALUS_PSP_USE_VFPU
}
//*****************************************************************************
// Conker Bad Fur Day rendering pipeline
//*****************************************************************************
void BaseRenderer::SetNewVertexInfoConker(u32 address, u32 v0, u32 n)
{
const Matrix4x4 & mat_project = mProjectionMat;
const Matrix4x4 & mat_world = mModelViewStack[mModelViewTop];
DL_PF( " Ambient color RGB[%f][%f][%f] Texture scale X[%f] Texture scale Y[%f]", mTnL.Lights[mTnL.NumLights].Colour.x, mTnL.Lights[mTnL.NumLights].Colour.y, mTnL.Lights[mTnL.NumLights].Colour.z, mTnL.TextureScaleX, mTnL.TextureScaleY);
DL_PF( " Light[%s] Texture[%s] EnvMap[%s] Fog[%s]", (mTnL.Flags.Light)? "On":"Off", (mTnL.Flags.Texture)? "On":"Off", (mTnL.Flags.TexGen)? (mTnL.Flags.TexGenLin)? "Linear":"Spherical":"Off", (mTnL.Flags.Fog)? "On":"Off");
//Model normal base vector
const s8 *mn = (const s8*)(g_pu8RamBase + gAuxAddr);
const FiddledVtx * pVtxBase = (const FiddledVtx*)(g_pu8RamBase + address);
#ifdef DAEDALUS_PSP_USE_VFPU
_TnLVFPUCBFD( &mat_world, &mat_project, pVtxBase, &mVtxProjected[v0], n, &mTnL, mn, v0<<1 );
#else
// Transform and Project + Lighting or Transform and Project with Colour
//
for (u32 i = v0; i < v0 + n; i++)
{
const FiddledVtx & vert = pVtxBase[i - v0];
// VTX Transform
//
v4 w( f32( vert.x ), f32( vert.y ), f32( vert.z ), 1.0f );
v4 & transformed( mVtxProjected[i].TransformedPos );
transformed = mat_world.Transform( w );
v4 & projected( mVtxProjected[i].ProjectedPos );
projected = mat_project.Transform( transformed );
// Initialise the clipping flags
//
mVtxProjected[i].ClipFlags = set_clip_flags( projected );
mVtxProjected[i].Colour.x = (f32)vert.rgba_r * (1.0f / 255.0f);
mVtxProjected[i].Colour.y = (f32)vert.rgba_g * (1.0f / 255.0f);
mVtxProjected[i].Colour.z = (f32)vert.rgba_b * (1.0f / 255.0f);
mVtxProjected[i].Colour.w = (f32)vert.rgba_a * (1.0f / 255.0f); //Pass alpha channel unmodified
// LIGHTING OR COLOR
//
if ( mTnL.Flags.Light )
{
v3 model_normal( mn[((i<<1)+0)^3], mn[((i<<1)+1)^3], vert.normz );
v3 vecTransformedNormal = mat_world.TransformNormal( model_normal );
vecTransformedNormal.Normalise();
const v3 & norm = vecTransformedNormal;
const v3 & col = mTnL.Lights[mTnL.NumLights].Colour;
v4 Pos;
Pos.x = (projected.x + mTnL.CoordMod[8]) * mTnL.CoordMod[12];
Pos.y = (projected.y + mTnL.CoordMod[9]) * mTnL.CoordMod[13];
Pos.z = (projected.z + mTnL.CoordMod[10])* mTnL.CoordMod[14];
Pos.w = (projected.w + mTnL.CoordMod[11])* mTnL.CoordMod[15];
v3 result( col.x, col.y, col.z );
f32 fCosT;
u32 l;
if ( mTnL.Flags.PointLight )
{ //POINT LIGHT
for (l = 0; l < mTnL.NumLights-1; l++)
{
if ( mTnL.Lights[l].SkipIfZero )
{
fCosT = norm.Dot( mTnL.Lights[l].Direction );
if (fCosT > 0.0f)
{
f32 pi = mTnL.Lights[l].Iscale / (Pos - mTnL.Lights[l].Position).LengthSq();
if (pi < 1.0f) fCosT *= pi;
result.x += mTnL.Lights[l].Colour.x * fCosT;
result.y += mTnL.Lights[l].Colour.y * fCosT;
result.z += mTnL.Lights[l].Colour.z * fCosT;
}
}
}
fCosT = norm.Dot( mTnL.Lights[l].Direction );
if (fCosT > 0.0f)
{
result.x += mTnL.Lights[l].Colour.x * fCosT;
result.y += mTnL.Lights[l].Colour.y * fCosT;
result.z += mTnL.Lights[l].Colour.z * fCosT;
}
}
else
{ //NORMAL LIGHT
for (l = 0; l < mTnL.NumLights; l++)
{
if ( mTnL.Lights[l].SkipIfZero )
{
f32 pi = mTnL.Lights[l].Iscale / (Pos - mTnL.Lights[l].Position).LengthSq();
if (pi > 1.0f) pi = 1.0f;
result.x += mTnL.Lights[l].Colour.x * pi;
result.y += mTnL.Lights[l].Colour.y * pi;
result.z += mTnL.Lights[l].Colour.z * pi;
}
}
}
//Clamp result to 1.0
if( result.x < 1.0f ) mVtxProjected[i].Colour.x *= result.x;
if( result.y < 1.0f ) mVtxProjected[i].Colour.y *= result.y;
if( result.z < 1.0f ) mVtxProjected[i].Colour.z *= result.z;
// ENV MAPPING
if ( mTnL.Flags.TexGen )
{
if( mTnL.Flags.TexGenLin )
{
mVtxProjected[i].Texture.x = 0.5f - 0.25f * norm.x - 0.25f * norm.x * norm.x * norm.x; //Cheap way to do ~Acos(x)/Pi //Corn
mVtxProjected[i].Texture.y = 0.5f - 0.25f * norm.y - 0.25f * norm.y * norm.y * norm.y;
}
else
{
mVtxProjected[i].Texture.x = 0.5f * ( 1.0f + norm.x );
mVtxProjected[i].Texture.y = 0.5f * ( 1.0f + norm.y );
}
}
else
{ //TEXTURE SCALE
mVtxProjected[i].Texture.x = (f32)vert.tu * mTnL.TextureScaleX;
mVtxProjected[i].Texture.y = (f32)vert.tv * mTnL.TextureScaleY;
}
}
else
{ //TEXTURE SCALE
mVtxProjected[i].Texture.x = (f32)vert.tu * mTnL.TextureScaleX;
mVtxProjected[i].Texture.y = (f32)vert.tv * mTnL.TextureScaleY;
}
}
#endif
}
//*****************************************************************************
// DKR/Jet Force Gemini rendering pipeline
//*****************************************************************************
void BaseRenderer::SetNewVertexInfoDKR(u32 address, u32 v0, u32 n, bool billboard)
{
const Matrix4x4 & mat_world_project = mModelViewStack[mDKRMatIdx];
DL_PF( " Ambient color RGB[%f][%f][%f] Texture scale X[%f] Texture scale Y[%f]", mTnL.Lights[mTnL.NumLights].Colour.x, mTnL.Lights[mTnL.NumLights].Colour.y, mTnL.Lights[mTnL.NumLights].Colour.z, mTnL.TextureScaleX, mTnL.TextureScaleY);
DL_PF( " Light[%s] Texture[%s] EnvMap[%s] Fog[%s]", (mTnL.Flags.Light)? "On":"Off", (mTnL.Flags.Texture)? "On":"Off", (mTnL.Flags.TexGen)? (mTnL.Flags.TexGenLin)? "Linear":"Spherical":"Off", (mTnL.Flags.Fog)? "On":"Off");
DL_PF( " CMtx[%d] Add base[%s]", mDKRMatIdx, billboard? "On":"Off");
uintptr_t pVtxBase = reinterpret_cast<uintptr_t>(g_pu8RamBase + address);
if( billboard )
{
//Copy vertices adding base vector and the color data
mWPmodified = false;
#ifdef DAEDALUS_PSP_USE_VFPU
_TnLVFPUDKRB( n, &mModelViewStack[0], (const FiddledVtx*)pVtxBase, &mVtxProjected[v0] );
#else
v4 & BaseVec( mVtxProjected[0].TransformedPos );
//Hack to worldproj matrix to scale and rotate billbords //Corn
Matrix4x4 mat( mModelViewStack[0]);
mat.mRaw[0] *= mModelViewStack[2].mRaw[0] * 0.5f;
mat.mRaw[4] *= mModelViewStack[2].mRaw[0] * 0.5f;
mat.mRaw[8] *= mModelViewStack[2].mRaw[0] * 0.5f;
mat.mRaw[1] *= mModelViewStack[2].mRaw[0] * 0.375f;
mat.mRaw[5] *= mModelViewStack[2].mRaw[0] * 0.375f;
mat.mRaw[9] *= mModelViewStack[2].mRaw[0] * 0.375f;
mat.mRaw[2] *= mModelViewStack[2].mRaw[10] * 0.5f;
mat.mRaw[6] *= mModelViewStack[2].mRaw[10] * 0.5f;
mat.mRaw[10] *= mModelViewStack[2].mRaw[10] * 0.5f;
for (u32 i = v0; i < v0 + n; i++)
{
v3 w;
w.x = *(s16*)((pVtxBase + 0) ^ 2);
w.y = *(s16*)((pVtxBase + 2) ^ 2);
w.z = *(s16*)((pVtxBase + 4) ^ 2);
w = mat.TransformNormal( w );
v4 & transformed( mVtxProjected[i].TransformedPos );
transformed.x = BaseVec.x + w.x;
transformed.y = BaseVec.y + w.y;
transformed.z = BaseVec.z + w.z;
transformed.w = 1.0f;
// Set Clipflags, zero clippflags if billbording //Corn
mVtxProjected[i].ClipFlags = 0;
// Assign true vert colour
const u32 WL = *(u16*)((pVtxBase + 6) ^ 2);
const u32 WH = *(u16*)((pVtxBase + 8) ^ 2);
mVtxProjected[i].Colour.x = (1.0f / 255.0f) * (WL >> 8);
mVtxProjected[i].Colour.y = (1.0f / 255.0f) * (WL & 0xFF);
mVtxProjected[i].Colour.z = (1.0f / 255.0f) * (WH >> 8);
mVtxProjected[i].Colour.w = (1.0f / 255.0f) * (WH & 0xFF);
pVtxBase += 10;
}
#endif
}
else
{
//Normal path for transform of triangles
if( mWPmodified )
{
//Only reload matrix if it has been changed and no billbording //Corn
mWPmodified = false;
sceGuSetMatrix( GU_PROJECTION, reinterpret_cast< const ScePspFMatrix4 * >( &mat_world_project) );
}
#ifdef DAEDALUS_PSP_USE_VFPU
_TnLVFPUDKR( n, &mat_world_project, (const FiddledVtx*)pVtxBase, &mVtxProjected[v0] );
#else
for (u32 i = v0; i < v0 + n; i++)
{
v4 & transformed( mVtxProjected[i].TransformedPos );
transformed.x = *(s16*)((pVtxBase + 0) ^ 2);
transformed.y = *(s16*)((pVtxBase + 2) ^ 2);
transformed.z = *(s16*)((pVtxBase + 4) ^ 2);
transformed.w = 1.0f;
v4 & projected( mVtxProjected[i].ProjectedPos );
projected = mat_world_project.Transform( transformed ); //Do projection
// Set Clipflags
mVtxProjected[i].ClipFlags = set_clip_flags( projected );
// Assign true vert colour
const u32 WL = *(u16*)((pVtxBase + 6) ^ 2);
const u32 WH = *(u16*)((pVtxBase + 8) ^ 2);
mVtxProjected[i].Colour.x = (1.0f / 255.0f) * (WL >> 8);
mVtxProjected[i].Colour.y = (1.0f / 255.0f) * (WL & 0xFF);
mVtxProjected[i].Colour.z = (1.0f / 255.0f) * (WH >> 8);
mVtxProjected[i].Colour.w = (1.0f / 255.0f) * (WH & 0xFF);
pVtxBase += 10;
}
#endif
}
}
//*****************************************************************************
// Perfect Dark rendering pipeline
//*****************************************************************************
void BaseRenderer::SetNewVertexInfoPD(u32 address, u32 v0, u32 n)
{
const Matrix4x4 & mat_world = mModelViewStack[mModelViewTop];
const Matrix4x4 & mat_project = mProjectionMat;
DL_PF( " Ambient color RGB[%f][%f][%f] Texture scale X[%f] Texture scale Y[%f]", mTnL.Lights[mTnL.NumLights].Colour.x, mTnL.Lights[mTnL.NumLights].Colour.y, mTnL.Lights[mTnL.NumLights].Colour.z, mTnL.TextureScaleX, mTnL.TextureScaleY);
DL_PF( " Light[%s] Texture[%s] EnvMap[%s] Fog[%s]", (mTnL.Flags.Light)? "On":"Off", (mTnL.Flags.Texture)? "On":"Off", (mTnL.Flags.TexGen)? (mTnL.Flags.TexGenLin)? "Linear":"Spherical":"Off", (mTnL.Flags.Fog)? "On":"Off");
//Model normal and color base vector
const u8 *mn = (const u8*)(g_pu8RamBase + gAuxAddr);
const FiddledVtxPD * const pVtxBase = (const FiddledVtxPD*)(g_pu8RamBase + address);
#ifdef DAEDALUS_PSP_USE_VFPU
_TnLVFPUPD( &mat_world, &mat_project, pVtxBase, &mVtxProjected[v0], n, &mTnL, mn );
#else
for (u32 i = v0; i < v0 + n; i++)
{
const FiddledVtxPD & vert = pVtxBase[i - v0];
v4 w( f32( vert.x ), f32( vert.y ), f32( vert.z ), 1.0f );
// VTX Transform
//
v4 & transformed( mVtxProjected[i].TransformedPos );
transformed = mat_world.Transform( w );
v4 & projected( mVtxProjected[i].ProjectedPos );
projected = mat_project.Transform( transformed );
// Set Clipflags //Corn
mVtxProjected[i].ClipFlags = set_clip_flags( projected );
if( mTnL.Flags.Light )
{
v3 model_normal((f32)mn[vert.cidx+3], (f32)mn[vert.cidx+2], (f32)mn[vert.cidx+1] );
v3 vecTransformedNormal;
vecTransformedNormal = mat_world.TransformNormal( model_normal );
vecTransformedNormal.Normalise();
const v3 col = LightVert(vecTransformedNormal);
mVtxProjected[i].Colour.x = col.x;
mVtxProjected[i].Colour.y = col.y;
mVtxProjected[i].Colour.z = col.z;
mVtxProjected[i].Colour.w = (f32)mn[vert.cidx+0] * (1.0f / 255.0f);
if ( mTnL.Flags.TexGen )
{
const v3 & norm = vecTransformedNormal;
//Env mapping
if( mTnL.Flags.TexGenLin )
{ //Cheap way to do Acos(x)/Pi //Corn
mVtxProjected[i].Texture.x = 0.5f - 0.25f * norm.x - 0.25f * norm.x * norm.x * norm.x;
mVtxProjected[i].Texture.y = 0.5f - 0.25f * norm.y - 0.25f * norm.y * norm.y * norm.y;
}
else
{
mVtxProjected[i].Texture.x = 0.5f * ( 1.0f + norm.x );
mVtxProjected[i].Texture.y = 0.5f * ( 1.0f + norm.y );
}
}
else
{
mVtxProjected[i].Texture.x = (float)vert.tu * mTnL.TextureScaleX;
mVtxProjected[i].Texture.y = (float)vert.tv * mTnL.TextureScaleY;
}
}
else
{
mVtxProjected[i].Colour.x = (f32)mn[vert.cidx+3] * (1.0f / 255.0f);
mVtxProjected[i].Colour.y = (f32)mn[vert.cidx+2] * (1.0f / 255.0f);
mVtxProjected[i].Colour.z = (f32)mn[vert.cidx+1] * (1.0f / 255.0f);
mVtxProjected[i].Colour.w = (f32)mn[vert.cidx+0] * (1.0f / 255.0f);
mVtxProjected[i].Texture.x = (float)vert.tu * mTnL.TextureScaleX;
mVtxProjected[i].Texture.y = (float)vert.tv * mTnL.TextureScaleY;
}
}
#endif
}
//*****************************************************************************
//
//*****************************************************************************
void BaseRenderer::ModifyVertexInfo(u32 whered, u32 vert, u32 val)
{
if (vert >= kMaxN64Vertices)
{
DAEDALUS_ERROR("Vertex index is out of bounds (%d)", vert);
return;
}
switch ( whered )
{
case G_MWO_POINT_RGBA:
{
DL_PF(" Setting RGBA to 0x%08x", val);
SetVtxColor( vert, val );
}
break;
case G_MWO_POINT_ST:
{
s16 tu = s16(val >> 16);
s16 tv = s16(val & 0xFFFF);
DL_PF( " Setting tu/tv to %f, %f", tu/32.0f, tv/32.0f );
SetVtxTextureCoord( vert, tu, tv );
}
break;
case G_MWO_POINT_XYSCREEN:
{
if( g_ROM.GameHacks == TARZAN ) return;
s16 x = (u16)(val >> 16) >> 2;
s16 y = (u16)(val & 0xFFFF) >> 2;
// Fixes the blocks lining up backwards in New Tetris
//
x -= uViWidth / 2;
y = uViHeight / 2 - y;
DL_PF(" Modify vert %d: x=%d, y=%d", vert, x, y);
// Megaman and other games
SetVtxXY( vert, f32(x<<1) / fViWidth, f32(y<<1) / fViHeight );
}
break;
case G_MWO_POINT_ZSCREEN:
{
//s32 z = val >> 16;
//DL_PF( " Setting ZScreen to 0x%08x", z );
DL_PF( " Setting ZScreen");
//Not sure about the scaling here //Corn
//SetVtxZ( vert, (( (f32)z / 0x03FF ) + 0.5f ) / 2.0f );
//SetVtxZ( vert, (( (f32)z ) + 0.5f ) / 2.0f );
}
break;
default:
DBGConsole_Msg( 0, "Unknown ModifyVtx - Setting vert data where: 0x%02x, vert: 0x%08x, val: 0x%08x", whered, vert, val );
DL_PF( " Setting unknown value: where: 0x%02x, vert: 0x%08x, val: 0x%08x", whered, vert, val );
break;
}
}
//*****************************************************************************
//
//*****************************************************************************
inline void BaseRenderer::SetVtxColor( u32 vert, u32 color )
{
u8 r = (color>>24)&0xFF;
u8 g = (color>>16)&0xFF;
u8 b = (color>>8)&0xFF;
u8 a = color&0xFF;
mVtxProjected[vert].Colour = v4( r * (1.0f / 255.0f), g * (1.0f / 255.0f), b * (1.0f / 255.0f), a * (1.0f / 255.0f) );
}
//*****************************************************************************
//
//*****************************************************************************
/*
inline void BaseRenderer::SetVtxZ( u32 vert, float z )
{
DAEDALUS_ASSERT( vert < kMaxN64Vertices, "Vertex index is out of bounds (%d)", vert );
mVtxProjected[vert].TransformedPos.z = z;
}
*/
//*****************************************************************************
//
//*****************************************************************************
inline void BaseRenderer::SetVtxXY( u32 vert, f32 x, f32 y )
{
mVtxProjected[vert].TransformedPos.x = x;
mVtxProjected[vert].TransformedPos.y = y;
}
//*****************************************************************************
// Init matrix stack to identity matrices (called once per frame)
//*****************************************************************************
void BaseRenderer::ResetMatrices(u32 size)
{
//Tigger's Honey Hunt and SSV does this...
if(size == 0 || size > MATRIX_STACK_SIZE)
size = MATRIX_STACK_SIZE;
mMatStackSize = size;
mModelViewTop = 0;
mProjectionMat = mModelViewStack[0] = gMatrixIdentity;
mWorldProjectValid = false;
}
//*****************************************************************************
//
//*****************************************************************************
void BaseRenderer::UpdateTileSnapshots( u32 tile_idx )
{
UpdateTileSnapshot( 0, tile_idx );
#if defined(DAEDALUS_PSP)
if ( g_ROM.LOAD_T1_HACK & !gRDPOtherMode.text_lod )
{
// LOD is disabled - use two textures
UpdateTileSnapshot( 1, tile_idx + 1 );
}
#elif defined(DAEDALUS_GL) || defined(RDP_USE_TEXEL1) || defined(DAEDALUS_CTR)
// FIXME(strmnnrmn): What's RDP_USE_TEXEL1? Can we remove it?
if (gRDPOtherMode.cycle_type == CYCLE_2CYCLE)
{
u32 t1_tile = (tile_idx + 1) & 7;
// NB: I don't think we need to do this. lod_frac is set to 0.0 in the
// OSX pixel shader, so it'll always use Texel 0 when mipmapping.
// LOD is enabled - use the highest detail texture in texel1
// if ( gRDPOtherMode.text_lod )
// t1_tile = tile_idx;
if ( !gRDPStateManager.IsTileInitialised(t1_tile) )
{
// FIXME(strmnnrmn): This happens a lot - not just for Tony Hawk.
// DAEDALUS_DL_ERROR("Using T1, but it's not been set up");
// FIXME(strmnnrmn): This is required so that Tony Hawk's text renders correctly.
// It's odd. It calls TexRect with tile 1, and has
// a color combiner that uses Texel 1 but not Texel 0.
// But tile 2 has never been initialised.
t1_tile = tile_idx;
}
UpdateTileSnapshot( 1, t1_tile );
}
#endif
}
#ifdef DAEDALUS_PSP
static void T1Hack(const TextureInfo & ti0, std::shared_ptr<CNativeTexture> texture0,
const TextureInfo & ti1, std::shared_ptr<CNativeTexture> texture1)
{
if((ti0.GetFormat() == G_IM_FMT_RGBA) &&
(ti1.GetFormat() == G_IM_FMT_I) &&
(ti1.GetWidth() == ti0.GetWidth()) &&
(ti1.GetHeight() == ti0.GetHeight()))
{
if( g_ROM.T1_HACK )
{
const u32 * src = static_cast<const u32*>(texture0->GetData());
u32 * dst = static_cast< u32*>(texture1->GetData());
//Merge RGB + I -> RGBA in texture 1
//We do two pixels in one go since its 16bit (RGBA_4444) //Corn
u32 size = texture1->GetWidth() * texture1->GetHeight() >> 1;
for(u32 i=0; i < size ; i++)
{
*dst = (*dst & 0xF000F000) | (*src & 0x0FFF0FFF);
dst++;
src++;
}
}
else
{
const u32* src = static_cast<const u32*>(texture1->GetData());
u32* dst = static_cast< u32*>(texture0->GetData());
//Merge RGB + I -> RGBA in texture 0
//We do two pixels in one go since its 16bit (RGBA_4444) //Corn
u32 size = texture1->GetWidth() * texture1->GetHeight() >> 1;
for(u32 i=0; i < size ; i++)
{
*dst = (*dst & 0x0FFF0FFF) | (*src & 0xF000F000);
dst++;
src++;
}
}
}
}
#endif // DAEDALUS_PSP
//*****************************************************************************
// This captures the state of the RDP tiles in:
// mTexWrap
// mTileTopLeft
// mBoundTexture
//*****************************************************************************
void BaseRenderer::UpdateTileSnapshot( u32 index, u32 tile_idx )
{
#ifdef DAEDALUS_ENABLE_PROFILING
DAEDALUS_PROFILE( "BaseRenderer::UpdateTileSnapshot" );
#endif
#ifdef DAEDALUS_ENABLE_ASSERTS
DAEDALUS_ASSERT( tile_idx < 8, "Invalid tile index %d", tile_idx );
DAEDALUS_ASSERT( index < kNumBoundTextures, "Invalid texture index %d", index );
#endif
// This hapens a lot! Even for index 0 (i.e. the main texture!)
// It might just be code that lazily does a texrect with Primcolour (i.e. not using either T0 or T1)?
// DAEDALUS_ASSERT( gRDPStateManager.IsTileInitialised( tile_idx ), "Tile %d hasn't been set up (index %d)", tile_idx, index );
const TextureInfo & ti = gRDPStateManager.GetUpdatedTextureDescriptor( tile_idx );
const RDP_Tile & rdp_tile = gRDPStateManager.GetTile( tile_idx );
const RDP_TileSize & tile_size = gRDPStateManager.GetTileSize( tile_idx );
// Avoid texture update, if texture is the same as last time around.
if( mBoundTexture[ index ] == NULL || mBoundTextureInfo[ index ] != ti )
{
// Check for 0 width/height textures
if( ti.GetWidth() == 0 || ti.GetHeight() == 0 )
{
#ifdef DAEDALUS_ENABLE_PROFILING
DAEDALUS_DL_ERROR( "Loading texture with bad width/height %dx%d in slot %d", ti.GetWidth(), ti.GetHeight(), index );
#endif
}
else
{
std::shared_ptr<CNativeTexture> texture = CTextureCache::Get()->GetOrCreateTexture( ti );
if( texture != NULL && texture != mBoundTexture[ index ] )
{
mBoundTextureInfo[index] = ti;
mBoundTexture[index] = texture;
#ifdef DAEDALUS_PSP
//If second texture is loaded try to merge two textures RGB(T0) + A(T1) into one RGBA(T1) //Corn
//If T1 Hack is not enabled index can never be other than 0
if(index)
{
T1Hack(mBoundTextureInfo[0], mBoundTexture[0], mBoundTextureInfo[1], mBoundTexture[1]);
}
#endif
}
}
}
// Initialise the clamping state. When the mask is 0, it forces clamp mode.
//
#ifdef DAEDALUS_PSP
u32 mode_u = (u32)((rdp_tile.clamp_s || (rdp_tile.mask_s == 0)) ? GU_CLAMP : GU_REPEAT);
u32 mode_v = (u32)((rdp_tile.clamp_t || (rdp_tile.mask_t == 0)) ? GU_CLAMP : GU_REPEAT);
#else
u32 mode_u = (u32)((rdp_tile.clamp_s || (rdp_tile.mask_s == 0)) ? GL_CLAMP : GL_REPEAT);
u32 mode_v = (u32)((rdp_tile.clamp_t || (rdp_tile.mask_t == 0)) ? GL_CLAMP : GL_REPEAT);
#endif
// In CRDPStateManager::GetTextureDescriptor, we limit the maximum dimension of a
// texture to that define by the mask_s/mask_t value.
// It this happens, the tile size can be larger than the truncated width/height
// as the rom can set clamp_s/clamp_t to wrap up to a certain value, then clamp.
// We can't support both wrapping and clamping (without manually repeating a texture...)
// so we choose to prefer wrapping.
// The castle in the background of the first SSB level is a good example of this behaviour.
// It sets up a texture with a mask_s/t of 6/6 (64x64), but sets the tile size to
// 256*128. clamp_s/t are set, meaning the texture wraps 4x and 2x.
//
if( tile_size.GetWidth() > ti.GetWidth() )
{
// This breaks the Sun, and other textures in Zelda. Breaks Mario's hat in SSB, and other textures, and foes in Kirby 64's cutscenes
// ToDo : Find a proper workaround for this, if this disabled the castle in Link's stage in SSB is broken :/
// Do a hack just for Zelda for now..
//
#ifdef DAEDALUS_PSP
mode_u = g_ROM.ZELDA_HACK ? GU_CLAMP : GU_REPEAT;
#else
mode_u = g_ROM.ZELDA_HACK ? GL_CLAMP : (rdp_tile.mirror_s ? GL_MIRRORED_REPEAT : GL_REPEAT);
#endif
}
if( tile_size.GetHeight() > ti.GetHeight() )
#ifdef DAEDALUS_PSP
mode_v = GU_REPEAT;
#else
mode_v = rdp_tile.mirror_t ? GL_MIRRORED_REPEAT : GL_REPEAT;
#endif
mTexWrap[ index ].u = mode_u;
mTexWrap[ index ].v = mode_v;
mTileTopLeft[ index ].s = tile_size.left;
mTileTopLeft[ index ].t = tile_size.top;
mActiveTile[ index ] = tile_idx;
#ifdef DAEDALUS_ENABLE_PROFILING
DL_PF( " Use Tile[%d] as Texture[%d] [%dx%d] [%s/%dbpp] [%s u, %s v] -> Adr[0x%08x] PAL[0x%x] Hash[0x%08x] Pitch[%d] TopLeft[%0.3f|%0.3f]",
tile_idx, index, ti.GetWidth(), ti.GetHeight(), ti.GetFormatName(), ti.GetSizeInBits(),
(mode_u==GU_CLAMP)? "Clamp" : "Repeat", (mode_v==GU_CLAMP)? "Clamp" : "Repeat",
ti.GetLoadAddress(), ti.GetTlutAddress(), ti.GetHashCode(), ti.GetPitch(),
mTileTopLeft[ index ].s / 4.f, mTileTopLeft[ index ].t / 4.f );
#endif
}
// This transforms UVs so that they're positive. The aim is to ensure UVs are in the
// range [(0,0),(w,h)]. If we can do this, we can specify GL_CLAMP_TO_EDGE/GU_CLAMP,
// which fixes some artifacts when rendering, such as bleed from wrapping at the edges
// of textures. E.g. http://imgur.com/db3Adws,dX9vOWE#1
// There are two inputs into the final uvs: the vertex UV and the mTileTopLeft value:
// final_uv = (vert_uv - mTileTopLeft).
// When rendering a large logo, most games set uv0=(s,t) and mTileTopLeft=(s,t) so
// that the resulting final_uv = (0,0). But some games (e.g. Automobili Lamborghini)
// set uv0=(0,0) but still have mTileTopLeft=(s,t). This results in a final_uv of (-s,-t).
// I think that the only reason this happened to work was because s was some multiple
// of the texture width, and so with GL_REPEAT the texrect rendered ok.
// Anyway the fix is to subtract mTileTopLeft from the uvs, zero it, then add multiples
// of the texture width/height until the uvs are positive. Then if the resulting UVs
// are in the range [(0,0),(w,h)] we can update mTexWrap to GL_CLAMP_TO_EDGE/GU_CLAMP
// and everything works correctly.
inline void FixUV(u32 * wrap, s16 * c0_, s16 * c1_, s16 offset, u32 size)
{
DAEDALUS_ASSERT(size > 0, "Texture has crazy width/height: %d", size);
s16 offset_10_5 = offset << 3;
s16 c0 = *c0_ - offset_10_5;
s16 c1 = *c1_ - offset_10_5;
// Many texrects already have GU_CLAMP set, so avoid some work.
#ifdef DAEDALUS_PSP
if (*wrap != GU_CLAMP && size > 0)
#else
if (*wrap != GL_CLAMP && size > 0)
#endif
{
// Check if the coord is negative - if so, offset to the range [0,size]
if (c0 < 0)
{
s16 lowest = std::min(c0, c1);
// Figure out by how much to translate so that the lowest of c0/c1 lies in the range [0,size]
// If we do lowest%size, we run the risk of implementation dependent behaviour for modulo of negative values.
// lowest + (size<<16) just adds a large multiple of size, which guarantees the result is positive.
s16 trans = (s16)(((s32)lowest + (size<<16)) % size) - lowest;
// NB! we have to apply the same offset to both coords, to preserve direction of mapping (i.e., don't clamp each independently)
c0 += trans;
c1 += trans;
}
// If both coords are in the range [0,size], we can clamp safely.
if ((u16)c0 <= size &&
(u16)c1 <= size)
{
#ifdef DAEDALUS_PSP
*wrap = GU_CLAMP;
#else
*wrap = GL_CLAMP;
#endif
}
}
*c0_ = c0;
*c1_ = c1;
}
// puv0, puv1 are in/out arguments.
void BaseRenderer::PrepareTexRectUVs(TexCoord * puv0, TexCoord * puv1)
{
const RDP_Tile & rdp_tile = gRDPStateManager.GetTile( mActiveTile[0] );
TexCoord offset = mTileTopLeft[0];
u32 size_x = mBoundTextureInfo[0].GetWidth() << 5;
u32 size_y = mBoundTextureInfo[0].GetHeight() << 5;
// If mirroring, we need to scroll twice as far to line up.
if (rdp_tile.mirror_s) size_x *= 2;
if (rdp_tile.mirror_t) size_y *= 2;
#ifdef DAEDALUS_GL
// If using shift, we need to take it into account here.
offset.s = ApplyShift(offset.s, rdp_tile.shift_s);
offset.t = ApplyShift(offset.t, rdp_tile.shift_t);
size_x = ApplyShift(size_x, rdp_tile.shift_s);
size_y = ApplyShift(size_y, rdp_tile.shift_t);
#endif
FixUV(&mTexWrap[0].u, &puv0->s, &puv1->s, offset.s, size_x);
FixUV(&mTexWrap[0].v, &puv0->t, &puv1->t, offset.t, size_y);
mTileTopLeft[0].s = 0;
mTileTopLeft[0].t = 0;
}
//*****************************************************************************
//
//*****************************************************************************
std::shared_ptr<CNativeTexture> BaseRenderer::LoadTextureDirectly( const TextureInfo & ti )
{
std::shared_ptr<CNativeTexture> texture = CTextureCache::Get()->GetOrCreateTexture( ti );
if (texture)
{
texture->InstallTexture();
}
else
{
DAEDALUS_ERROR("Texture is null");
}
mBoundTexture[0] = texture;
mBoundTextureInfo[0] = ti;
return texture;
}
//*****************************************************************************
//
//*****************************************************************************
void BaseRenderer::SetScissor( u32 x0, u32 y0, u32 x1, u32 y1 )
{
//Clamp scissor to max N64 screen resolution //Corn
if( x1 > uViWidth ) x1 = uViWidth;
if( y1 > uViHeight ) y1 = uViHeight;
v2 n64_tl( (f32)x0, (f32)y0 );
v2 n64_br( (f32)x1, (f32)y1 );
v2 screen_tl;
v2 screen_br;
ConvertN64ToScreen( n64_tl, screen_tl );
ConvertN64ToScreen( n64_br, screen_br );
//Clamp TOP and LEFT values to 0 if < 0 , needed for zooming //Corn
s32 l = std::max<s32>( s32(screen_tl.x), 0 );
s32 t = std::max<s32>( s32(screen_tl.y), 0 );
s32 r = s32(screen_br.x);
s32 b = s32(screen_br.y);
#if defined(DAEDALUS_PSP)
// N.B. Think the arguments are x0,y0,x1,y1, and not x,y,w,h as the docs describe
//printf("%d %d %d %d\n", s32(screen_tl.x),s32(screen_tl.y),s32(screen_br.x),s32(screen_br.y));
sceGuScissor( l, t, r, b );
#elif defined(DAEDALUS_GL) || defined(DAEDALUS_CTR)
// NB: OpenGL is x,y,w,h. Errors if width or height is negative, so clamp this.
s32 w = std::max<s32>( r - l, 0 );
s32 h = std::max<s32>( b - t, 0 );
glScissor( l, (s32)mScreenHeight - (t + h), w, h );
#ifdef DAEDALUS_DEBUG_CONSOLE
#else
DAEDALUS_ERROR("Need to implement scissor for this platform.");
#endif
#endif
}
extern void MatrixFromN64FixedPoint( Matrix4x4 & mat, u32 address );
//*****************************************************************************
//
//*****************************************************************************
void BaseRenderer::SetProjection(const u32 address, bool bReplace)
{
// Projection
if (bReplace)
{
// Load projection matrix
MatrixFromN64FixedPoint( mProjectionMat, address);
//Hack needed to show heart in OOT & MM
//it renders at Z cordinate = 0.0f that gets clipped away.
//so we translate them a bit along Z to make them stick :) //Corn
//
if( g_ROM.ZELDA_HACK )
mProjectionMat.mRaw[14] += 0.4f;
if( gGlobalPreferences.ViewportType == VT_FULLSCREEN_HD )
mProjectionMat.mRaw[0] *= HD_SCALE; //proper 16:9 scale
}
else
{
MatrixFromN64FixedPoint( mTempMat, address);
MatrixMultiplyAligned( &mProjectionMat, &mTempMat, &mProjectionMat );
}
mWorldProjectValid = false;
sceGuSetMatrix( GU_PROJECTION, reinterpret_cast< const ScePspFMatrix4 * >( &mProjectionMat) );
#ifdef DAEDALUS_ENABLE_PROFILING
DL_PF(
" %#+12.5f %#+12.5f %#+12.7f %#+12.5f\n"
" %#+12.5f %#+12.5f %#+12.7f %#+12.5f\n"
" %#+12.5f %#+12.5f %#+12.7f %#+12.5f\n"
" %#+12.5f %#+12.5f %#+12.7f %#+12.5f\n",
mProjectionMat.m[0][0], mProjectionMat.m[0][1], mProjectionMat.m[0][2], mProjectionMat.m[0][3],
mProjectionMat.m[1][0], mProjectionMat.m[1][1], mProjectionMat.m[1][2], mProjectionMat.m[1][3],
mProjectionMat.m[2][0], mProjectionMat.m[2][1], mProjectionMat.m[2][2], mProjectionMat.m[2][3],
mProjectionMat.m[3][0], mProjectionMat.m[3][1], mProjectionMat.m[3][2], mProjectionMat.m[3][3]);
#endif
}
//*****************************************************************************
//
//*****************************************************************************
void BaseRenderer::SetDKRMat(const u32 address, bool mul, u32 idx)
{
mDKRMatIdx = idx;
mWPmodified = true;
if( mul )
{
MatrixFromN64FixedPoint( mTempMat, address );
MatrixMultiplyAligned( &mModelViewStack[idx], &mTempMat, &mModelViewStack[0] );
}
else
{
MatrixFromN64FixedPoint( mModelViewStack[idx], address );
}
#ifdef DAEDALUS_DEBUG_DISPLAYLIST
const Matrix4x4 & mtx( mModelViewStack[idx] );
DL_PF(" Mtx_DKR: Index %d %s Address 0x%08x\n"
" %#+12.5f %#+12.5f %#+12.5f %#+12.5f\n"
" %#+12.5f %#+12.5f %#+12.5f %#+12.5f\n"
" %#+12.5f %#+12.5f %#+12.5f %#+12.5f\n"
" %#+12.5f %#+12.5f %#+12.5f %#+12.5f\n",
idx, mul ? "Mul" : "Load", address,
mtx.m[0][0], mtx.m[0][1], mtx.m[0][2], mtx.m[0][3],
mtx.m[1][0], mtx.m[1][1], mtx.m[1][2], mtx.m[1][3],
mtx.m[2][0], mtx.m[2][1], mtx.m[2][2], mtx.m[2][3],
mtx.m[3][0], mtx.m[3][1], mtx.m[3][2], mtx.m[3][3]);
#endif
}
//*****************************************************************************
//
//*****************************************************************************
void BaseRenderer::SetWorldView(const u32 address, bool bPush, bool bReplace)
{
// ModelView
if (bPush && (mModelViewTop < mMatStackSize))
{
++mModelViewTop;
// We should store the current projection matrix...
if (bReplace)
{
// Load ModelView matrix
MatrixFromN64FixedPoint( mModelViewStack[mModelViewTop], address);
//Hack to make GEX games work, need to multiply all elements with 2.0 //Corn
if( g_ROM.GameHacks == GEX_GECKO ) for(u32 i=0;i<16;i++) mModelViewStack[mModelViewTop].mRaw[i] += mModelViewStack[mModelViewTop].mRaw[i];
}
else // Multiply ModelView matrix
{
MatrixFromN64FixedPoint( mTempMat, address);
MatrixMultiplyAligned( &mModelViewStack[mModelViewTop], &mTempMat, &mModelViewStack[mModelViewTop-1] );
}
}
else // NoPush
{
if (bReplace)
{
// Load ModelView matrix
MatrixFromN64FixedPoint( mModelViewStack[mModelViewTop], address);
}
else
{
// Multiply ModelView matrix
MatrixFromN64FixedPoint( mTempMat, address);
MatrixMultiplyAligned( &mModelViewStack[mModelViewTop], &mTempMat, &mModelViewStack[mModelViewTop] );
}
}
mWorldProjectValid = false;
#ifdef DAEDALUS_ENABLE_PROFILING
DL_PF(" Level = %d\n"
" %#+12.5f %#+12.5f %#+12.5f %#+12.5f\n"
" %#+12.5f %#+12.5f %#+12.5f %#+12.5f\n"
" %#+12.5f %#+12.5f %#+12.5f %#+12.5f\n"
" %#+12.5f %#+12.5f %#+12.5f %#+12.5f\n",
mModelViewTop,
mModelViewStack[mModelViewTop].m[0][0], mModelViewStack[mModelViewTop].m[0][1], mModelViewStack[mModelViewTop].m[0][2], mModelViewStack[mModelViewTop].m[0][3],
mModelViewStack[mModelViewTop].m[1][0], mModelViewStack[mModelViewTop].m[1][1], mModelViewStack[mModelViewTop].m[1][2], mModelViewStack[mModelViewTop].m[1][3],
mModelViewStack[mModelViewTop].m[2][0], mModelViewStack[mModelViewTop].m[2][1], mModelViewStack[mModelViewTop].m[2][2], mModelViewStack[mModelViewTop].m[2][3],
mModelViewStack[mModelViewTop].m[3][0], mModelViewStack[mModelViewTop].m[3][1], mModelViewStack[mModelViewTop].m[3][2], mModelViewStack[mModelViewTop].m[3][3]);
#endif
}
//*****************************************************************************
//
//*****************************************************************************
inline void BaseRenderer::UpdateWorldProject()
{
if( !mWorldProjectValid )
{
mWorldProjectValid = true;
if( mReloadProj )
{
mReloadProj = false;
sceGuSetMatrix( GU_PROJECTION, reinterpret_cast< const ScePspFMatrix4 * >( &mProjectionMat) );
}
MatrixMultiplyAligned( &mWorldProject, &mModelViewStack[mModelViewTop], &mProjectionMat );
}
//If WoldProjectmatrix has been modified due to insert or force matrix (Kirby, SSB / Tarzan, Rayman2, Donald duck, SW racer, Robot on wheels)
//We need to also pdate sceGU projmtx //Corn
if( mWPmodified )
{
mWPmodified = false;
mReloadProj = true;
//proper 16:9 scale
if( gGlobalPreferences.ViewportType == VT_FULLSCREEN_HD )
{
mWorldProject.mRaw[0] *= HD_SCALE;
mWorldProject.mRaw[4] *= HD_SCALE;
mWorldProject.mRaw[8] *= HD_SCALE;
mWorldProject.mRaw[12] *= HD_SCALE;
}
sceGuSetMatrix( GU_PROJECTION, reinterpret_cast< const ScePspFMatrix4 * >( &mWorldProject ) );
mModelViewStack[mModelViewTop] = gMatrixIdentity;
}
}
//*****************************************************************************
//
//*****************************************************************************
#ifdef DAEDALUS_DEBUG_DISPLAYLIST
void BaseRenderer::PrintActive()
{
UpdateWorldProject();
const Matrix4x4 & mat = mWorldProject;
DL_PF(
" %#+12.5f %#+12.5f %#+12.5f %#+12.5f\n"
" %#+12.5f %#+12.5f %#+12.5f %#+12.5f\n"
" %#+12.5f %#+12.5f %#+12.5f %#+12.5f\n"
" %#+12.5f %#+12.5f %#+12.5f %#+12.5f\n",
mat.m[0][0], mat.m[0][1], mat.m[0][2], mat.m[0][3],
mat.m[1][0], mat.m[1][1], mat.m[1][2], mat.m[1][3],
mat.m[2][0], mat.m[2][1], mat.m[2][2], mat.m[2][3],
mat.m[3][0], mat.m[3][1], mat.m[3][2], mat.m[3][3]);
}
#endif
//*****************************************************************************
//Modify the WorldProject matrix, used by Kirby & SSB //Corn
//*****************************************************************************
void BaseRenderer::InsertMatrix(u32 w0, u32 w1)
{
mWPmodified = true; //Signal that Worldproject matrix is changed
//Make sure WP matrix is up to date before changing WP matrix
if( !mWorldProjectValid )
{
mWorldProject = mModelViewStack[mModelViewTop] * mProjectionMat;
mWorldProjectValid = true;
}
u32 x = (w0 & 0x1F) >> 1;
u32 y = x >> 2;
x &= 3;
if (w0 & 0x20)
{
//Change fraction part
mWorldProject.m[y][x] = (f32)(s32)mWorldProject.m[y][x] + ((f32)(w1 >> 16) / 65536.0f);
mWorldProject.m[y][x+1] = (f32)(s32)mWorldProject.m[y][x+1] + ((f32)(w1 & 0xFFFF) / 65536.0f);
}
else
{
//Change integer part
mWorldProject.m[y][x] = (f32)(s16)(w1 >> 16);
mWorldProject.m[y][x+1] = (f32)(s16)(w1 & 0xFFFF);
}
#ifdef DAEDALUS_ENABLE_PROFILING
DL_PF(
" %#+12.5f %#+12.5f %#+12.5f %#+12.5f\n"
" %#+12.5f %#+12.5f %#+12.5f %#+12.5f\n"
" %#+12.5f %#+12.5f %#+12.5f %#+12.5f\n"
" %#+12.5f %#+12.5f %#+12.5f %#+12.5f\n",
mWorldProject.m[0][0], mWorldProject.m[0][1], mWorldProject.m[0][2], mWorldProject.m[0][3],
mWorldProject.m[1][0], mWorldProject.m[1][1], mWorldProject.m[1][2], mWorldProject.m[1][3],
mWorldProject.m[2][0], mWorldProject.m[2][1], mWorldProject.m[2][2], mWorldProject.m[2][3],
mWorldProject.m[3][0], mWorldProject.m[3][1], mWorldProject.m[3][2], mWorldProject.m[3][3]);
#endif
}
//*****************************************************************************
//Replaces the WorldProject matrix //Corn
//*****************************************************************************
void BaseRenderer::ForceMatrix(const u32 address)
{
mWorldProjectValid = true;
mWPmodified = true; //Signal that Worldproject matrix is changed
MatrixFromN64FixedPoint( mWorldProject, address );
#ifdef DAEDALUS_ENABLE_PROFILING
DL_PF(
" %#+12.5f %#+12.5f %#+12.5f %#+12.5f\n"
" %#+12.5f %#+12.5f %#+12.5f %#+12.5f\n"
" %#+12.5f %#+12.5f %#+12.5f %#+12.5f\n"
" %#+12.5f %#+12.5f %#+12.5f %#+12.5f\n",
mWorldProject.m[0][0], mWorldProject.m[0][1], mWorldProject.m[0][2], mWorldProject.m[0][3],
mWorldProject.m[1][0], mWorldProject.m[1][1], mWorldProject.m[1][2], mWorldProject.m[1][3],
mWorldProject.m[2][0], mWorldProject.m[2][1], mWorldProject.m[2][2], mWorldProject.m[2][3],
mWorldProject.m[3][0], mWorldProject.m[3][1], mWorldProject.m[3][2], mWorldProject.m[3][3]);
#endif
}