/* 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 "Utility/MathUtil.h" #include "Ultra/ultra_gbi.h" #include "Ultra/ultra_os.h" // System type #include "Utility/Profiler.h" #include #include #include #ifdef DAEDALUS_PSP #include "SysPSP/Math/Math.h" #endif #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) || defined(DAEDALUS_GLES) free(Verts); #endif } DaedalusVtx * Alloc(u32 count) { u32 bytes = count * sizeof(DaedalusVtx); #ifdef DAEDALUS_PSP Verts = static_cast(sceGuGetMemory(bytes)); #endif #if defined(DAEDALUS_GL) || defined(DAEDALUS_CTR) || defined(DAEDALUS_GLES) Verts = static_cast(malloc(bytes)); #endif Count = count; return Verts; } DaedalusVtx * Verts; u32 Count; }; extern "C" { void _TnLVFPU( const glm::mat4 * world_matrix, const glm::mat4 * projection_matrix, const FiddledVtx * p_in, const DaedalusVtx4 * p_out, u32 num_vertices, const TnLParams * params ); void _TnLVFPU_Plight( const glm::mat4 * world_matrix, const glm::mat4 * projection_matrix, const FiddledVtx * p_in, const DaedalusVtx4 * p_out, u32 num_vertices, const TnLParams * params ); void _TnLVFPUDKR( u32 num_vertices, const glm::mat4 * projection_matrix, const FiddledVtx * p_in, const DaedalusVtx4 * p_out ); void _TnLVFPUDKRB( u32 num_vertices, const glm::mat4 * projection_matrix, const FiddledVtx * p_in, const DaedalusVtx4 * p_out ); void _TnLVFPUCBFD( const glm::mat4 * world_matrix, const glm::mat4 * 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 glm::mat4 * world_matrix, const glm::mat4 * 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 glm::vec4 * 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 = glm::vec2( 640.f*0.25f, 480.f*0.25f ); mVpTrans = glm::vec2( 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) || defined(DAEDALUS_GLES) f32 w = mScreenWidth; f32 h = mScreenHeight; mScreenToDevice = glm::mat4( 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 glm::vec2 & scale, const glm::vec2 & 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() { glm::vec2 n64_min( mVpTrans.x - mVpScale.x, mVpTrans.y - mVpScale.y ); glm::vec2 n64_max( mVpTrans.x + mVpScale.x, mVpTrans.y + mVpScale.y ); glm::vec2 psp_min; glm::vec2 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) || defined(DAEDALUS_GLES) 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 glm::vec4 & A = mVtxProjected[v0].ProjectedPos; const glm::vec4 & B = mVtxProjected[v1].ProjectedPos; const glm::vec4 & 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 NDCPlane = { glm::vec4( 0.f, 0.f, -1.f, -1.f ), // near glm::vec4( 0.f, 0.f, 1.f, -1.f ), // far glm::vec4( 1.f, 0.f, 0.f, -1.f ), // left glm::vec4( -1.f, 0.f, 0.f, -1.f ), // right glm::vec4( 0.f, 1.f, 0.f, -1.f ), // bottom glm::vec4( 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 glm::mat4 & 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 glm::vec4 &plane ) { u32 outCount(0); DaedalusVtx4 * out(dest); const DaedalusVtx4 * a; const DaedalusVtx4 * b(source); f32 bDotPlane = glm::dot(b->ProjectedPos, 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 = glm::dot(a->ProjectedPos, 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 / glm::dot((b->ProjectedPos - a->ProjectedPos), 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 / glm::dot((b->ProjectedPos - a->ProjectedPos), 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 glm::vec4 & 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 temp_a; // std::array 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 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 //***************************************************************************** // //***************************************************************************** glm::vec3 BaseRenderer::LightVert( const glm::vec3 & norm ) const { const glm::vec3 & col = mTnL.Lights[mTnL.NumLights].Colour; glm::vec3 result( col.x, col.y, col.z ); for ( u32 l = 0; l < mTnL.NumLights; l++ ) { f32 fCosT = glm::dot(mTnL.Lights[l].Direction, norm); 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; } //***************************************************************************** // //***************************************************************************** glm::vec3 BaseRenderer::LightPointVert( const glm::vec4 & w ) const { const glm::vec3 & col = mTnL.Lights[mTnL.NumLights].Colour; glm::vec3 result( col.x, col.y, col.z ); for ( u32 l = 0; l < mTnL.NumLights; l++ ) { if ( mTnL.Lights[l].SkipIfZero ) { glm::vec3 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 = glm::dot(distance_vec, distance_vec); 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(); alignas(DATA_ALIGN) const glm::mat4 & mat_world_project = mWorldProject; alignas(DATA_ALIGN) const glm::mat4 & 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 // glm::vec4 w( f32( vert.x ), f32( vert.y ), f32( vert.z ), 1.0f ); glm::vec4 & projected( mVtxProjected[i].ProjectedPos ); projected = mat_world_project * w; mVtxProjected[i].TransformedPos = mat_world * w; // Initialise the clipping flags // mVtxProjected[i].ClipFlags = set_clip_flags( projected ); // LIGHTING OR COLOR // if ( mTnL.Flags.Light ) { glm::vec3 model_normal(f32( vert.norm_x ), f32( vert.norm_y ), f32( vert.norm_z ) ); glm::vec3 vecTransformedNormal = glm::normalize(glm::mat3(mat_world) * model_normal); glm::vec3 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 = glm::normalize(glm::mat3(mat_world_project) * model_normal); #endif const glm::vec3 & 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 = glm::vec4( 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) { alignas(DATA_ALIGN) const glm::mat4 & mat_project = mProjectionMat; alignas(DATA_ALIGN) const glm::mat4 & 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 // glm::vec4 w( f32( vert.x ), f32( vert.y ), f32( vert.z ), 1.0f ); glm::vec4 & transformed( mVtxProjected[i].TransformedPos ); transformed = mat_world * w; glm::vec4 & projected( mVtxProjected[i].ProjectedPos ); projected = mat_project * 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 ) { glm::vec3 model_normal( mn[((i<<1)+0)^3], mn[((i<<1)+1)^3], vert.normz ); glm::vec3 vecTransformedNormal = glm::normalize(glm::mat3(mat_world) * model_normal); const glm::vec3 & norm = vecTransformedNormal; const glm::vec3 & col = mTnL.Lights[mTnL.NumLights].Colour; glm::vec4 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]; glm::vec3 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 = glm::dot(mTnL.Lights[l].Direction, norm); if (fCosT > 0.0f) { f32 pi = mTnL.Lights[l].Iscale / glm::dot(Pos - mTnL.Lights[l].Position, Pos - mTnL.Lights[l].Position); 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 = glm::dot( mTnL.Lights[l].Direction, norm ); 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 / glm::dot(Pos - mTnL.Lights[l].Position, Pos - mTnL.Lights[l].Position); 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) { alignas(DATA_ALIGN) const glm::mat4 & 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(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 glm::vec4 & BaseVec( mVtxProjected[0].TransformedPos ); //Hack to worldproj matrix to scale and rotate billbords //Corn glm::mat4 mat = mModelViewStack[0]; mat[0][0] *= mModelViewStack[2][0][0] * 0.5f; mat[1][0] *= mModelViewStack[2][0][0] * 0.5f; mat[2][0] *= mModelViewStack[2][0][0] * 0.5f; mat[0][1] *= mModelViewStack[2][0][0] * 0.375f; mat[1][1] *= mModelViewStack[2][0][0] * 0.375f; mat[2][1] *= mModelViewStack[2][0][0] * 0.375f; mat[0][2] *= mModelViewStack[2][2][2] * 0.5f; mat[1][2] *= mModelViewStack[2][2][2] * 0.5f; mat[2][2] *= mModelViewStack[2][2][2] * 0.5f; for (u32 i = v0; i < v0 + n; i++) { glm::vec3 w; w.x = *(s16*)((pVtxBase + 0) ^ 2); w.y = *(s16*)((pVtxBase + 2) ^ 2); w.z = *(s16*)((pVtxBase + 4) ^ 2); w = glm::normalize(glm::transpose(glm::inverse(glm::mat3(mat))) * w); glm::vec4 & 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++) { glm::vec4 & 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; glm::vec4 & projected( mVtxProjected[i].ProjectedPos ); projected = mat_world_project * 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) { alignas(DATA_ALIGN) const glm::mat4 & mat_world = mModelViewStack[mModelViewTop]; alignas(DATA_ALIGN) const glm::mat4 & 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]; glm::vec4 w( f32( vert.x ), f32( vert.y ), f32( vert.z ), 1.0f ); // VTX Transform // glm::vec4 & transformed( mVtxProjected[i].TransformedPos ); transformed = mat_world * w; glm::vec4 & projected( mVtxProjected[i].ProjectedPos ); projected = mat_project * transformed; // Set Clipflags //Corn mVtxProjected[i].ClipFlags = set_clip_flags( projected ); if( mTnL.Flags.Light ) { glm::vec3 model_normal((f32)mn[vert.cidx+3], (f32)mn[vert.cidx+2], (f32)mn[vert.cidx+1] ); glm::vec3 vecTransformedNormal = glm::normalize(glm::mat3(mat_world) * model_normal); const glm::vec3 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 glm::vec3 & 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 = glm::vec4( 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] = glm::mat4(1.0f);; 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) || defined(DAEDALUS_GLES) // 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 texture0, const TextureInfo & ti1, std::shared_ptr 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(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(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 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; #if defined(DAEDALUS_GLES) || defined(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 BaseRenderer::LoadTextureDirectly( const TextureInfo & ti ) { std::shared_ptr 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; glm::vec2 n64_tl( (f32)x0, (f32)y0 ); glm::vec2 n64_br( (f32)x1, (f32)y1 ); glm::vec2 screen_tl; glm::vec2 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(screen_tl.x), 0 ); s32 t = std::max( 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) || defined(DAEDALUS_GLES) // NB: OpenGL is x,y,w,h. Errors if width or height is negative, so clamp this. s32 w = std::max( r - l, 0 ); s32 h = std::max( 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( glm::mat4 & 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[3][2] += 0.4f; if( gGlobalPreferences.ViewportType == VT_FULLSCREEN_HD ) mProjectionMat[0][0] *= HD_SCALE;//proper 16:9 scale } else { MatrixFromN64FixedPoint( mTempMat, address); mProjectionMat *= mTempMat; } 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[0][0], mProjectionmat[0][1], mProjectionmat[0][2], mProjectionmat[0][3], mProjectionmat[1][0], mProjectionmat[1][1], mProjectionmat[1][2], mProjectionmat[1][3], mProjectionmat[2][0], mProjectionmat[2][1], mProjectionmat[2][2], mProjectionmat[2][3], mProjectionmat[3][0], mProjectionmat[3][1], mProjectionmat[3][2], mProjectionmat[3][3]); #endif } //***************************************************************************** // //***************************************************************************** void BaseRenderer::SetDKRMat(const u32 address, bool mul, u32 idx) { mDKRMatIdx = idx; mWPmodified = true; if( mul ) { MatrixFromN64FixedPoint( mTempMat, address ); mModelViewStack[idx] = mTempMat * mModelViewStack[0]; } else { MatrixFromN64FixedPoint( mModelViewStack[idx], address ); } #ifdef DAEDALUS_DEBUG_DISPLAYLIST alignas(DATA_ALIGN) const glm::mat4 & 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[0][0], mtx[0][1], mtx[0][2], mtx[0][3], mtx[1][0], mtx[1][1], mtx[1][2], mtx[1][3], mtx[2][0], mtx[2][1], mtx[2][2], mtx[2][3], mtx[3][0], mtx[3][1], mtx[3][2], mtx[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) { mModelViewStack[mModelViewTop] *= 2.0f; // Multiply entire matrix by 2 } } else // Multiply ModelView matrix { MatrixFromN64FixedPoint( mTempMat, address); mModelViewStack[mModelViewTop] = mModelViewStack[mModelViewTop - 1] * mTempMat; } } else // NoPush { if (bReplace) { // Load ModelView matrix MatrixFromN64FixedPoint( mModelViewStack[mModelViewTop], address); } else { // Multiply ModelView matrix MatrixFromN64FixedPoint( mTempMat, address); mModelViewStack[mModelViewTop] *= mTempMat; } } 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) ); } mWorldProject = mProjectionMat * mModelViewStack[mModelViewTop]; } //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[0][0] *= HD_SCALE; // Column 0, Row 0 mWorldProject[1][0] *= HD_SCALE; // Column 0, Row 1 mWorldProject[2][0] *= HD_SCALE; // Column 0, Row 2 mWorldProject[3][0] *= HD_SCALE; // Column 0, Row 3 } sceGuSetMatrix( GU_PROJECTION, reinterpret_cast< const ScePspFMatrix4 * >( &mWorldProject ) ); mModelViewStack[mModelViewTop] = glm::mat4(1.0f);; } } //***************************************************************************** // //***************************************************************************** #ifdef DAEDALUS_DEBUG_DISPLAYLIST void BaseRenderer::PrintActive() { UpdateWorldProject(); alignas(DATA_ALIGN) const glm::mat4 & 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[0][0], mat[0][1], mat[0][2], mat[0][3], mat[1][0], mat[1][1], mat[1][2], mat[1][3], mat[2][0], mat[2][1], mat[2][2], mat[2][3], mat[3][0], mat[3][1], mat[3][2], mat[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 = mProjectionMat * mModelViewStack[mModelViewTop]; mWorldProjectValid = true; } u32 x = (w0 & 0x1F) >> 1; u32 y = x >> 2; x &= 3; if (w0 & 0x20) { //Change fraction part mWorldProject[y][x] = (f32)(s32)mWorldProject[y][x] + ((f32)(w1 >> 16) / 65536.0f); mWorldProject[y][x+1] = (f32)(s32)mWorldProject[y][x+1] + ((f32)(w1 & 0xFFFF) / 65536.0f); } else { //Change integer part mWorldProject[y][x] = (f32)(s16)(w1 >> 16); mWorldProject[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 }