//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // // $NoKeywords: $ // //===========================================================================// #include "render_pch.h" #include "client.h" #include "sound.h" #include "debug_leafvis.h" #include "cdll_int.h" #include "enginestats.h" #include "ivrenderview.h" #include "studio.h" #include "l_studio.h" #include "r_areaportal.h" #include "materialsystem/materialsystem_config.h" #include "materialsystem/itexture.h" #include "cdll_engine_int.h" #include "materialsystem/imaterialsystemhardwareconfig.h" #include "istudiorender.h" #include "staticpropmgr.h" #include "tier0/vprof.h" #include "IOcclusionSystem.h" #include "con_nprint.h" #include "debugoverlay.h" #include "demo.h" #include "ivideomode.h" #include "sys_dll.h" #include "collisionutils.h" #include "tier1/utlstack.h" #include "r_decal.h" #include "cl_main.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" #ifndef _X360 extern ConVar r_waterforceexpensive; #endif ConVar r_aspectratio( "r_aspectratio", "0" #if !defined( _X360 ) , FCVAR_CHEAT #endif ); ConVar r_dynamiclighting( "r_dynamiclighting", "1", FCVAR_CHEAT ); extern ConVar building_cubemaps; extern float scr_demo_override_fov; extern colorVec R_LightPoint (Vector& p); CEngineStats g_EngineStats; //----------------------------------------------------------------------------- // view origin //----------------------------------------------------------------------------- extern Vector g_CurrentViewOrigin, g_CurrentViewForward, g_CurrentViewRight, g_CurrentViewUp; extern Vector g_MainViewOrigin, g_MainViewForward, g_MainViewRight, g_MainViewUp; bool g_bCanAccessCurrentView = false; int d_lightstyleframe[256]; void ProjectPointOnPlane( Vector& dst, const Vector& p, const Vector& normal ) { float d; Vector n; float inv_denom; inv_denom = 1.0F / DotProduct( normal, normal ); d = DotProduct( normal, p ) * inv_denom; n[0] = normal[0] * inv_denom; n[1] = normal[1] * inv_denom; n[2] = normal[2] * inv_denom; dst[0] = p[0] - d * n[0]; dst[1] = p[1] - d * n[1]; dst[2] = p[2] - d * n[2]; } /* ** assumes "src" is normalized */ void PerpendicularVector( Vector& dst, const Vector& src ) { int pos; int i; float minelem = 1.0F; Vector tempvec; /* ** find the smallest magnitude axially aligned vector */ for ( pos = 0, i = 0; i < 3; i++ ) { if ( fabs( src[i] ) < minelem ) { pos = i; minelem = fabs( src[i] ); } } tempvec[0] = tempvec[1] = tempvec[2] = 0.0F; tempvec[pos] = 1.0F; /* ** project the point onto the plane defined by src */ ProjectPointOnPlane( dst, tempvec, src ); /* ** normalize the result */ VectorNormalize( dst ); } //----------------------------------------------------------------------------- // Returns the aspect ratio of the screen //----------------------------------------------------------------------------- float GetScreenAspect( ) { // use the override if set if ( r_aspectratio.GetFloat() > 0.0f ) return r_aspectratio.GetFloat(); // mikesart: This is just sticking in unnecessary BeginRender/EndRender calls to the queue. // CMatRenderContextPtr pRenderContext( materials ); IMatRenderContext *pRenderContext = g_pMaterialSystem->GetRenderContext(); int width, height; pRenderContext->GetRenderTargetDimensions( width, height ); return (height != 0) ? ( (float)width / (float)height ) : 1.0f; } /* ==================== CalcFov ==================== */ void R_DrawScreenRect( float left, float top, float right, float bottom ) { CMatRenderContextPtr pRenderContext( materials ); pRenderContext->MatrixMode( MATERIAL_VIEW ); pRenderContext->PushMatrix(); pRenderContext->LoadIdentity(); pRenderContext->MatrixMode( MATERIAL_PROJECTION ); pRenderContext->PushMatrix(); pRenderContext->LoadIdentity(); IMaterial *pMaterial = materials->FindMaterial( "debug/debugportals", TEXTURE_GROUP_OTHER ); IMesh *pMesh = pRenderContext->GetDynamicMesh( true, NULL, NULL, pMaterial ); CMeshBuilder builder; builder.Begin( pMesh, MATERIAL_LINE_LOOP, 4 ); Vector v1( left, bottom, 0.5 ); Vector v2( left, top, 0.5 ); Vector v3( right, top, 0.5 ); Vector v4( right, bottom, 0.5 ); builder.Position3fv( v1.Base() ); builder.AdvanceVertex(); builder.Position3fv( v2.Base() ); builder.AdvanceVertex(); builder.Position3fv( v3.Base() ); builder.AdvanceVertex(); builder.Position3fv( v4.Base() ); builder.AdvanceVertex(); builder.End( false, true ); pRenderContext->MatrixMode( MATERIAL_VIEW ); pRenderContext->PopMatrix(); pRenderContext->MatrixMode( MATERIAL_PROJECTION ); pRenderContext->PopMatrix(); } void R_DrawPortals() { // Draw the portals. if( !r_DrawPortals.GetInt() ) return; IMaterial *pMaterial = materials->FindMaterial( "debug/debugportals", TEXTURE_GROUP_OTHER ); CMatRenderContextPtr pRenderContext( materials ); IMesh *pMesh = pRenderContext->GetDynamicMesh( true, NULL, NULL, pMaterial ); worldbrushdata_t *pBrushData = host_state.worldbrush; for( int i=0; i < pBrushData->m_nAreaPortals; i++ ) { dareaportal_t *pAreaPortal = &pBrushData->m_pAreaPortals[i]; if( !R_IsAreaVisible( pAreaPortal->otherarea ) ) continue; CMeshBuilder builder; builder.Begin( pMesh, MATERIAL_LINES, pAreaPortal->m_nClipPortalVerts ); for( int j=0; j < pAreaPortal->m_nClipPortalVerts; j++ ) { unsigned short iVert; iVert = pAreaPortal->m_FirstClipPortalVert + j; builder.Position3f( VectorExpand( pBrushData->m_pClipPortalVerts[iVert] ) ); builder.Color4f( 0, 0, 0, 1 ); builder.AdvanceVertex(); iVert = pAreaPortal->m_FirstClipPortalVert + (j+1) % pAreaPortal->m_nClipPortalVerts; builder.Position3f( VectorExpand( pBrushData->m_pClipPortalVerts[iVert] ) ); builder.Color4f( 0, 0, 0, 1 ); builder.AdvanceVertex(); } builder.End( false, true ); } // Draw the clip rectangles. for( int i=0; i < g_PortalRects.Size(); i++ ) { CPortalRect *pRect = &g_PortalRects[i]; R_DrawScreenRect( pRect->left, pRect->top, pRect->right, pRect->bottom ); } g_PortalRects.Purge(); } //----------------------------------------------------------------------------- // // Loose collection of functions related to rendering the world in a particular view // //----------------------------------------------------------------------------- class CRender : public IRender { public: CRender(); void FrameBegin( void ); void FrameEnd( void ); void ViewSetupVis( bool novis, int numorigins, const Vector origin[] ); void ViewSetupVisEx( bool novis, int numorigins, const Vector origin[], unsigned int &returnFlags ); void ViewEnd( void ); void ViewDrawFade( byte *color, IMaterial* pMaterial ); IWorldRenderList * CreateWorldList(); void BuildWorldLists( IWorldRenderList *pList, WorldListInfo_t* pInfo, int iForceViewLeaf, const VisOverrideData_t* pVisData, bool bShadowDepth, float *pWaterReflectionHeight ); void DrawWorldLists( IWorldRenderList *pList, unsigned long flags, float waterZAdjust ); void DrawSceneBegin( void ); void DrawSceneEnd( void ); // utility functions void ExtractMatrices( void ); void ExtractFrustumPlanes( Frustum frustumPlanes ); void OrthoExtractFrustumPlanes( Frustum frustumPlanes ); void OverrideViewFrustum( Frustum custom ); void SetViewport( int x, int y, int w, int h ); // UNDONE: these are temporary functions that will end up on the other // side of this interface const Vector &ViewOrigin( ) { return CurrentView().origin; } const QAngle &ViewAngles( ) { return CurrentView().angles; } const CViewSetup &ViewGetCurrent( void ) { return CurrentView(); } const VMatrix &ViewMatrix( void ); const VMatrix &WorldToScreenMatrix( void ); float GetFramerate( void ) { return m_framerate; } virtual float GetZNear( void ) { return m_zNear; } virtual float GetZFar( void ) { return m_zFar; } // Query current fov and view model fov float GetFov( void ) { return CurrentView().fov; }; float GetFovY( void ) { return m_yFOV; }; float GetFovViewmodel( void ) { return CurrentView().fovViewmodel; }; virtual bool ClipTransformWithProjection ( const VMatrix& worldToScreen, const Vector& point, Vector* pClip ); virtual bool ClipTransform( const Vector& point, Vector* pClip ); virtual bool ScreenTransform( const Vector& point, Vector* pScreen ); virtual void Push3DView( const CViewSetup &view, int nFlags, ITexture* pRenderTarget, Frustum frustumPlanes ); virtual void Push3DView( const CViewSetup &view, int nFlags, ITexture* pRenderTarget, Frustum frustumPlanes, ITexture* pDepthTexture ); virtual void Push2DView( const CViewSetup &view, int nFlags, ITexture* pRenderTarget, Frustum frustumPlanes ); virtual void PopView( Frustum frustumPlanes ); virtual void SetMainView( const Vector &vecOrigin, const QAngle &angles ); virtual void UpdateBrushModelLightmap( model_t *model, IClientRenderable *Renderable ); virtual void BeginUpdateLightmaps( void ); virtual void EndUpdateLightmaps( void ); virtual bool InLightmapUpdate( void ) const; private: // Called when a particular view becomes active void OnViewActive( Frustum frustumPlanes ); // Clear the view (assumes the render target has already been pushed) void ClearView( CViewSetup &view, int nFlags, ITexture* pRenderTarget, ITexture* pDepthTexture = NULL ); const CViewSetup &CurrentView() const { return m_ViewStack.Top().m_View; } CViewSetup &CurrentView() { return m_ViewStack.Top().m_View; } // Stack of view info struct ViewStack_t { CViewSetup m_View; // matrices VMatrix m_matrixView; VMatrix m_matrixProjection; VMatrix m_matrixWorldToScreen; bool m_bIs2DView; bool m_bNoDraw; }; // Y field of view, calculated from X FOV and screen aspect ratio. float m_yFOV; // timing double m_frameStartTime; float m_framerate; float m_zNear; float m_zFar; // matrices VMatrix m_matrixView; VMatrix m_matrixProjection; VMatrix m_matrixWorldToScreen; CUtlStack< ViewStack_t > m_ViewStack; int m_iLightmapUpdateDepth; }; //----------------------------------------------------------------------------- // Singleton //----------------------------------------------------------------------------- static CRender gRender; IRender *g_EngineRenderer = &gRender; //----------------------------------------------------------------------------- // Called when the engine is about to begin rendering for any reason //----------------------------------------------------------------------------- CRender::CRender() { // Make sure the stack isn't empty int i = m_ViewStack.Push(); memset( &m_ViewStack[i], 0, sizeof( CViewSetup ) ); m_ViewStack[i].m_bIs2DView = true; m_iLightmapUpdateDepth = 0; } //----------------------------------------------------------------------------- // Called when the engine is about to begin rendering for any reason //----------------------------------------------------------------------------- void CRender::FrameBegin( void ) { if ( host_state.worldmodel ) { // This has to be before R_AnimateLight because it uses it to // set the frame number of changed lightstyles // FIXME: Why isn't this being done in DrawSceneBegin // or some other client-side simulation of state? r_framecount++; R_AnimateLight (); R_PushDlights(); if (!r_norefresh.GetInt()) { m_frameStartTime = Sys_FloatTime (); } } UpdateStudioRenderConfig(); g_pStudioRender->BeginFrame(); } //----------------------------------------------------------------------------- // Called when the engine has finished rendering //----------------------------------------------------------------------------- void CRender::FrameEnd( void ) { // A debugging overlay that renders all raycasts. // Why, or why is this being done here instead of // where all the other debug overlays are being done in the client DLL? EngineTraceRenderRayCasts(); m_framerate = cl.GetFrameTime(); if ( m_framerate > 0 ) { m_framerate = 1 / m_framerate; } g_pStudioRender->EndFrame(); } const VMatrix &CRender::ViewMatrix( ) { // If we aren't in a valid view, then use the last value cached off into the global variable instead if ( m_ViewStack.Count() > 1 ) { return m_ViewStack.Top().m_matrixView; } return m_matrixView; } const VMatrix &CRender::WorldToScreenMatrix( void ) { // If we aren't in a valid view, then use the last value cached off into the global variable instead if ( m_ViewStack.Count() > 1 ) { return m_ViewStack.Top().m_matrixWorldToScreen; } return m_matrixWorldToScreen; } void CRender::ViewSetupVis( bool novis, int numorigins, const Vector origin[] ) { unsigned int returnFlags = 0; ViewSetupVisEx( novis, numorigins, origin, returnFlags ); } void CRender::ViewSetupVisEx( bool novis, int numorigins, const Vector origin[], unsigned int &returnFlags ) { Map_VisSetup( host_state.worldmodel, numorigins, origin, novis, returnFlags ); } //----------------------------------------------------------------------------- // Called when a particular view becomes active //----------------------------------------------------------------------------- void CRender::OnViewActive( Frustum frustumPlanes ) { const CViewSetup &view = CurrentView(); m_yFOV = CalcFovY( view.fov, view.m_flAspectRatio ); // build the transformation matrix for the given view angles VectorCopy( view.origin, g_CurrentViewOrigin ); AngleVectors( view.angles, &g_CurrentViewForward, &g_CurrentViewRight, &g_CurrentViewUp ); // g_CurrentViewUp = -g_CurrentViewUp; g_bCanAccessCurrentView = true; if ( frustumPlanes ) { if ( view.m_bOrtho ) { OrthoExtractFrustumPlanes( frustumPlanes ); } else { ExtractFrustumPlanes( frustumPlanes ); } OcclusionSystem()->SetView( view.origin, view.fov, m_matrixView, m_matrixProjection, frustumPlanes[ FRUSTUM_NEARZ ] ); } if ( !m_ViewStack.Top().m_bNoDraw ) { R_SceneBegin( ); } // debug, build leaf volume // NOTE: This is pretty hacky, but I want the leaf based on the main view. The skybox view is reseting // the g_LeafVis here because it is global. This need to be resolved more correctly some other way! if ( VectorCompare( g_MainViewOrigin, view.origin ) ) { LeafVisBuild( view.origin ); } } //----------------------------------------------------------------------------- // Clear the view (assumes the render target has already been pushed) //----------------------------------------------------------------------------- void CRender::ClearView( CViewSetup &view, int nFlags, ITexture* pRenderTarget, ITexture* pDepthTexture /* = NULL */ ) { bool bClearColor = (nFlags & VIEW_CLEAR_COLOR) != 0; bool bClearDepth = (nFlags & VIEW_CLEAR_DEPTH) != 0; bool bClearStencil = (nFlags & VIEW_CLEAR_STENCIL) != 0; bool bForceClearWholeRenderTarget = (nFlags & VIEW_CLEAR_FULL_TARGET) != 0; bool bObeyStencil = (nFlags & VIEW_CLEAR_OBEY_STENCIL) != 0; // Handle an initial clear request if asked for if ( !bClearColor && !bClearDepth && !bClearStencil ) return; CMatRenderContextPtr pRenderContext( materials ); if ( !bForceClearWholeRenderTarget ) { if( bObeyStencil ) pRenderContext->ClearBuffersObeyStencil( bClearColor, bClearDepth ); else pRenderContext->ClearBuffers( bClearColor, bClearDepth, bClearStencil ); } else { // Get the render target dimensions int nWidth, nHeight; if ( pRenderTarget ) { nWidth = pRenderTarget->GetActualWidth(); nHeight = pRenderTarget->GetActualHeight(); } else { materials->GetBackBufferDimensions( nWidth, nHeight ); } pRenderContext->PushRenderTargetAndViewport( pRenderTarget, pDepthTexture, 0, 0, nWidth, nHeight ); if( bObeyStencil ) pRenderContext->ClearBuffersObeyStencil( bClearColor, bClearDepth ); else pRenderContext->ClearBuffers( bClearColor, bClearDepth, bClearStencil ); pRenderContext->PopRenderTargetAndViewport( ); } } //----------------------------------------------------------------------------- // Push, pop views //----------------------------------------------------------------------------- void CRender::Push3DView( const CViewSetup &view, int nFlags, ITexture* pRenderTarget, Frustum frustumPlanes ) { Push3DView( view, nFlags, pRenderTarget, frustumPlanes, NULL ); } //----------------------------------------------------------------------------- // Computes view matrices //----------------------------------------------------------------------------- float ComputeViewMatrices( VMatrix *pWorldToView, VMatrix *pViewToProjection, VMatrix *pWorldToProjection, const CViewSetup &viewSetup ) { float flAspectRatio = viewSetup.m_flAspectRatio; if ( flAspectRatio == 0.0f ) { flAspectRatio = (viewSetup.height != 0) ? ( (float)viewSetup.width / (float)viewSetup.height ) : 1.0f; } ComputeViewMatrix( pWorldToView, viewSetup.origin, viewSetup.angles ); if ( viewSetup.m_bOrtho ) { MatrixBuildOrtho( *pViewToProjection, viewSetup.m_OrthoLeft, viewSetup.m_OrthoTop, viewSetup.m_OrthoRight, viewSetup.m_OrthoBottom, viewSetup.zNear, viewSetup.zFar ); } else if ( viewSetup.m_bOffCenter ) // Off-center projection, useful for AA jitter and tiled output of posters { MatrixBuildPerspectiveOffCenterX( *pViewToProjection, viewSetup.fov, flAspectRatio, viewSetup.zNear, viewSetup.zFar, viewSetup.m_flOffCenterBottom, viewSetup.m_flOffCenterTop, viewSetup.m_flOffCenterLeft, viewSetup.m_flOffCenterRight ); } else if ( viewSetup.m_bViewToProjectionOverride ) { *pViewToProjection = viewSetup.m_ViewToProjection; // ...but then override the Z range (needed for correct skybox rendering, etc). MatrixBuildPerspectiveZRange ( *pViewToProjection, viewSetup.zNear, viewSetup.zFar ); } else { MatrixBuildPerspectiveX( *pViewToProjection, viewSetup.fov, flAspectRatio, viewSetup.zNear, viewSetup.zFar ); } MatrixMultiply( *pViewToProjection, *pWorldToView, *pWorldToProjection ); return flAspectRatio; } // Flip y, screen y goes down static VMatrix g_ProjectionToOffset( 0.5f, 0.0f, 0.0f, 0.5f, 0.0f, -0.5f, 0.0f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f ); // NOTE: Screen coordinates go from 0->w, 0->h void ComputeWorldToScreenMatrix( VMatrix *pWorldToScreen, const VMatrix &worldToProjection, const CViewSetup &viewSetup ) { // First need to transform -1 -> 1 to 0 -> 1 in x and y // Then transform from 0->1 to x->w+x in x, and 0->1 to y->y+h in y. VMatrix offsetToPixels( viewSetup.width, 0.0f, 0.0f, viewSetup.x, 0.0f, viewSetup.height, 0.0f, viewSetup.y, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f ); VMatrix projectionToPixels; MatrixMultiply( offsetToPixels, g_ProjectionToOffset, projectionToPixels ); MatrixMultiply( projectionToPixels, worldToProjection, *pWorldToScreen ); } //----------------------------------------------------------------------------- // Push, pop views //----------------------------------------------------------------------------- void CRender::Push3DView( const CViewSetup &view, int nFlags, ITexture* pRenderTarget, Frustum frustumPlanes, ITexture* pDepthTexture ) { Assert( !IsX360() || (pDepthTexture == NULL) ); //Don't render to a depth texture on the 360. Instead, render using a normal depth buffer and use IDirect3DDevice9::Resolve() int i = m_ViewStack.Push( ); m_ViewStack[i].m_View = view; m_ViewStack[i].m_bIs2DView = false; m_ViewStack[i].m_bNoDraw = ( ( nFlags & VIEW_NO_DRAW ) != 0 ); CViewSetup &topView = m_ViewStack[i].m_View; // Compute aspect ratio if asked for if ( topView.m_flAspectRatio == 0.0f ) { topView.m_flAspectRatio = (topView.height != 0) ? ( (float)topView.width / (float)topView.height ) : 1.0f; } ViewStack_t &viewStack = m_ViewStack.Top(); topView.m_flAspectRatio = ComputeViewMatrices( &viewStack.m_matrixView, &viewStack.m_matrixProjection, &viewStack.m_matrixWorldToScreen, topView ); m_zNear = topView.zNear; m_zFar = topView.zFar; // cache this for queries ExtractMatrices(); if ( !m_ViewStack[i].m_bNoDraw ) { CMatRenderContextPtr pRenderContext( materials ); if ( !pRenderTarget ) { pRenderTarget = pRenderContext->GetRenderTarget(); } // Push render target and viewport pRenderContext->PushRenderTargetAndViewport( pRenderTarget, pDepthTexture, topView.x, topView.y, topView.width, topView.height ); // Handle an initial clear request if asked for ClearView( topView, nFlags, pRenderTarget, pDepthTexture ); pRenderContext->DepthRange( 0, 1 ); pRenderContext->MatrixMode( MATERIAL_PROJECTION ); pRenderContext->PushMatrix(); pRenderContext->LoadMatrix( m_matrixProjection ); pRenderContext->MatrixMode( MATERIAL_VIEW ); pRenderContext->PushMatrix(); pRenderContext->LoadMatrix( m_matrixView ); pRenderContext->MatrixMode( MATERIAL_MODEL ); pRenderContext->PushMatrix(); OnViewActive( frustumPlanes ); } } void CRender::Push2DView( const CViewSetup &view, int nFlags, ITexture* pRenderTarget, Frustum frustumPlanes ) { int i = m_ViewStack.Push( ); m_ViewStack[i].m_View = view; m_ViewStack[i].m_bIs2DView = true; m_ViewStack[i].m_bNoDraw = ( ( nFlags & VIEW_NO_DRAW ) != 0 ); m_ViewStack[i].m_matrixView = m_matrixView; m_ViewStack[i].m_matrixProjection = m_matrixProjection; m_ViewStack[i].m_matrixWorldToScreen = m_matrixWorldToScreen; CViewSetup &topView = m_ViewStack[i].m_View; g_bCanAccessCurrentView = false; CMatRenderContextPtr pRenderContext( materials ); if ( !pRenderContext ) { pRenderTarget = pRenderContext->GetRenderTarget(); } // Push render target and viewport pRenderContext->PushRenderTargetAndViewport( pRenderTarget, topView.x, topView.y, topView.width, topView.height ); // Handle an initial clear request if asked for ClearView( topView, nFlags, pRenderTarget ); pRenderContext->MatrixMode( MATERIAL_PROJECTION ); pRenderContext->PushMatrix(); pRenderContext->LoadIdentity(); pRenderContext->Scale( 1, -1, 1 ); pRenderContext->Ortho( 0, 0, topView.width, topView.height, -99999, 99999 ); pRenderContext->MatrixMode( MATERIAL_VIEW ); pRenderContext->PushMatrix(); pRenderContext->LoadIdentity(); pRenderContext->MatrixMode( MATERIAL_MODEL ); pRenderContext->PushMatrix(); pRenderContext->LoadIdentity(); } void CRender::PopView( Frustum frustumPlanes ) { if ( !m_ViewStack.Top().m_bNoDraw ) { CMatRenderContextPtr pRenderContext( materials ); pRenderContext->MatrixMode( MATERIAL_PROJECTION ); pRenderContext->PopMatrix(); pRenderContext->MatrixMode( MATERIAL_VIEW ); pRenderContext->PopMatrix(); pRenderContext->MatrixMode( MATERIAL_MODEL ); pRenderContext->PopMatrix(); pRenderContext->PopRenderTargetAndViewport( ); } bool bReset = ( m_ViewStack.Count() > 1 ) ? true : false; m_ViewStack.Pop(); // Don't pop off the very last view g_bCanAccessCurrentView = false; if ( bReset ) { if ( !m_ViewStack.Top().m_bIs2DView ) { ExtractMatrices(); OnViewActive( frustumPlanes ); } } } //----------------------------------------------------------------------------- // Sets the main 3D view (for console commands, sound, etc.) //----------------------------------------------------------------------------- void CRender::SetMainView( const Vector &vecOrigin, const QAngle &angles ) { VectorCopy( vecOrigin, g_MainViewOrigin ); AngleVectors( angles, &g_MainViewForward, &g_MainViewRight, &g_MainViewUp ); } CUtlVector g_LightmapUpdateList; CUtlVector g_LightmapTransformList; int __cdecl LightmapPageCompareFunc( const void *pElem0, const void *pElem1 ) { const LightmapUpdateInfo_t *pSurf0 = (const LightmapUpdateInfo_t *)pElem0; const LightmapUpdateInfo_t *pSurf1 = (const LightmapUpdateInfo_t *)pElem1; int page0 = materialSortInfoArray[MSurf_MaterialSortID( (pSurf0->m_SurfHandle) )].lightmapPageID; int page1 = materialSortInfoArray[MSurf_MaterialSortID( (pSurf1->m_SurfHandle) )].lightmapPageID; return page0 - page1; } void CRender::BeginUpdateLightmaps( void ) { if ( ++m_iLightmapUpdateDepth == 1) { Assert( g_LightmapUpdateList.Count() == 0 ); materials->BeginUpdateLightmaps(); // UNDONE: Move this to an init or constructor? g_LightmapTransformList.RemoveAll(); int index = g_LightmapTransformList.AddToTail(); g_LightmapTransformList[index].pModel = host_state.worldmodel; SetIdentityMatrix( g_LightmapTransformList[index].xform ); } } void CRender::UpdateBrushModelLightmap( model_t *model, IClientRenderable *pRenderable ) { AssertOnce( m_iLightmapUpdateDepth ); if( !r_drawbrushmodels.GetBool() || !m_iLightmapUpdateDepth ) return; R_MarkDlightsOnBrushModel( model, pRenderable ); if ( model->flags & MODELFLAG_HAS_DLIGHT ) { int transformIndex = g_LightmapTransformList.AddToTail(); LightmapTransformInfo_t &transform = g_LightmapTransformList[transformIndex]; transform.pModel = model; AngleMatrix( pRenderable->GetRenderAngles(), pRenderable->GetRenderOrigin(), transform.xform ); SurfaceHandle_t surfID = SurfaceHandleFromIndex( model->brush.firstmodelsurface, model->brush.pShared ); bool bLight = false; for (int i=0 ; ibrush.nummodelsurfaces ; i++, surfID++) { if ( MSurf_Flags(surfID) & (SURFDRAW_HASDLIGHT|SURFDRAW_HASLIGHTSYTLES) ) { LightmapUpdateInfo_t tmp; tmp.m_SurfHandle = surfID; tmp.transformIndex = transformIndex; g_LightmapUpdateList.AddToTail( tmp ); bLight = true; } } if ( !bLight ) { model->flags &= ~MODELFLAG_HAS_DLIGHT; // don't need to check again unless a dlight hits us } } } void CRender::EndUpdateLightmaps( void ) { Assert( m_iLightmapUpdateDepth > 0 ); if ( --m_iLightmapUpdateDepth == 0 ) { VPROF_BUDGET( "EndUpdateLightmaps", VPROF_BUDGETGROUP_DLIGHT_RENDERING ); if ( g_LightmapUpdateList.Count() && r_dynamiclighting.GetBool() && !r_unloadlightmaps.GetBool() ) { CMatRenderContextPtr pRenderContext( materials ); ICallQueue *pCallQueue = pRenderContext->GetCallQueue(); dlight_t *pLights = &cl_dlights[0]; // only do the copy when there are valid dlights to process and threading is on if ( g_bActiveDlights && pCallQueue ) { // keep a copy of the current dlight state around for the thread to work on // in parallel. This way the main thread can continue to modify this state without // generating any bad results static dlight_t threadDlights[MAX_DLIGHTS*2]; static int threadFrameCount = 0; pLights = &threadDlights[MAX_DLIGHTS*threadFrameCount]; Q_memcpy( pLights, cl_dlights, sizeof(dlight_t) * MAX_DLIGHTS ); threadFrameCount = (threadFrameCount+1) & 1; } qsort( g_LightmapUpdateList.Base(), g_LightmapUpdateList.Count(), sizeof(g_LightmapUpdateList.Element(0)), LightmapPageCompareFunc ); int i; for ( i = g_LightmapUpdateList.Count()-1; i >= 0; --i ) { const LightmapUpdateInfo_t &lightmapUpdateInfo = g_LightmapUpdateList.Element(i); // a surface can get queued more than once if it's visible in multiple views (e.g. water reflection can do this) // so check frame to make sure we only recompute once if ( SurfaceLighting(lightmapUpdateInfo.m_SurfHandle)->m_nLastComputedFrame != r_framecount ) { R_RenderDynamicLightmaps( pLights, pCallQueue, lightmapUpdateInfo.m_SurfHandle, g_LightmapTransformList[lightmapUpdateInfo.transformIndex].xform ); } } } materials->EndUpdateLightmaps(); g_LightmapUpdateList.RemoveAll(); g_LightmapTransformList.RemoveAll(); } } bool CRender::InLightmapUpdate( void ) const { return ( m_iLightmapUpdateDepth != 0 ); } //----------------------------------------------------------------------------- // Compute the scene coordinates of a point in 3D //----------------------------------------------------------------------------- bool CRender::ClipTransformWithProjection ( const VMatrix& worldToScreen, const Vector& point, Vector* pClip ) { // UNDONE: Clean this up some, handle off-screen vertices float w; pClip->x = worldToScreen[0][0] * point[0] + worldToScreen[0][1] * point[1] + worldToScreen[0][2] * point[2] + worldToScreen[0][3]; pClip->y = worldToScreen[1][0] * point[0] + worldToScreen[1][1] * point[1] + worldToScreen[1][2] * point[2] + worldToScreen[1][3]; // z = worldToScreen[2][0] * point[0] + worldToScreen[2][1] * point[1] + worldToScreen[2][2] * point[2] + worldToScreen[2][3]; w = worldToScreen[3][0] * point[0] + worldToScreen[3][1] * point[1] + worldToScreen[3][2] * point[2] + worldToScreen[3][3]; // Just so we have something valid here pClip->z = 0.0f; bool behind; if( w < 0.001f ) { behind = true; pClip->x *= 100000; pClip->y *= 100000; } else { behind = false; float invw = 1.0f / w; pClip->x *= invw; pClip->y *= invw; } return behind; } //----------------------------------------------------------------------------- // Compute the scene coordinates of a point in 3D using the current engine's projection //----------------------------------------------------------------------------- bool CRender::ClipTransform ( const Vector& point, Vector* pClip ) { const VMatrix &worldToScreen = g_EngineRenderer->WorldToScreenMatrix(); return CRender::ClipTransformWithProjection ( worldToScreen, point, pClip ); } //----------------------------------------------------------------------------- // Purpose: Given a point, return the screen position in pixels //----------------------------------------------------------------------------- bool CRender::ScreenTransform( const Vector& point, Vector* pScreen ) { bool retval = ClipTransform( point, pScreen ); pScreen->x = 0.5f * ( pScreen->x + 1.0f ) * CurrentView().width + CurrentView().x; pScreen->y = 0.5f * ( pScreen->y + 1.0f ) * CurrentView().height + CurrentView().y; return retval; } void CRender::ViewDrawFade( byte *color, IMaterial* pFadeMaterial ) { if ( !color || !color[3] ) return; if( !pFadeMaterial ) return; const CViewSetup &view = CurrentView(); CMatRenderContextPtr pRenderContext( materials ); pRenderContext->Bind( pFadeMaterial ); pFadeMaterial->AlphaModulate( color[3] * ( 1.0f / 255.0f ) ); pFadeMaterial->ColorModulate( color[0] * ( 1.0f / 255.0f ), color[1] * ( 1.0f / 255.0f ), color[2] * ( 1.0f / 255.0f ) ); bool bOldIgnoreZ = pFadeMaterial->GetMaterialVarFlag( MATERIAL_VAR_IGNOREZ ); pFadeMaterial->SetMaterialVarFlag( MATERIAL_VAR_IGNOREZ, true ); int nTexWidth, nTexHeight; nTexWidth = pFadeMaterial->GetMappingWidth(); nTexHeight = pFadeMaterial->GetMappingHeight(); float flUOffset = 0.5f / nTexWidth; float flVOffset = 0.5f / nTexHeight; pRenderContext->MatrixMode( MATERIAL_PROJECTION ); pRenderContext->PushMatrix(); pRenderContext->LoadIdentity(); pRenderContext->Scale( 1, -1, 1 ); pRenderContext->Ortho( 0, 0, view.width, view.height, -99999, 99999 ); pRenderContext->MatrixMode( MATERIAL_MODEL ); pRenderContext->PushMatrix(); pRenderContext->LoadIdentity(); pRenderContext->MatrixMode( MATERIAL_VIEW ); pRenderContext->PushMatrix(); pRenderContext->LoadIdentity(); IMesh* pMesh = pRenderContext->GetDynamicMesh(); CMeshBuilder meshBuilder; meshBuilder.Begin( pMesh, MATERIAL_QUADS, 1 ); float flOffset = 0.5f; // Note - the viewport has already adjusted the origin float x1=0.0f - flOffset; float x2=view.width - flOffset; float y1=0.0f - flOffset; float y2=view.height - flOffset; // adjust nominal uvs to reflect adjusted xys float u1=FLerp(flUOffset, 1-flUOffset,view.x,view.x+view.width,x1); float u2=FLerp(flUOffset, 1-flUOffset,view.x,view.x+view.width,x2); float v1=FLerp(flVOffset, 1-flVOffset,view.y,view.y+view.height,y1); float v2=FLerp(flVOffset, 1-flVOffset,view.y,view.y+view.height,y2); for ( int corner=0; corner<4; corner++ ) { bool left=(corner==0) || (corner==3); meshBuilder.Position3f( (left) ? x1 : x2, (corner & 2) ? y2 : y1, 0.0f ); meshBuilder.TexCoord2f( 0, (left) ? u1 : u2, (corner & 2) ? v2 : v1 ); meshBuilder.AdvanceVertex(); } meshBuilder.End(); pMesh->Draw(); pRenderContext->MatrixMode( MATERIAL_MODEL ); pRenderContext->PopMatrix(); pRenderContext->MatrixMode( MATERIAL_VIEW ); pRenderContext->PopMatrix(); pRenderContext->MatrixMode( MATERIAL_PROJECTION ); pRenderContext->PopMatrix(); pFadeMaterial->SetMaterialVarFlag( MATERIAL_VAR_IGNOREZ, bOldIgnoreZ ); } void CRender::ExtractFrustumPlanes( Frustum frustumPlanes ) { const CViewSetup &view = CurrentView(); GeneratePerspectiveFrustum( CurrentViewOrigin(), CurrentViewForward(), CurrentViewRight(), CurrentViewUp(), view.zNear, view.zFar, view.fov, m_yFOV, g_Frustum ); // Copy out to the planes that the engine renderer uses. for( int i=0; i < FRUSTUM_NUMPLANES; i++ ) { frustumPlanes[i].m_Normal = g_Frustum.GetPlane(i)->normal; frustumPlanes[i].m_Dist = g_Frustum.GetPlane(i)->dist; } } void CRender::OrthoExtractFrustumPlanes( Frustum frustumPlanes ) { const CViewSetup &view = CurrentView(); // Setup the near and far planes. float orgOffset = DotProduct(CurrentViewOrigin(), CurrentViewForward()); frustumPlanes[FRUSTUM_FARZ].m_Normal = -CurrentViewForward(); frustumPlanes[FRUSTUM_FARZ].m_Dist = -view.zFar - orgOffset; frustumPlanes[FRUSTUM_NEARZ].m_Normal = CurrentViewForward(); frustumPlanes[FRUSTUM_NEARZ].m_Dist = view.zNear + orgOffset; // Left and right planes... orgOffset = DotProduct(CurrentViewOrigin(), CurrentViewRight()); frustumPlanes[FRUSTUM_LEFT].m_Normal = CurrentViewRight(); frustumPlanes[FRUSTUM_LEFT].m_Dist = view.m_OrthoLeft + orgOffset; frustumPlanes[FRUSTUM_RIGHT].m_Normal = -CurrentViewRight(); frustumPlanes[FRUSTUM_RIGHT].m_Dist = -view.m_OrthoRight - orgOffset; // Top and buttom planes... orgOffset = DotProduct(CurrentViewOrigin(), CurrentViewUp()); frustumPlanes[FRUSTUM_TOP].m_Normal = CurrentViewUp(); frustumPlanes[FRUSTUM_TOP].m_Dist = view.m_OrthoTop + orgOffset; frustumPlanes[FRUSTUM_BOTTOM].m_Normal = -CurrentViewUp(); frustumPlanes[FRUSTUM_BOTTOM].m_Dist = -view.m_OrthoBottom - orgOffset; // Copy out to the planes that the engine renderer uses. for(int i=0; i < FRUSTUM_NUMPLANES; i++) { /* if (fabs(frustumPlanes[i].m_Normal.x) - 1.0f > -1e-3) frustum[i].type = PLANE_X; else if (fabs(frustumPlanes[i].m_Normal.y) - 1.0f > -1e-3) frustum[i].type = PLANE_Y; else if (fabs(frustumPlanes[i].m_Normal.z) - 1.0f > -1e-3) frustum[i].type = PLANE_Z; else */ g_Frustum.SetPlane( i, PLANE_ANYZ, frustumPlanes[i].m_Normal, frustumPlanes[i].m_Dist ); } } void CRender::OverrideViewFrustum( Frustum custom ) { // Copy out to the planes that the engine renderer uses. for( int i = 0; i != FRUSTUM_NUMPLANES; ++i ) { g_Frustum.SetPlane( i, PLANE_ANYZ, custom[i].m_Normal, custom[i].m_Dist ); } } void CRender::ExtractMatrices( void ) { m_matrixView = m_ViewStack.Top().m_matrixView; m_matrixProjection = m_ViewStack.Top().m_matrixProjection; m_matrixWorldToScreen = m_ViewStack.Top().m_matrixWorldToScreen; } void ComputeViewMatrix( VMatrix *pViewMatrix, const Vector &origin, const QAngle &angles ) { static VMatrix baseRotation; static bool bDidInit; if ( !bDidInit ) { MatrixBuildRotationAboutAxis( baseRotation, Vector( 1, 0, 0 ), -90 ); MatrixRotate( baseRotation, Vector( 0, 0, 1 ), 90 ); bDidInit = true; } *pViewMatrix = baseRotation; MatrixRotate( *pViewMatrix, Vector( 1, 0, 0 ), -angles[2] ); MatrixRotate( *pViewMatrix, Vector( 0, 1, 0 ), -angles[0] ); MatrixRotate( *pViewMatrix, Vector( 0, 0, 1 ), -angles[1] ); MatrixTranslate( *pViewMatrix, -origin ); } void CRender::SetViewport( int x, int y, int w, int h ) { int x2, y2; int windowWidth = w, windowHeight = h; CMatRenderContextPtr pRenderContext( materials ); // set the viewport to be out to the size of the render target, unless explicitly told not to if (!CurrentView().m_bRenderToSubrectOfLargerScreen) { pRenderContext->GetRenderTargetDimensions( windowWidth, windowHeight ); } x2 = (x + w); y2 = (windowHeight - (y + h)); y = (windowHeight - y); // fudge around because of frac screen scale if (x > 0) x--; if (x2 < windowWidth) x2++; if (y2 < 0) y2--; if (y < windowHeight) y++; w = x2 - x; h = y - y2; pRenderContext->Viewport( x, y2, w, h ); } void DrawLightmapPage( int lightmapPageID ) { // assumes that we are already in ortho mode. int lightmapPageWidth, lightmapPageHeight; CMatRenderContextPtr pRenderContext( materials ); IMesh* pMesh = pRenderContext->GetDynamicMesh( true, NULL, NULL, g_materialDebugLightmap ); // pRenderContext->Bind( g_materialWireframe ); // IMesh* pMesh = pRenderContext->GetDynamicMesh( g_materialWireframe ); materials->GetLightmapPageSize( lightmapPageID, &lightmapPageWidth, &lightmapPageHeight ); pRenderContext->BindLightmapPage( lightmapPageID ); CMeshBuilder meshBuilder; meshBuilder.Begin( pMesh, MATERIAL_QUADS, 1 ); #ifndef _XBOX int x = 0; int y = 0; #else // xboxissue - border safe int x = 32; int y = 32; #endif float s = 1.0f; float t = 1.0f; // texcoord 1 is lightmaptexcoord for fixed function. meshBuilder.TexCoord2f( 1, 0.0f, 0.0f ); meshBuilder.Position3f( x, y, 0.0f ); meshBuilder.AdvanceVertex(); meshBuilder.TexCoord2f( 1, s, 0.0f ); meshBuilder.Position3f( x+lightmapPageWidth, y, 0.0f ); meshBuilder.AdvanceVertex(); meshBuilder.TexCoord2f( 1, s, t ); meshBuilder.Position3f( x+lightmapPageWidth, y+lightmapPageHeight, 0.0f ); meshBuilder.AdvanceVertex(); meshBuilder.TexCoord2f( 1, 0.0f, t ); meshBuilder.Position3f( x, y+lightmapPageHeight, 0.0f ); meshBuilder.AdvanceVertex(); meshBuilder.End(); pMesh->Draw(); } //hack extern void DebugDrawLightmapAtCrossHair(); void R_DrawLightmaps( IWorldRenderList *pList, int pageId ) { #ifdef USE_CONVARS if ( pageId != -1 ) { DrawLightmapPage( pageId ); Shader_DrawLightmapPageChains( pList, pageId ); } #endif } void R_CheckForLightingConfigChanges() { tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); UpdateStudioRenderConfig(); UpdateMaterialSystemConfig(); if( MaterialConfigLightingChanged() || g_RebuildLightmaps ) { ClearMaterialConfigLightingChanged(); ConMsg( "Redownloading all lightmaps\n" ); BuildGammaTable( 2.2f, 2.2f, 0.0f, OVERBRIGHT ); R_RedownloadAllLightmaps(); StaticPropMgr()->RecomputeStaticLighting(); } } void CRender::DrawSceneBegin( void ) { R_CheckForLightingConfigChanges(); } void CRender::DrawSceneEnd( void ) { R_SceneEnd(); LeafVisDraw(); } IWorldRenderList * CRender::CreateWorldList() { return AllocWorldRenderList(); } // JasonM TODO: optimize in the case of shadow depth mapping (i.e. don't update lightmaps) void CRender::BuildWorldLists( IWorldRenderList *pList, WorldListInfo_t* pInfo, int iForceViewLeaf, const VisOverrideData_t* pVisData, bool bShadowDepth, float *pWaterReflectionHeight ) { Assert( pList ); Assert( m_iLightmapUpdateDepth > 0 || g_LightmapUpdateList.Count() == 0 ); if ( !bShadowDepth ) { BeginUpdateLightmaps(); } R_BuildWorldLists( pList, pInfo, iForceViewLeaf, pVisData, bShadowDepth, pWaterReflectionHeight ); if ( !bShadowDepth ) { EndUpdateLightmaps(); } Assert( m_iLightmapUpdateDepth > 0 || g_LightmapUpdateList.Count() == 0 ); } void CRender::DrawWorldLists( IWorldRenderList *pList, unsigned long flags, float flWaterZAdjust ) { Assert( pList ); R_DrawWorldLists( pList, flags, flWaterZAdjust ); }