diff --git a/trinity/CMakeLists.txt b/trinity/CMakeLists.txt index 1c92eedee..749f5c35c 100644 --- a/trinity/CMakeLists.txt +++ b/trinity/CMakeLists.txt @@ -832,6 +832,8 @@ set(_SOURCES Eve/EveRootTransform.cpp Eve/EveRootTransform.h Eve/EveRootTransform_Blue.cpp + Eve/EveSceneView.cpp + Eve/EveSceneView.h Eve/EveSpaceScene.cpp Eve/EveSpaceScene.h Eve/EveSpaceScene_Blue.cpp diff --git a/trinity/Eve/EveSceneView.cpp b/trinity/Eve/EveSceneView.cpp new file mode 100644 index 000000000..4750c58a8 --- /dev/null +++ b/trinity/Eve/EveSceneView.cpp @@ -0,0 +1,58 @@ +// Copyright © 2026 CCP ehf. + +#include "StdAfx.h" +#include "EveSceneView.h" + +namespace EveSceneView +{ +Camera::Camera( const Matrix& view, const Matrix& projection, const TextureSize2D& renderSize, const Matrix& jitterMatrix, float mipLodBias, float upscalingAmount ) +{ + this->view = view; + this->projection = projection; + this->jitterMatrix = jitterMatrix; + this->renderSize = renderSize; + this->mipLodBias = mipLodBias; + this->upscalingAmount = upscalingAmount; + + const Matrix jitteredProjection = projection * jitterMatrix; + const Matrix inverseView = Inverse( view ); + const Vector3 position( inverseView._41, inverseView._42, inverseView._43 ); + + CTriViewport viewport; + viewport.width = renderSize.width; + viewport.height = renderSize.height; + + this->frustum.DeriveFrustum( &this->view, &position, &jitteredProjection, viewport ); +} + +ViewInfo ViewInfo::Main( bool distortion ) +{ + ViewInfo view; + view.reason = TR2RENDERREASON_NORMAL; + view.cullMode = Tr2RenderContextEnum::CULLMODE_CW; + view.distortion = distortion; + view.sceneObjects = true; + view.temporal = true; + view.subsurfaceScattering = true; + view.gpuParticles = true; + view.lensflares = true; + return view; +} + +ViewInfo ViewInfo::ReflectionFace( bool lensflares ) +{ + ViewInfo view; + view.reason = TR2RENDERREASON_REFLECTION; + // Faces are rendered into a left handed cubemap, so the caller inverts the cull mode + // and the scene must not override it + view.cullMode = Tr2RenderContextEnum::CULLMODE_NONE; + view.distortion = false; + view.sceneObjects = false; + // faces must not disturb the main view's history + view.temporal = false; + view.subsurfaceScattering = false; + view.gpuParticles = false; + view.lensflares = lensflares; + return view; +} +} diff --git a/trinity/Eve/EveSceneView.h b/trinity/Eve/EveSceneView.h new file mode 100644 index 000000000..10ba956d8 --- /dev/null +++ b/trinity/Eve/EveSceneView.h @@ -0,0 +1,56 @@ +// Copyright © 2026 CCP ehf. + +#pragma once + +#include "TriFrustum.h" +#include "ITr2Renderable.h" +#include "Tr2GpuResourcePool.h" + +namespace EveSceneView +{ +// Where a view looks from. Constructor derives the frustum from the other fields, so build a +// new Camera rather than changing one. +struct Camera +{ + Matrix view; + Matrix projection; + Matrix jitterMatrix; + TextureSize2D renderSize; + float mipLodBias = 0.0f; + float upscalingAmount = 1.0f; + // derived from the view, the jittered projection and renderSize, never from the bound viewport + TriFrustum frustum; + + Camera( const Matrix& view, const Matrix& projection, const TextureSize2D& renderSize, const Matrix& jitterMatrix = IdentityMatrix(), float mipLodBias = 0.0f, float upscalingAmount = 1.0f ); +}; + +// What the scene renders for a view. The scene reads these flags rather than deciding per +// call site what a kind of view gets. The flags are off by default and every factory sets +// every field, so a new field has to be decided for each kind of view. +struct ViewInfo +{ + // Passed on to GetBatches, so renderables can filter themselves (e.g. ShouldReflect) + Tr2RenderReason reason = TR2RENDERREASON_NORMAL; + Tr2RenderContextEnum::CullMode cullMode = Tr2RenderContextEnum::CULLMODE_CW; + bool distortion = false; + // Gather the scene's objects, camera attachments, static particles and impostors. + // Otherwise only the reflectable renderables in the component registry are gathered. + bool sceneObjects = false; + // The view feeds the next frame: view history for reprojection, temporal volumetrics + // and lens flare occlusion queries + bool temporal = false; + bool subsurfaceScattering = false; + bool gpuParticles = false; + bool lensflares = false; + + // Render the scene as a reflection probe sees it: damped reflection intensity, + // reflection background and the reflection shadow map + bool IsReflection() const + { + return reason == TR2RENDERREASON_REFLECTION; + } + + static ViewInfo Main( bool distortion ); + static ViewInfo ReflectionFace( bool lensflares ); +}; +} diff --git a/trinity/Eve/EveSpaceScene.cpp b/trinity/Eve/EveSpaceScene.cpp index 0406b9975..45f38264b 100644 --- a/trinity/Eve/EveSpaceScene.cpp +++ b/trinity/Eve/EveSpaceScene.cpp @@ -146,7 +146,6 @@ TriFrustum CreatePickingFrustum() } } - struct ShadowReceiver { float estimatedSize; @@ -217,8 +216,8 @@ EveSpaceScene::EveSpaceScene( IRoot* lockobj ) : m_projectionLast( IdentityMatrix() ), m_jitteredProjection( IdentityMatrix() ), m_reprojectionMatrix( IdentityMatrix() ), - m_jitter( 0.f, 0.f, 0.f, 0.f ), - m_upscalingAmount( 1.0f ) + m_upscalingAmount( 1.0f ), + m_mipLodBias( 0.0f ) { TriPoolAllocator* allocator = Tr2Renderer::GetPoolAllocator(); m_primaryBatches[TRIBATCHTYPE_OPAQUE] = CCP_NEW( "EveSpaceScene/m_batches" ) TriRenderBatchAccumulator( allocator ); @@ -471,26 +470,52 @@ void EveSpaceScene::Update( Be::Time realTime, Be::Time simTime ) if( frame == m_lastUpdateFrame ) { - // already updated this frame - - TriFrustum frustum; - frustum.DeriveFrustum( &Tr2Renderer::GetViewTransform(), &Tr2Renderer::GetViewPosition(), &Tr2Renderer::GetProjectionTransform(), Tr2Renderer::GetViewport() ); - - m_updateContext.SetFrustum( frustum ); - m_updateContext.SetHighDetailThreshold( g_eveSpaceSceneHighDetailThreshold / m_upscalingAmount ); - m_updateContext.SetMediumDetailThreshold( g_eveSpaceSceneMediumDetailThreshold / m_upscalingAmount ); - m_updateContext.SetLowDetailThreshold( g_eveSpaceSceneLowDetailThreshold / m_upscalingAmount ); - m_updateContext.SetVisibilityThreshold( g_eveSpaceSceneVisibilityThreshold / m_upscalingAmount ); - m_updateContext.SetLodFactor( g_eveSpaceSceneLODFactor / m_upscalingAmount ); - m_updateContext.m_raytracingEnabled = m_shadowQuality == ShadowQuality::SHADOW_RAYTRACED && m_enableShadows; - - // update the combined postprocess attributes + // already updated this frame, but another render driver may have changed the + // post-process settings since UpdatePostProcessAttributes(); return; } m_lastUpdateFrame = frame; } + UpdateFrame( realTime, simTime ); +} + + +// -------------------------------------------------------------------------------------- +// Description: +// Per-view part of the update: set the view's camera matrices and rendering parameters, +// apply the jittered projection to the renderer, and set the frustum and the detail +// thresholds that culling and LOD use. Called for every view before the frame's Update +// and before it is rendered. Only depends on the camera, so it is safe to call more than +// once for a view. +// -------------------------------------------------------------------------------------- +void EveSpaceScene::SetCamera( const EveSceneView::Camera& camera ) +{ + m_projection = camera.projection; + m_jitterMatrix = camera.jitterMatrix; + m_jitteredProjection = m_projection * m_jitterMatrix; + m_reprojectionMatrix = Inverse( m_projection ) * Inverse( camera.view ) * m_viewLast * m_projectionLast; + m_upscalingAmount = camera.upscalingAmount; + m_mipLodBias = camera.mipLodBias; + Tr2Renderer::SetProjectionTransform( m_jitteredProjection ); + + m_updateContext.SetFrustum( camera.frustum ); + m_updateContext.SetHighDetailThreshold( g_eveSpaceSceneHighDetailThreshold / camera.upscalingAmount ); + m_updateContext.SetMediumDetailThreshold( g_eveSpaceSceneMediumDetailThreshold / camera.upscalingAmount ); + m_updateContext.SetLowDetailThreshold( g_eveSpaceSceneLowDetailThreshold / camera.upscalingAmount ); + m_updateContext.SetVisibilityThreshold( g_eveSpaceSceneVisibilityThreshold / camera.upscalingAmount ); + m_updateContext.SetLodFactor( g_eveSpaceSceneLODFactor / camera.upscalingAmount ); + m_updateContext.m_raytracingEnabled = m_shadowQuality == ShadowQuality::SHADOW_RAYTRACED && m_enableShadows; +} + +// -------------------------------------------------------------------------------------- +// Description: +// Per-frame part of the update: animates and updates all objects, using the camera set +// with SetCamera before it for LOD. +// -------------------------------------------------------------------------------------- +void EveSpaceScene::UpdateFrame( Be::Time realTime, Be::Time simTime ) +{ if( !m_update ) { return; @@ -500,17 +525,6 @@ void EveSpaceScene::Update( Be::Time realTime, Be::Time simTime ) m_updateContext.UpdateOrigin( m_ballpark ); m_updateContext.SetDataTextureManager( m_dataTextureMgr ); - TriFrustum frustum; - frustum.DeriveFrustum( &Tr2Renderer::GetViewTransform(), &Tr2Renderer::GetViewPosition(), &Tr2Renderer::GetProjectionTransform(), Tr2Renderer::GetViewport() ); - - m_updateContext.SetFrustum( frustum ); - m_updateContext.SetHighDetailThreshold( g_eveSpaceSceneHighDetailThreshold / m_upscalingAmount ); - m_updateContext.SetMediumDetailThreshold( g_eveSpaceSceneMediumDetailThreshold / m_upscalingAmount ); - m_updateContext.SetLowDetailThreshold( g_eveSpaceSceneLowDetailThreshold / m_upscalingAmount ); - m_updateContext.SetVisibilityThreshold( g_eveSpaceSceneVisibilityThreshold / m_upscalingAmount ); - m_updateContext.SetLodFactor( g_eveSpaceSceneLODFactor / m_upscalingAmount ); - m_updateContext.m_raytracingEnabled = m_shadowQuality == ShadowQuality::SHADOW_RAYTRACED && m_enableShadows; - { TRINITY_STATS_ZONE( "UpdateBackgroundObjects" ); @@ -1309,71 +1323,43 @@ bool EveSpaceScene::RenderDistortionBatches( BatchMap& batches, const Tr2Texture return true; } -void EveSpaceScene::Jitter( Tr2RenderContext& renderContext ) +// -------------------------------------------------------------------------------------- +// Description: +// Lower the reflection intensity for the objects rendered into the reflection (so the +// reflections in the reflections don't get brighter and brighter). We cap it at 0.8 +// for no good reason, just felt like a good number to cap it at. +// -------------------------------------------------------------------------------------- +static float GetReflectionIntensityInReflections( float reflectionIntensity ) { - m_projection = Tr2Renderer::GetProjectionTransform(); - - auto upscalingInfo = renderContext.GetPrimaryRenderContext().GetUpscalingInfo( Tr2Renderer::GetUpscalingContextID() ); - if( upscalingInfo.technique != Tr2UpscalingAL::NONE && upscalingInfo.temporal ) + if( reflectionIntensity == 0.0f ) { - m_jitter.x = upscalingInfo.jitterX; - m_jitter.y = upscalingInfo.jitterY; - m_jitterMatrix = TranslationMatrix( Vector3( m_jitter.x, m_jitter.y, 0 ) ); - m_jitteredProjection = m_projection * m_jitterMatrix; - } - else if( m_sceneDefaultPostProcess && m_sceneDefaultPostProcess->GetTaaIfAvailable() != nullptr ) - { - auto rtWidth = renderContext.m_esm.GetRenderTargetWidth(); - auto rtHeight = renderContext.m_esm.GetRenderTargetHeight(); - const Vector2 samplingPatterns[] = { Vector2( .125f, -.375f ), - Vector2( -.125f, .375f ), - Vector2( .375f, .125f ), - Vector2( -.375f, -.125f ) }; - - auto frame = renderContext.GetPrimaryRenderContext().GetRecordingFrameNumber(); - auto samplingIndex = frame % ( sizeof( samplingPatterns ) / sizeof( samplingPatterns[0] ) ); - - m_jitter.x = 2.0f * samplingPatterns[samplingIndex].x / float( rtWidth ); - m_jitter.y = 2.0f * samplingPatterns[samplingIndex].y / float( rtHeight ); - - auto currJitterOffset = Vector3( m_jitter.x, m_jitter.y, 0 ); - - m_jitterMatrix = TranslationMatrix( currJitterOffset ); - m_jitteredProjection = m_projection * m_jitterMatrix; - } - else - { - m_jitterMatrix = IdentityMatrix(); - m_jitteredProjection = m_projection; - m_jitter.x = m_jitter.y = 0.0f; + return reflectionIntensity; } + return min( 0.8f, 1.0f / ( reflectionIntensity * reflectionIntensity ) ); } - // -------------------------------------------------------------------------------------- // Description: -// Set up rendering states, frustum and gather all batches. +// Work that does not depend on the view being rendered. Called once per frame, after +// the frame's Update and before the first BeginView. // -------------------------------------------------------------------------------------- -void EveSpaceScene::BeginRender( bool enableDistortion, Tr2RenderContext& renderContext ) +void EveSpaceScene::BeginFrame( Tr2RenderContext& renderContext ) { - TRINITY_STATS_ZONE( __FUNCTION__ ); - if( !m_display ) { return; } + TRINITY_STATS_ZONE( __FUNCTION__ ); renderContext.AddGpuMarker( __FUNCTION__ ); - if( m_visualizeMethod != VM_NONE ) - { - if( !m_visualizerEffects[m_visualizeMethod].effect ) - { - m_visualizerEffects[m_visualizeMethod].effect.CreateInstance(); - const char* path = VISUALIZER_EFFECT_PATH[m_visualizeMethod]; - m_visualizerEffects[m_visualizeMethod].effect->SetEffectPathName( path ); - } - } + // Ensure that the camera attachment parent is updated to the current view transform, + // so that any attached objects are in the correct position for the frame. + m_cameraAttachmentParent->SetTransform( Tr2Renderer::GetInverseViewTransform() ); + m_cameraAttachmentParent->UpdateSyncronous( m_updateContext ); + m_cameraAttachmentParent->UpdateAsyncronous( m_updateContext ); + + EnsureVisualizerEffect(); if( m_shLightingManager ) { @@ -1385,102 +1371,164 @@ void EveSpaceScene::BeginRender( bool enableDistortion, Tr2RenderContext& render m_dataTextureMgr->SetVariables(); } - Jitter( renderContext ); - - Tr2Renderer::SetProjectionTransform( m_jitteredProjection ); - m_reprojectionMatrix = Inverse( m_projection ) * Inverse( Tr2Renderer::GetViewTransform() ) * m_viewLast * m_projectionLast; + m_instancedMeshManager->CollectMeshes( *m_componentRegistry ); - m_velocityMapDirty = false; + ResolveLightingOverrides(); + // fog settings and the environment map accumulate over time, so they must only advance once per frame + if( m_volumetricsRenderer ) { - std::vector overrides; - m_componentRegistry->ProcessComponents( [&overrides]( IEveLightingOverride* component ) -> void { - overrides.push_back( component->GetOverrides() ); - } ); - sort( begin( overrides ), end( overrides ), []( const auto& a, const auto& b ) { - return a.priority > b.priority; - } ); + m_volumetricsRenderer->UpdateFogSettings( *m_componentRegistry, m_updateContext ); + UpdateVariableStore(); + m_volumetricsRenderer->UpdateFogEnvironmentMap( renderContext ); + } +} - IEveLightingOverride::OverrideInfo baseline; - baseline.priority = (PostProcessEnums::Priority)-1; - baseline.intensity = 1; - auto sunColor = m_useSunColorWithDynamicLights && g_eveSpaceSceneDynamicLighting ? m_sunColorWithDynamicLights : m_sunColor; - baseline.value.sunIntensity = std::max( { sunColor.r, sunColor.g, sunColor.b } ); - if( baseline.value.sunIntensity != 0 ) - { - baseline.value.sunColor = sunColor * ( 1.f / baseline.value.sunIntensity ); - } - else - { - baseline.value.sunColor = sunColor; - } - baseline.value.backgroundIntensity = m_nebulaIntensity; - baseline.value.reflectionIntensity = m_reflectionIntensity; - overrides.push_back( baseline ); - auto over = SimplePriorityBlend( overrides ); - m_currentSunColor = over.sunColor * over.sunIntensity; - m_currentNebulaIntensity = over.backgroundIntensity; - m_currentReflectionIntensity = over.reflectionIntensity; +// -------------------------------------------------------------------------------------- +// Description: +// Per-view setup: lighting values, states, and frustum dependent gathering of batches. +// Called for the main view and for each reflection face, after BeginFrame. +// -------------------------------------------------------------------------------------- +void EveSpaceScene::BeginView( const EveSceneView::ViewInfo& view, Tr2RenderContext& renderContext ) +{ + TRINITY_STATS_ZONE( __FUNCTION__ ); + + if( !m_display ) + { + return; } + renderContext.AddGpuMarker( __FUNCTION__ ); + + m_velocityMapDirty = false; + m_currentReflectionIntensity = view.IsReflection() ? GetReflectionIntensityInReflections( m_baseReflectionIntensity ) : m_baseReflectionIntensity; + + // Set for every view rather than set and reset in pairs, so a view never inherits + // the previous view's value. It is read whenever the per-frame data is populated. if( m_volumetricsRenderer ) { - m_volumetricsRenderer->UpdateFogSettings( *m_componentRegistry, m_updateContext ); - UpdateVariableStore(); - m_volumetricsRenderer->UpdateFogEnvironmentMap( renderContext ); + m_volumetricsRenderer->SetIsReflectionProbe( view.IsReflection() ); } - renderContext.m_esm.BeginManagedRendering(); + renderContext.m_esm.BeginManagedRendering( view.cullMode ); renderContext.m_esm.ApplyStandardStates( Tr2EffectStateManager::RM_OPAQUE ); - GatherBatches( enableDistortion, renderContext ); + GatherBatches( view, renderContext ); - if( m_shadowQuality == ShadowQuality::SHADOW_RAYTRACED && m_enableShadows ) + // reflection views use the reflection shadow map, which is not raytraced + if( !view.IsReflection() && m_shadowQuality == ShadowQuality::SHADOW_RAYTRACED && m_enableShadows ) { PrepareRaytracedShadows( renderContext ); } UpdateVariableStore(); - if( auto lightManager = Tr2LightManager::GetInstance() ) + PrepareLensflares(); + + PopulatePerFramePSData( m_perFramePS, renderContext ); + PopulatePerFrameVSData( m_perFrameVS, renderContext ); + ApplyPerFrameData( renderContext ); +} + +void EveSpaceScene::EnsureVisualizerEffect() +{ + if( m_visualizeMethod != VM_NONE && !m_visualizerEffects[m_visualizeMethod].effect ) { - TRINITY_STATS_SCOPED_TIME( gatherDynamicLights ); + m_visualizerEffects[m_visualizeMethod].effect.CreateInstance(); + const char* path = VISUALIZER_EFFECT_PATH[m_visualizeMethod]; + m_visualizerEffects[m_visualizeMethod].effect->SetEffectPathName( path ); + } +} - lightManager->SetShadowQuality( m_shadowQuality, renderContext.GetPrimaryRenderContextPointer()->GetRecordingFrameNumber() ); - lightManager->Clear( renderContext ); - lightManager->SetFrustum( m_updateContext.GetFrustum() ); - lightManager->AdjustLightCutoff( m_updateContext.GetLodFactor() ); +// -------------------------------------------------------------------------------------- +// Description: +// Blend the lighting overrides into the frame's sun color and nebula intensity, and +// into the base reflection intensity that BeginView derives each view's from. +// -------------------------------------------------------------------------------------- +void EveSpaceScene::ResolveLightingOverrides() +{ + std::vector overrides; + m_componentRegistry->ProcessComponents( [&overrides]( IEveLightingOverride* component ) -> void { + overrides.push_back( component->GetOverrides() ); + } ); + sort( begin( overrides ), end( overrides ), []( const auto& a, const auto& b ) { + return a.priority > b.priority; + } ); - auto lightOwners = m_componentRegistry->GetComponents(); + IEveLightingOverride::OverrideInfo baseline; + baseline.priority = (PostProcessEnums::Priority)-1; + baseline.intensity = 1; + auto sunColor = m_useSunColorWithDynamicLights && g_eveSpaceSceneDynamicLighting ? m_sunColorWithDynamicLights : m_sunColor; + baseline.value.sunIntensity = std::max( { sunColor.r, sunColor.g, sunColor.b } ); + if( baseline.value.sunIntensity != 0 ) + { + baseline.value.sunColor = sunColor * ( 1.f / baseline.value.sunIntensity ); + } + else + { + baseline.value.sunColor = sunColor; + } + baseline.value.backgroundIntensity = m_nebulaIntensity; + baseline.value.reflectionIntensity = m_reflectionIntensity; + overrides.push_back( baseline ); - Tr2ParallelFor( Tr2BlockedRange( 0, lightOwners.size(), 20 ), [&]( Tr2BlockedRange range ) { - for( auto i = range.begin(); i != range.end(); ++i ) - { - ITr2LightOwnerPtr lightOwner = lightOwners[i]; - lightOwner->GetLights( *lightManager ); - } - } ); + auto over = SimplePriorityBlend( overrides ); + m_currentSunColor = over.sunColor * over.sunIntensity; + m_currentNebulaIntensity = over.backgroundIntensity; + m_baseReflectionIntensity = over.reflectionIntensity; +} - lightManager->ResolveLightData(); +// -------------------------------------------------------------------------------------- +// Description: +// Gather the dynamic lights visible in the frustum of each of the light manager's views. +// The caller sets the views (SetViews) and calls this once per frame, after the +// main view's update and BeginFrame and before the first view is rendered. +// -------------------------------------------------------------------------------------- +void EveSpaceScene::GatherDynamicLights( Tr2RenderContext& renderContext ) +{ + auto lightManager = Tr2LightManager::GetInstance(); + if( !m_display || !lightManager ) + { + return; } + TRINITY_STATS_SCOPED_TIME( gatherDynamicLights ); + + lightManager->SetShadowQuality( m_shadowQuality, renderContext.GetPrimaryRenderContextPointer()->GetRecordingFrameNumber() ); + lightManager->Clear( renderContext ); + lightManager->AdjustLightCutoff( m_updateContext.GetLodFactor() ); + + auto lightOwners = m_componentRegistry->GetComponents(); + + Tr2ParallelFor( Tr2BlockedRange( 0, lightOwners.size(), 20 ), [&]( Tr2BlockedRange range ) { + for( auto i = range.begin(); i != range.end(); ++i ) + { + ITr2LightOwnerPtr lightOwner = lightOwners[i]; + lightOwner->GetLights( *lightManager ); + } + } ); + + lightManager->ResolveLightData(); +} + +void EveSpaceScene::PrepareLensflares() +{ // the lensflares need a special pre-render update + // visibility is updated for every view, after PrepareRender, so a reflection face + // doesn't use the main view's visibility or the previous view's transform for( auto it = m_lensflares.cbegin(); it != m_lensflares.cend(); ++it ) { ( *it )->PrepareRender( m_updateContext.GetFrustum() ); + ( *it )->UpdateVisibility( m_updateContext ); } - - PopulatePerFramePSData( m_perFramePS, renderContext ); - PopulatePerFrameVSData( m_perFrameVS, renderContext ); - ApplyPerFrameData( renderContext ); } // -------------------------------------------------------------------------------------- // Description: // Gather all batches into the m_primaryBatches BatchMap. // -------------------------------------------------------------------------------------- -void EveSpaceScene::GatherBatches( bool includeDistortions, Tr2RenderContext& renderContext ) +void EveSpaceScene::GatherBatches( const EveSceneView::ViewInfo& view, Tr2RenderContext& renderContext ) { TRINITY_STATS_ZONE( __FUNCTION__ ); @@ -1490,33 +1538,72 @@ void EveSpaceScene::GatherBatches( bool includeDistortions, Tr2RenderContext& re Tr2RenderableSortList transparentObjects; const Matrix& identity = IdentityMatrix(); + // Reflection views only get the dynamic objects once the probe is ready for them + const bool gatherObjects = view.sceneObjects || ( m_dynamicObjectReflectionEnabled && m_reflectionProbe && m_reflectionProbe->ReadyForDynamicObjectReflections() ); + { TRINITY_STATS_ZONE( "UpdateVisibility" ); - Tr2ParallelFor( Tr2BlockedRange( 0, m_objects.size(), GrainSize( m_objects.size(), 32 ) ), [&]( Tr2BlockedRange range ) { - for( size_t i = range.begin(); i != range.end(); ++i ) - { - m_objects[i]->UpdateVisibility( m_updateContext, identity ); - } + if( gatherObjects ) + { + Tr2ParallelFor( Tr2BlockedRange( 0, m_objects.size(), GrainSize( m_objects.size(), 32 ) ), [&]( Tr2BlockedRange range ) { + for( size_t i = range.begin(); i != range.end(); ++i ) + { + m_objects[i]->UpdateVisibility( m_updateContext, identity ); + } + } ); + } + + Tr2ParallelDo( m_planets.begin(), m_planets.end(), [&]( EvePlanet* obj ) { + obj->UpdateZOnlyVisibility( m_updateContext ); } ); + } + + if( view.sceneObjects ) + { + GatherSceneObjectRenderables( allObjects, renderables, renderContext ); + } + else if( gatherObjects ) + { + GatherReflectionRenderables( renderables ); + } + + UpdateQuadRenderer( m_updateContext.GetFrustum(), allObjects, renderContext ); + Tr2QuadRenderer::Instance()->GetBatches( TRIBATCHTYPE_OPAQUE, m_primaryBatches[TRIBATCHTYPE_OPAQUE] ); + Tr2QuadRenderer::Instance()->GetBatches( TRIBATCHTYPE_ADDITIVE, m_primaryBatches[TRIBATCHTYPE_ADDITIVE] ); + + GetAllBatchesFromRenderables( renderables, transparentObjects, view.distortion, m_primaryBatches, view.reason ); + PrepareTransparentBatch( transparentObjects, m_primaryBatches, view.reason ); + + if( gatherObjects ) + { + // passing the reason filters out instanced meshes that should not be reflected + m_instancedMeshManager->GetBatches( m_updateContext.GetFrustum(), m_updateContext.GetInvLodFactor(), { { TRIBATCHTYPE_OPAQUE, *m_primaryBatches[TRIBATCHTYPE_OPAQUE] }, { TRIBATCHTYPE_DECAL, *m_primaryBatches[TRIBATCHTYPE_DECAL] }, { TRIBATCHTYPE_ADDITIVE, *m_primaryBatches[TRIBATCHTYPE_ADDITIVE] }, { TRIBATCHTYPE_DEPTH, *m_primaryBatches[TRIBATCHTYPE_DEPTH] }, { TRIBATCHTYPE_DISTORTION, *m_primaryBatches[TRIBATCHTYPE_DISTORTION] } }, view.reason ); + + m_instancedMeshManager->ReportUsedScreenSizes(); + } + + FinalizeBatches( m_primaryBatches ); + + UpdateShLighting( allObjects ); +} + +// -------------------------------------------------------------------------------------- +// Description: +// Gather the renderables of the scene's objects, camera attachments and static +// particles, updating the impostors on the way. +// -------------------------------------------------------------------------------------- +void EveSpaceScene::GatherSceneObjectRenderables( std::vector& allObjects, std::vector& renderables, Tr2RenderContext& renderContext ) +{ + const Matrix& identity = IdentityMatrix(); + + { + TRINITY_STATS_ZONE( "UpdateVisibility" ); - m_cameraAttachmentParent->SetTransform( Tr2Renderer::GetInverseViewTransform() ); - m_cameraAttachmentParent->UpdateSyncronous( m_updateContext ); - m_cameraAttachmentParent->UpdateAsyncronous( m_updateContext ); m_cameraAttachmentParent->UpdateVisibility( m_updateContext, identity ); Tr2ParallelDo( m_staticParticles.begin(), m_staticParticles.end(), [&]( EveSceneStaticParticles* staticParticles ) { staticParticles->UpdateVisibility( m_updateContext ); } ); - - Tr2ParallelDo( m_planets.begin(), m_planets.end(), [&]( EvePlanet* obj ) { - obj->UpdateZOnlyVisibility( m_updateContext ); - } ); - - // until we have proper support for multiple lensflares we just do it in a list... - for( auto& lensflare : m_lensflares ) - { - lensflare->UpdateVisibility( m_updateContext ); - } } // objects will be batched up. @@ -1558,23 +1645,23 @@ void EveSpaceScene::GatherBatches( bool includeDistortions, Tr2RenderContext& re { ( *it )->GetRenderables( m_updateContext.GetFrustum(), renderables ); } +} - UpdateQuadRenderer( m_updateContext.GetFrustum(), allObjects, renderContext ); - Tr2QuadRenderer::Instance()->GetBatches( TRIBATCHTYPE_OPAQUE, m_primaryBatches[TRIBATCHTYPE_OPAQUE] ); - Tr2QuadRenderer::Instance()->GetBatches( TRIBATCHTYPE_ADDITIVE, m_primaryBatches[TRIBATCHTYPE_ADDITIVE] ); - - GetAllBatchesFromRenderables( renderables, transparentObjects, includeDistortions, m_primaryBatches ); - PrepareTransparentBatch( transparentObjects, m_primaryBatches ); - - m_instancedMeshManager->CollectMeshes( *m_componentRegistry ); - - m_instancedMeshManager->GetBatches( m_updateContext.GetFrustum(), m_updateContext.GetInvLodFactor(), { { TRIBATCHTYPE_OPAQUE, *m_primaryBatches[TRIBATCHTYPE_OPAQUE] }, { TRIBATCHTYPE_DECAL, *m_primaryBatches[TRIBATCHTYPE_DECAL] }, { TRIBATCHTYPE_ADDITIVE, *m_primaryBatches[TRIBATCHTYPE_ADDITIVE] }, { TRIBATCHTYPE_DEPTH, *m_primaryBatches[TRIBATCHTYPE_DEPTH] }, { TRIBATCHTYPE_DISTORTION, *m_primaryBatches[TRIBATCHTYPE_DISTORTION] } } ); - - m_instancedMeshManager->ReportUsedScreenSizes(); - - FinalizeBatches( m_primaryBatches ); - - UpdateShLighting( allObjects ); +// -------------------------------------------------------------------------------------- +// Description: +// Gather the visible renderables from the component registry. Each renderable decides +// in GetBatches (via TR2RENDERREASON_REFLECTION / ShouldReflect) whether it shows up. +// -------------------------------------------------------------------------------------- +void EveSpaceScene::GatherReflectionRenderables( std::vector& renderables ) +{ + renderables.reserve( m_componentRegistry->ComponentCount() ); + m_componentRegistry->ProcessComponents( + [&]( ITr2Renderable* renderable ) -> void { + if( renderable->IsVisible( m_updateContext ) ) + { + renderables.push_back( renderable ); + } + } ); } void EveSpaceScene::PrepareRaytracedShadows( Tr2RenderContext& renderContext ) @@ -1817,253 +1904,13 @@ Tr2QuadRenderer* EveSpaceScene::GetQuadRenderer() const return Tr2QuadRenderer::Instance(); } -void EveSpaceScene::RenderReflectionPass( Tr2GpuResourcePool& gpuResourcePool, Tr2RenderContext& renderContext ) -{ - if( !HasReflectionProbe() || !m_display ) - { - return; - } - - TRINITY_STATS_ZONE( __FUNCTION__ ); - - // set the current reflection - GPU_REGION( renderContext, "Reflection" ); - - m_volumetricsRenderer->SetIsReflectionProbe( true ); - ON_BLOCK_EXIT( [&] { - m_volumetricsRenderer->SetIsReflectionProbe( false ); - } ); - - // lower the reflection intensity for the objects rendered into the reflection - // (so the reflections in the reflections don't get brighter and brighter) - // we cap it at 0.8 for no good reason, just felt like a good number to cap the reflection intensity in the reflections - auto tmp = m_currentReflectionIntensity; - if( m_currentReflectionIntensity != 0.0f ) - { - m_currentReflectionIntensity = min( 0.8f, 1.0f / ( m_currentReflectionIntensity * m_currentReflectionIntensity ) ); - } - - { - // Override DepthMap in the variable store - ITr2TextureProvider* bkDepthMapTexVar = nullptr; - CTr2TextureReference faceDepthBuffer; - if( m_depthMapVar.GetType() == TRIVARIABLE_TEXTURE_RES ) - { - m_depthMapVar.GetValue( bkDepthMapTexVar ); - if( bkDepthMapTexVar ) - { - bkDepthMapTexVar->Lock(); - } - - m_depthMapVar = &faceDepthBuffer; - } - ON_BLOCK_EXIT( [&] { - if( m_depthMapVar.GetType() == TRIVARIABLE_TEXTURE_RES ) - { - m_depthMapVar = bkDepthMapTexVar; - if( bkDepthMapTexVar ) - { - bkDepthMapTexVar->Unlock(); - } - } - UpdateVariableStore(); - } ); - - auto bkProjection = m_projection; - ON_BLOCK_EXIT( [&] { m_projection = bkProjection; } ); - auto bkJitter = m_jitterMatrix; - m_jitterMatrix = IdentityMatrix(); - ON_BLOCK_EXIT( [&] { m_jitterMatrix = bkJitter; } ); - - // we change the frustum during reflection updates/renderings - // we need the old one for resetting - auto normalFrustum = m_updateContext.GetFrustum(); - - - m_reflectionProbe->InitRenderPass( renderContext ); - - m_projection = Tr2Renderer::GetProjectionTransform(); - - for( unsigned i = m_reflectionProbe->GetStartFace(); i < m_reflectionProbe->GetEndFace(); i++ ) - { - std::string contextName = "Reflection Face " + std::to_string( i ); - - GPU_REGION( renderContext, contextName.c_str() ); - m_reflectionProbe->StartRenderFace( i, renderContext ); - - PopulatePerFramePSData( m_perFramePS, renderContext ); - PopulatePerFrameVSData( m_perFrameVS, renderContext ); - - ApplyPerFrameData( renderContext ); - *faceDepthBuffer.GetTexture() = m_reflectionProbe->GetDepthBuffer( i ); - - { - Matrix orgViewMatrix = Tr2Renderer::GetViewTransform(); - Matrix planetViewMatrix = CreatePlanetViewMatrix( orgViewMatrix ); - Tr2Renderer::SetViewTransform( planetViewMatrix ); - - TriFrustum frustum; - Matrix planetProjection = EveCamera::ModifyClipPlanes( Tr2Renderer::GetProjectionTransform(), 0.01f, 1e5f ); - frustum.DeriveFrustum( &Tr2Renderer::GetViewTransform(), &Tr2Renderer::GetViewPosition(), &planetProjection, renderContext.m_esm.GetViewport() ); - m_updateContext.SetFrustum( frustum ); - - for( auto& planet : m_planets ) - { - planet->UpdatePlanetVisibility( m_updateContext, m_planetScale ); - } - - Tr2Renderer::SetViewTransform( orgViewMatrix ); - } - - TriFrustum currentFrustum = m_reflectionProbe->GetFrustum( i, renderContext ); - m_updateContext.SetFrustum( currentFrustum ); - // get the background reflection renderables from the component registry - RenderBackgroundPassObjects( {}, {}, renderContext, BackgroundRenderingReason::BACKGROUND_RENDER_REFLECTION ); - - if( g_lensflaresInReflections && !m_lensflares.empty() && g_eveReflectionMode == EntityComponents::REFLECTION_SETTING_ULTRA ) - { - GPU_REGION( renderContext, "Lens Flares in reflections" ); - std::vector visible; - - // lensflares - for( auto it = m_lensflares.cbegin(); it != m_lensflares.cend(); ++it ) - { - ( *it )->GetRenderables( currentFrustum, visible ); - } - - if( !visible.empty() ) - { - renderContext.SetReadOnlyDepth( true ); - RenderRenderables( visible, - m_secondaryBatches[TRIBATCHTYPE_ADDITIVE], - TRIBATCHTYPE_ADDITIVE, - Tr2EffectStateManager::RM_ALPHA_ADDITIVE, - renderContext, - Tr2RenderReason::TR2RENDERREASON_REFLECTION ); - renderContext.SetReadOnlyDepth( false ); - } - } - - if( m_dynamicObjectReflectionEnabled && m_reflectionProbe->ReadyForDynamicObjectReflections() ) - { - std::vector visibleRenderables; - visibleRenderables.reserve( m_componentRegistry->ComponentCount() ); - - // Filter out the non-visible reflection renderables based on the current frustum - m_componentRegistry->ProcessComponents( - [&]( ITr2Renderable* renderable ) -> void { - if( renderable->IsVisible( m_updateContext ) ) - { - visibleRenderables.push_back( renderable ); - } - } ); - - bool hasFog = m_volumetricsRenderer && m_volumetricsRenderer->HasFog(); - - ClearBatches( m_secondaryBatches ); - - bool hasInstancedBatches = m_instancedMeshManager->GetBatches( - m_updateContext.GetFrustum(), - m_updateContext.GetInvLodFactor(), - { - { TRIBATCHTYPE_OPAQUE, *m_secondaryBatches[TRIBATCHTYPE_OPAQUE] }, - { TRIBATCHTYPE_DECAL, *m_secondaryBatches[TRIBATCHTYPE_DECAL] }, - { TRIBATCHTYPE_ADDITIVE, *m_secondaryBatches[TRIBATCHTYPE_ADDITIVE] }, - { TRIBATCHTYPE_DEPTH, *m_secondaryBatches[TRIBATCHTYPE_DEPTH] }, - } ) > 0; - - m_instancedMeshManager->ReportUsedScreenSizes(); - - if( hasFog || !visibleRenderables.empty() || hasInstancedBatches ) - { - // clear planets - renderContext.Clear( CLEARFLAGS_ZBUFFER, 0, 0, 0 ); - - // We are rendering in a left hand coordinate system (for cubemaps) so don't override the cullmode!!! - renderContext.m_esm.BeginManagedRendering( CullMode::CULLMODE_NONE ); - - Tr2RenderableSortList transparentObjects; - GetAllBatchesFromRenderables( visibleRenderables, transparentObjects, false, m_secondaryBatches, Tr2RenderReason::TR2RENDERREASON_REFLECTION ); - - PrepareTransparentBatch( transparentObjects, m_secondaryBatches, Tr2RenderReason::TR2RENDERREASON_REFLECTION ); - FinalizeBatches( m_secondaryBatches ); - - renderContext.m_esm.ApplyStandardStates( Tr2EffectStateManager::RM_DEPTH_ONLY ); - renderContext.RenderBatches( m_secondaryBatches[TRIBATCHTYPE_OPAQUE], BlueSharedString( "Depth" ) ); - renderContext.RenderBatches( m_secondaryBatches[TRIBATCHTYPE_DEPTH], BlueSharedString( "Depth" ) ); - - EveSpaceScene::ShadowResources shadowResources; - - if( m_enableShadows && m_shadowQuality != ShadowQuality::SHADOW_DISABLED && m_reflectionShadowMap ) - { - // We have so many ways to turn off shadows... - bool reallyHaveShadows = false; - if( m_shadowQuality == ShadowQuality::SHADOW_RAYTRACED ) - { - reallyHaveShadows = m_rtManager != nullptr; - } - else - { - reallyHaveShadows = m_cascadedShadowMap != nullptr; - } - - if( reallyHaveShadows ) - { - renderContext.m_esm.SetInvertedCullMode( false ); - shadowResources = SetupCascadedShadows( TR2RENDERREASON_REFLECTION, *m_reflectionShadowMap, currentFrustum, m_reflectionProbe->GetDepthBuffer( i ), gpuResourcePool, renderContext ); - renderContext.m_esm.SetInvertedCullMode( true ); - } - } - - // TODO: raytraced shadows here - RegisterWithVariableStore( shadowResources, gpuResourcePool ); - - renderContext.SetReadOnlyDepth( true ); - - RenderOpaqueBatches( m_secondaryBatches, renderContext ); - - Tr2GpuResourcePool::Texture froxelFog; - if( m_volumetricsRenderer ) - { - uint32_t width = Tr2Renderer::GetViewport().width; - uint32_t height = Tr2Renderer::GetViewport().height; - Vector3d origin = m_updateContext.GetOrigin(); - froxelFog = m_volumetricsRenderer->RenderFogIntoReflectionMap( renderContext, gpuResourcePool, width, height, m_sunData.DirWorld, m_currentSunColor, origin, Tr2Renderer::GetViewTransform(), Tr2Renderer::GetReversedDepthProjectionTransform() ); - } - GlobalStore().RegisterVariable( "EveSceneFroxelFogMap", froxelFog ); - - RenderTransparentBatches( m_secondaryBatches, renderContext ); - renderContext.SetReadOnlyDepth( false ); - - GlobalStore().RegisterVariable( "EveSceneFroxelFogMap", Tr2TextureAL{} ); - RegisterWithVariableStore( ShadowResources{}, gpuResourcePool ); - - ClearBatches( m_secondaryBatches ); - renderContext.m_esm.EndManagedRendering(); - } - } - } - - m_reflectionProbe->EndRenderPass( renderContext ); - - m_updateContext.SetFrustum( normalFrustum ); - - // reset the reflection intensity - m_currentReflectionIntensity = tmp; - } - - PopulatePerFramePSData( m_perFramePS, renderContext ); - PopulatePerFrameVSData( m_perFrameVS, renderContext ); - ApplyPerFrameData( renderContext ); -} - // -------------------------------------------------------------------------------------- // Description: // Render the background scene with all objects, and generate reflection map // Returns: // boolean indicating whether background distortion is required. // -------------------------------------------------------------------------------------- -bool EveSpaceScene::RenderBackgroundPass( const Tr2TextureAL& depthMap, const Tr2TextureAL& distortionMap, const Tr2TextureAL& velocityMap, Tr2RenderContext& renderContext ) +bool EveSpaceScene::RenderBackgroundPass( const Tr2TextureAL& depthMap, const Tr2TextureAL& distortionMap, const Tr2TextureAL& velocityMap, const EveSceneView::ViewInfo& view, Tr2RenderContext& renderContext ) { TRINITY_STATS_ZONE( __FUNCTION__ ); @@ -2122,7 +1969,7 @@ bool EveSpaceScene::RenderBackgroundPass( const Tr2TextureAL& depthMap, const Tr else { renderContext.RenderPassHint( { Tr2LoadAction::LOAD, Tr2StoreAction::STORE }, { Tr2LoadAction::LOAD, Tr2StoreAction::STORE } ); - hasBackgroundDistortionBatches = RenderBackgroundPassObjects( depthMap, distortionMap, renderContext, BACKGROUND_RENDER_COLOR ); + hasBackgroundDistortionBatches = RenderBackgroundPassObjects( depthMap, distortionMap, renderContext, view.IsReflection() ? BACKGROUND_RENDER_REFLECTION : BACKGROUND_RENDER_COLOR ); } if( !m_planets.empty() ) { @@ -2259,7 +2106,7 @@ bool EveSpaceScene::RenderBackgroundPassObjects( const Tr2TextureAL& depthMap, c // Description: // Render opaque objects to populate a readable depth stencil. // -------------------------------------------------------------------------------------- -void EveSpaceScene::RenderDepthPass( const Tr2TextureAL& depthMap, const Tr2TextureAL& normalMap, const Tr2TextureAL& customStencil, Tr2RenderContext& renderContext, const BlueSharedString& techniqueName ) +void EveSpaceScene::RenderDepthPass( const Tr2TextureAL& depthMap, const Tr2TextureAL& normalMap, const Tr2TextureAL& customStencil, const EveSceneView::ViewInfo& view, Tr2RenderContext& renderContext, const BlueSharedString& techniqueName ) { TRINITY_STATS_ZONE( __FUNCTION__ ); @@ -2268,7 +2115,7 @@ void EveSpaceScene::RenderDepthPass( const Tr2TextureAL& depthMap, const Tr2Text return; } - renderContext.m_esm.BeginManagedRendering(); + renderContext.m_esm.BeginManagedRendering( view.cullMode ); { // Update mesh morphs auto& meshMorphs = m_componentRegistry->GetComponents(); @@ -2676,13 +2523,40 @@ void EveSpaceScene::FinishRenderingShadowMapForLights( Tr2RenderContext& renderC ApplyPerFrameData( renderContext ); } -EveSpaceScene::ShadowResources EveSpaceScene::RenderShadows( const Tr2TextureAL& depthMap, const Tr2TextureAL& normalMap, Tr2GpuResourcePool& gpuResourcePool, Tr2RenderContext& renderContext ) +// -------------------------------------------------------------------------------------- +// Description: +// Reflection views get cascaded shadows from the reflection shadow map, whenever the +// main view has shadows. +// -------------------------------------------------------------------------------------- +EveSpaceScene::ShadowResources EveSpaceScene::RenderReflectionShadows( const Tr2TextureAL& depthMap, Tr2GpuResourcePool& gpuResourcePool, Tr2RenderContext& renderContext ) +{ + // We have so many ways to turn off shadows... + const bool haveShadows = m_shadowQuality == ShadowQuality::SHADOW_RAYTRACED ? m_rtManager != nullptr : m_cascadedShadowMap != nullptr; + if( !m_enableShadows || !m_reflectionShadowMap || !haveShadows || m_shadowQuality == ShadowQuality::SHADOW_DISABLED ) + { + return {}; + } + + // the shadow map is not rendered into the cubemap, so it must not use the inverted cull mode + bool prevCullInversion = renderContext.m_esm.IsCullModeInverted(); + renderContext.m_esm.SetInvertedCullMode( false ); + ShadowResources result = SetupCascadedShadows( TR2RENDERREASON_REFLECTION, *m_reflectionShadowMap, m_updateContext.GetFrustum(), depthMap, gpuResourcePool, renderContext ); + renderContext.m_esm.SetInvertedCullMode( prevCullInversion ); + return result; +} + +EveSpaceScene::ShadowResources EveSpaceScene::RenderShadows( const Tr2TextureAL& depthMap, const Tr2TextureAL& normalMap, Tr2GpuResourcePool& gpuResourcePool, const EveSceneView::ViewInfo& view, Tr2RenderContext& renderContext ) { if( !m_display || !m_enableShadows ) { return {}; } + if( view.IsReflection() ) + { + return RenderReflectionShadows( depthMap, gpuResourcePool, renderContext ); + } + EveSpaceScene::ShadowResources result; if( m_cascadedShadowMap && ( m_shadowQuality == ShadowQuality::SHADOW_LOW || m_shadowQuality == ShadowQuality::SHADOW_HIGH ) ) { @@ -2754,13 +2628,14 @@ bool EveSpaceScene::RenderMainPass( const Tr2TextureAL& velocityMap, const Tr2TextureAL& opaqueColorMap, Tr2GpuResourcePool& gpuResourcePool, + const EveSceneView::ViewInfo& view, Tr2RenderContext& renderContext ) { TRINITY_STATS_ZONE( __FUNCTION__ ); bool hasForegroundDistortionBatches = false; - renderContext.m_esm.BeginManagedRendering(); + renderContext.m_esm.BeginManagedRendering( view.cullMode ); if( !m_display ) { @@ -2810,14 +2685,31 @@ bool EveSpaceScene::RenderMainPass( GlobalStore().RegisterVariable( "EveSpaceSceneOpaqueMap", opaqueColorMap ); } - m_sssss->SetupScreenSpaceSubSurfaceScattering( renderContext, m_primaryBatches[TRIBATCHTYPE_OPAQUE], colorMap, opaqueColorMap, depthMap, gpuResourcePool ); + if( view.subsurfaceScattering ) + { + m_sssss->SetupScreenSpaceSubSurfaceScattering( renderContext, m_primaryBatches[TRIBATCHTYPE_OPAQUE], colorMap, opaqueColorMap, depthMap, gpuResourcePool ); + } Tr2Renderer::SetProjectionTransform( m_jitteredProjection ); PopulateAndApplyPerFrameData( renderContext ); - auto [froxelFog, volumetricSlices] = RenderVolumetrics( depthMap, gpuResourcePool, renderContext ); - GlobalStore().RegisterVariable( "EveSceneFogVolumeMap", volumetricSlices ); - GlobalStore().RegisterVariable( "EveSceneFroxelFogMap", froxelFog ); + std::pair froxelFogTextures; + + switch( view.reason ) + { + case TR2RENDERREASON_NORMAL: + froxelFogTextures = RenderVolumetrics( depthMap, gpuResourcePool, renderContext ); + break; + case TR2RENDERREASON_REFLECTION: + froxelFogTextures = RenderReflectionVolumetrics( depthMap, gpuResourcePool, renderContext ); + break; + case TR2RENDERREASON_VOLUMETRIC: + froxelFogTextures = { Tr2VolumetricsRenderer::GetEmptyFogTexture( gpuResourcePool ), Tr2VolumetricsRenderer::GetEmptyVolumetricTexture( gpuResourcePool ) }; + break; + } + + GlobalStore().RegisterVariable( "EveSceneFroxelFogMap", froxelFogTextures.first ); + GlobalStore().RegisterVariable( "EveSceneFogVolumeMap", froxelFogTextures.second ); RenderTransparentBatches( m_primaryBatches, renderContext ); if( distortionMap.IsValid() ) @@ -2826,7 +2718,7 @@ bool EveSpaceScene::RenderMainPass( } //GPU particles - if( GetGpuParticleSystem() ) + if( view.gpuParticles && GetGpuParticleSystem() ) { GetGpuParticleSystem()->Render( renderContext ); } @@ -2840,6 +2732,26 @@ bool EveSpaceScene::RenderMainPass( return hasForegroundDistortionBatches; } +// -------------------------------------------------------------------------------------- +// Description: +// Reflection views use the reflection fog, so they don't disturb the main view's fog +// history. +// -------------------------------------------------------------------------------------- +std::pair EveSpaceScene::RenderReflectionVolumetrics( const Tr2TextureAL& depthMap, Tr2GpuResourcePool& gpuResourcePool, Tr2RenderContext& renderContext ) +{ + Tr2GpuResourcePool::Texture froxelFog; + if( m_volumetricsRenderer ) + { + froxelFog = m_volumetricsRenderer->RenderFogIntoReflectionMap( renderContext, gpuResourcePool, depthMap.GetWidth(), depthMap.GetHeight(), m_sunData.DirWorld, m_currentSunColor, m_updateContext.GetOrigin(), Tr2Renderer::GetViewTransform(), Tr2Renderer::GetReversedDepthProjectionTransform() ); + } + else + { + froxelFog = Tr2VolumetricsRenderer::GetEmptyFogTexture( gpuResourcePool ); + } + + return { froxelFog, Tr2VolumetricsRenderer::GetEmptyVolumetricTexture( gpuResourcePool ) }; +} + void EveSpaceScene::RunLensflareOcclusionQueries( const Tr2TextureAL& depthMap, Tr2RenderContext& renderContext ) { for( auto& lensflare : m_lensflares ) @@ -2853,7 +2765,7 @@ void EveSpaceScene::RunLensflareOcclusionQueries( const Tr2TextureAL& depthMap, // Description: // Render lensflares. Clear primary batches and frame dependant object lists. // -------------------------------------------------------------------------------------- -void EveSpaceScene::EndRender( Tr2RenderContext& renderContext ) +void EveSpaceScene::EndView( const EveSceneView::ViewInfo& view, Tr2RenderContext& renderContext ) { TRINITY_STATS_ZONE( __FUNCTION__ ); @@ -2866,7 +2778,7 @@ void EveSpaceScene::EndRender( Tr2RenderContext& renderContext ) Tr2QuadRenderer::Instance()->DoneRendering( renderContext ); - renderContext.m_esm.BeginManagedRendering(); + renderContext.m_esm.BeginManagedRendering( view.cullMode ); renderContext.m_esm.ApplyStandardStates( Tr2EffectStateManager::RM_OPAQUE ); PopulatePerFramePSData( m_perFramePS, renderContext ); @@ -2898,7 +2810,7 @@ void EveSpaceScene::EndRender( Tr2RenderContext& renderContext ) } // lensflares - if( !m_lensflares.empty() ) + if( !m_lensflares.empty() && view.lensflares ) { GPU_REGION( renderContext, "Lens Flares" ); // lensflares @@ -2914,7 +2826,8 @@ void EveSpaceScene::EndRender( Tr2RenderContext& renderContext ) m_secondaryBatches[TRIBATCHTYPE_ADDITIVE], TRIBATCHTYPE_ADDITIVE, Tr2EffectStateManager::RM_ALPHA_ADDITIVE, - renderContext ); + renderContext, + view.reason ); renderContext.SetReadOnlyDepth( false ); } } @@ -2925,8 +2838,11 @@ void EveSpaceScene::EndRender( Tr2RenderContext& renderContext ) ClearBatches( m_primaryBatches ); // Store the view transform from this frame - m_viewLast = Tr2Renderer::GetViewTransform(); - m_projectionLast = m_projection; + if( view.temporal ) + { + m_viewLast = Tr2Renderer::GetViewTransform(); + m_projectionLast = m_projection; + } ClearVariableStore(); @@ -3177,10 +3093,10 @@ void EveSpaceScene::PopulatePerFramePSData( PerFramePSData& data, Tr2ShadowMap* data.ViewportSize.y = renderContext.m_esm.GetDeviceViewport().m_height; data.Time = Tr2Renderer::GetAnimationTime(); - data.Upscaling = 1.0f; + data.Upscaling = m_upscalingAmount; data.FrameIndex = (uint32_t)Tr2Renderer::GetCurrentFrameCounter(); - data.Jittering = m_jitter != Vector4( 0, 0, 0, 0 ); + data.Jittering = LengthSq( m_jitterMatrix.GetTranslation() ) > 0.0f ? 1 : 0; data.ShadowQuality = 1 << (uint32_t)m_shadowQuality; @@ -3205,24 +3121,8 @@ void EveSpaceScene::PopulatePerFramePSData( PerFramePSData& data, Tr2ShadowMap* data.ProjectionToView.x = projection._43; data.ProjectionToView.y = projection._33; - data.SceneMipLodBias = 0.0f; - m_upscalingAmount = 1.0f; - - auto upscalingInfo = renderContext.GetPrimaryRenderContext().GetUpscalingInfo( Tr2Renderer::GetUpscalingContextID() ); - if( upscalingInfo.technique != Tr2UpscalingAL::NONE ) - { - m_upscalingAmount = upscalingInfo.upscalingAmount; - data.SceneMipLodBias = upscalingInfo.mipLevelBias; - if( !upscalingInfo.temporal && m_sceneDefaultPostProcess ) - { - data.SceneMipLodBias += m_sceneDefaultPostProcess->GetMipLodBias(); - } - data.Upscaling = m_upscalingAmount; - } - else if( m_sceneDefaultPostProcess ) - { - data.SceneMipLodBias = m_sceneDefaultPostProcess->GetMipLodBias(); - } + data.SceneMipLodBias = m_mipLodBias; + data.Upscaling = m_upscalingAmount; if( shadowMap ) { @@ -3265,6 +3165,11 @@ void EveSpaceScene::PopulatePerFramePSData( PerFramePSData& data, Tr2ShadowMap* m_volumetricsRenderer->PopulatePerFrameData( data.FroxelFogData ); } +Tr2ReflectionProbe* EveSpaceScene::GetReflectionProbe() const +{ + return m_reflectionProbe; +} + bool EveSpaceScene::Initialize() { // the environment cubemap aka the nebula diff --git a/trinity/Eve/EveSpaceScene.h b/trinity/Eve/EveSpaceScene.h index 0effaab68..7de9f58f7 100644 --- a/trinity/Eve/EveSpaceScene.h +++ b/trinity/Eve/EveSpaceScene.h @@ -27,6 +27,7 @@ #include "../Tr2VolumetricsRenderer.h" #include "../Tr2RuntimeGpuBuffer.h" #include "EvePicking.h" +#include "EveSceneView.h" #include #include @@ -97,7 +98,6 @@ BLUE_DECLARE_VECTOR( Tr2PostProcessAttributes ); class EveInstancedMeshManager; - BLUE_CLASS( EveSpaceScene ) : public ITr2Scene, public IInitialize, @@ -132,7 +132,7 @@ BLUE_CLASS( EveSpaceScene ) : Tr2GpuResourcePool::Texture pointLightShadowDepth; }; - ShadowResources RenderShadows( const Tr2TextureAL& depthMap, const Tr2TextureAL& normalMap, Tr2GpuResourcePool& gpuResourcePool, Tr2RenderContext& renderContext ); + ShadowResources RenderShadows( const Tr2TextureAL& depthMap, const Tr2TextureAL& normalMap, Tr2GpuResourcePool& gpuResourcePool, const EveSceneView::ViewInfo& view, Tr2RenderContext& renderContext ); bool RenderMainPass( const Tr2TextureAL& colorMap, const Tr2TextureAL& depthMap, @@ -140,21 +140,25 @@ BLUE_CLASS( EveSpaceScene ) : const Tr2TextureAL& velocityMap, const Tr2TextureAL& opaqueColorMap, Tr2GpuResourcePool& gpuResourcePool, + const EveSceneView::ViewInfo& view, Tr2RenderContext& renderContext ); void RunLensflareOcclusionQueries( const Tr2TextureAL& depthMap, Tr2RenderContext& renderContext ); - void RenderDepthPass( const Tr2TextureAL& depthMap, const Tr2TextureAL& normalMap, const Tr2TextureAL& customStencil, Tr2RenderContext& renderContext, const BlueSharedString& techniqueName = BlueSharedString( "Depth" ) ); + void RenderDepthPass( const Tr2TextureAL& depthMap, const Tr2TextureAL& normalMap, const Tr2TextureAL& customStencil, const EveSceneView::ViewInfo& view, Tr2RenderContext& renderContext, const BlueSharedString& techniqueName = BlueSharedString( "Depth" ) ); - bool RenderBackgroundPass( const Tr2TextureAL& depthMap, const Tr2TextureAL& distortionMap, const Tr2TextureAL& velocityMap, Tr2RenderContext& renderContext ); - void RenderReflectionPass( Tr2GpuResourcePool & gpuResourcePool, Tr2RenderContext & renderContext ); - void BeginRender( bool enableDistortion, Tr2RenderContext& renderContext ); - void EndRender( Tr2RenderContext & renderContext ); + bool RenderBackgroundPass( const Tr2TextureAL& depthMap, const Tr2TextureAL& distortionMap, const Tr2TextureAL& velocityMap, const EveSceneView::ViewInfo& view, Tr2RenderContext& renderContext ); + void BeginFrame( Tr2RenderContext & renderContext ); + void BeginView( const EveSceneView::ViewInfo& view, Tr2RenderContext& renderContext ); + void EndView( const EveSceneView::ViewInfo& view, Tr2RenderContext& renderContext ); void Render3DUI( Tr2RenderContext & renderContext ); void PopulateAndApplyPerFrameData( Tr2RenderContext & renderContext ); void ApplyUpscalingToPerFrameData( uint32_t width, uint32_t height, Tr2RenderContext& renderContext ); - void GatherBatches( bool includeDistortions, Tr2RenderContext& renderContext ); + void GatherBatches( const EveSceneView::ViewInfo& view, Tr2RenderContext& renderContext ); void PrepareRaytracedShadows( Tr2RenderContext & renderContext ); + void SetCamera( const EveSceneView::Camera& camera ); + Tr2ReflectionProbe* GetReflectionProbe() const; + ////////////////////////////////////////////////////////////////////////////////////// // IInitialize bool Initialize(); @@ -211,6 +215,11 @@ BLUE_CLASS( EveSpaceScene ) : return m_objects; } Tr2PostProcess2Ptr GetPostProcess(); + // the scene's own post process, which the render driver sets the TAA on + const Tr2PostProcess2Ptr& GetDefaultPostProcess() const + { + return m_sceneDefaultPostProcess; + } Matrix GetReprojectionMatrix() const; @@ -337,6 +346,7 @@ BLUE_CLASS( EveSpaceScene ) : Vector4 sphere = Vector4( 0, 0, 0, 0 ); float pixelSize = 0.0; }; + EvePlanetPerObjData m_planetPerObjData; Tr2ShaderBufferPtr m_planetPerObjBuffer; @@ -384,8 +394,6 @@ BLUE_CLASS( EveSpaceScene ) : void ReregisterEntities(); void ClearComponentRegistry(); - std::pair RenderVolumetrics( const Tr2TextureAL& depthMap, Tr2GpuResourcePool& gpuResourcePool, Tr2RenderContext& renderContext ); - void EnableShadowsInReflections( bool enable ); bool IsShadowsInReflectionsEnabled() const; @@ -415,8 +423,8 @@ BLUE_CLASS( EveSpaceScene ) : EveUpdateContext m_updateContext; PEveDistanceFieldVector m_distanceFields; - // Primary batches, gathered in BeginRender and - // cleared in EndRender + // Primary batches, gathered in BeginView and + // cleared in EndView BatchMap m_primaryBatches; // Secondary batches used to render planets, stars, shadowed objects // and so forth. Should be finalized and cleared each time they're used. @@ -492,6 +500,9 @@ BLUE_CLASS( EveSpaceScene ) : Color m_currentSunColor; float m_currentNebulaIntensity; + // Base value for the reflection intensity, derived from the lighting overrides and used to derive the per-view values in BeginView. + float m_baseReflectionIntensity = 1.0f; + float m_fogStart; // Depth at which fogging starts float m_fogEnd; // Depth at which fog does not get stronger float m_fogMax; // Maximum strength of fog, range [0,1], at m_fogEnd distance. @@ -597,6 +608,18 @@ BLUE_CLASS( EveSpaceScene ) : virtual void ReleaseResources( TriStorage s ); virtual bool OnPrepareResources(); + void UpdateFrame( Be::Time realTime, Be::Time simTime ); + void EnsureVisualizerEffect(); + void ResolveLightingOverrides(); + void GatherDynamicLights( Tr2RenderContext & renderContext ); + void PrepareLensflares(); + void GatherSceneObjectRenderables( std::vector & allObjects, std::vector & renderables, Tr2RenderContext & renderContext ); + void GatherReflectionRenderables( std::vector & renderables ); + ShadowResources RenderReflectionShadows( const Tr2TextureAL& depthMap, Tr2GpuResourcePool& gpuResourcePool, Tr2RenderContext& renderContext ); + + std::pair RenderVolumetrics( const Tr2TextureAL& depthMap, Tr2GpuResourcePool& gpuResourcePool, Tr2RenderContext& renderContext ); + std::pair RenderReflectionVolumetrics( const Tr2TextureAL& depthMap, Tr2GpuResourcePool& gpuResourcePool, Tr2RenderContext& renderContext ); + void UpdateShLighting( const std::vector& objectsNotReceivingShadow ); @@ -625,9 +648,7 @@ BLUE_CLASS( EveSpaceScene ) : Tr2ShLightingManagerPtr m_shLightingManager; float m_upscalingAmount; - Vector4 m_jitter; // xy: projection offset, zw: pixel offset - - void Jitter( Tr2RenderContext & renderContext ); + float m_mipLodBias; Tr2ImpostorManagerPtr m_impostorManager; diff --git a/trinity/Eve/EveSpaceSceneRenderDriver.cpp b/trinity/Eve/EveSpaceSceneRenderDriver.cpp index fe3c1de73..97e486097 100644 --- a/trinity/Eve/EveSpaceSceneRenderDriver.cpp +++ b/trinity/Eve/EveSpaceSceneRenderDriver.cpp @@ -13,27 +13,29 @@ #include "../Particle/Tr2GpuParticleSystem.h" #include "../Tr2TextureReference.h" #include "Resources/TriTextureRes.h" +#include "Tr2ReflectionProbe.h" CCP_STATS_DECLARE( updateDynamicLightLists, "Trinity/EveSpaceScene/updateDynamicLights", true, CST_TIME, "Time took to gather dynamic lights for EveSpaceScene" ); extern bool g_eveSpaceSceneDynamicLighting; +extern bool g_lensflaresInReflections; namespace { -void BeginRenderPass( Tr2RenderContext& renderContext, const std::initializer_list& colorAttachments, const Tr2TextureAL& depthAttachment ) +void BeginRenderPass( Tr2RenderContext& renderContext, const std::initializer_list>& colorAttachments, const Tr2TextureAL& depthAttachment ) { uint32_t index = 0; - for( auto& colorAttachment : colorAttachments ) + for( auto& [colorAttachment, slice] : colorAttachments ) { - renderContext.m_esm.SetRenderTarget( index++, colorAttachment ); + renderContext.m_esm.SetRenderTarget( index++, colorAttachment, true, slice ); } // clear out remaining slots, generously assuming max 4 render targets const uint32_t maxRenderTargets = 4; for( ; index < maxRenderTargets; ++index ) { - renderContext.m_esm.SetRenderTarget( index++, Tr2TextureAL{} ); + renderContext.m_esm.SetRenderTarget( index, Tr2TextureAL{} ); } renderContext.m_esm.SetDepthStencilBuffer( depthAttachment ); renderContext.m_esm.SetFullScreenViewport(); @@ -62,7 +64,7 @@ void ApplyDistortion( const Tr2TextureAL& destination, const Tr2TextureAL& disto auto backBufferCopy = gpuResourcePool.GetTempTexture( "backBufferCopy", destination.GetWidth(), destination.GetHeight(), destination.GetFormat(), Tr2GpuUsage::COPY_DESTINATION | Tr2GpuUsage::SHADER_RESOURCE ); backBufferCopy->CopySubresourceRegion( {}, destination, {}, renderContext ); - BeginRenderPass( renderContext, { destination }, {} ); + BeginRenderPass( renderContext, { { destination, 0 } }, {} ); renderContext.m_esm.ApplyStandardStates( Tr2EffectStateManager::RM_FULLSCREEN ); effect->SetParameter( BlueSharedString( "BlitCurrent" ), backBufferCopy ); effect->SetParameter( BlueSharedString( "TexDistortion" ), distortion ); @@ -78,7 +80,7 @@ struct TimeSection { if( rootTimer.GetCaptureCpuTime() || rootTimer.GetCaptureGpuTime() ) { - m_timer.SetStatName( ( rootTimer.GetStatName() + std::string( "/" ) + name ).c_str() ); + m_timer.SetStatName( ( rootTimer.GetStatName() + "/" + name ).c_str() ); m_timer.SetCaptureCpuTime( rootTimer.GetCaptureCpuTime() ); m_timer.SetCaptureGpuTime( rootTimer.GetCaptureGpuTime() ); m_timer.Begin( renderContext ); @@ -172,6 +174,7 @@ void EveSpaceSceneRenderDriver::ReleaseResources( TriStorage s ) { DeleteUpscalingContext( m_upscalingContext ); m_upscalingContext = nullptr; + m_upscalingInfo = Tr2UpscalingAL::UpscalingInfo(); } } @@ -202,33 +205,36 @@ void EveSpaceSceneRenderDriver::SetupUpscaling( const TextureSize2D& displaySize params.depthFormat = Tr2RenderContextEnum::DSFMT_D32F; m_upscalingContext = renderContext.CreateUpscalingContext( params, m_upscalingContext ? m_upscalingContext->GetID() : Tr2UpscalingAL::INVALID_CONTEXT_ID ); + // m_upscalingContext may not have been successfully created, so we need to check it before calling GetUpscalingInfo + m_upscalingInfo = renderContext.GetPrimaryRenderContext().GetUpscalingInfo( m_upscalingContext ? m_upscalingContext->GetID() : Tr2UpscalingAL::INVALID_CONTEXT_ID ); } } else if( m_upscalingContext ) { DeleteUpscalingContext( m_upscalingContext ); m_upscalingContext = nullptr; + m_upscalingInfo = Tr2UpscalingAL::UpscalingInfo(); } } void EveSpaceSceneRenderDriver::PropagateSettings() { // AA setting - if( m_scene->m_sceneDefaultPostProcess ) + if( const auto& defaultPostProcess = m_scene->GetDefaultPostProcess() ) { if( m_settings.antiAliasingQuality == AntiAliasingQuality::Disabled ) { - m_scene->m_sceneDefaultPostProcess->SetTaa( nullptr ); + defaultPostProcess->SetTaa( nullptr ); } else { - if( !m_scene->m_sceneDefaultPostProcess->GetTaaIfAvailable() ) + if( !defaultPostProcess->GetTaaIfAvailable() ) { Tr2PPTaaEffectPtr taa; taa.CreateInstance(); - m_scene->m_sceneDefaultPostProcess->SetTaa( taa ); + defaultPostProcess->SetTaa( taa ); } - m_scene->m_sceneDefaultPostProcess->GetTaaIfAvailable()->m_quality = (Tr2PPTaaEffect::Quality)m_settings.antiAliasingQuality; + defaultPostProcess->GetTaaIfAvailable()->m_quality = (Tr2PPTaaEffect::Quality)m_settings.antiAliasingQuality; } } @@ -295,8 +301,85 @@ void EveSpaceSceneRenderDriver::PropagateSettings() { m_scene->m_volumetricsRenderer->SetQuality( m_settings.volumetricQuality ); } + + // The reflection quality is set from the global variable g_eveReflectionMode, which is set from the graphics settings. + // Leaving the global in for now, until a broader refactor is done. + switch( g_eveReflectionMode ) + { + case EntityComponents::ReflectionSetting::REFLECTION_SETTING_OFF: + m_settings.reflectionQuality = ReflectionQuality::OFF; + break; + case EntityComponents::ReflectionSetting::REFLECTION_SETTING_LOW: + m_settings.reflectionQuality = ReflectionQuality::LOW; + break; + case EntityComponents::ReflectionSetting::REFLECTION_SETTING_MEDIUM: + m_settings.reflectionQuality = ReflectionQuality::MEDIUM; + break; + case EntityComponents::ReflectionSetting::REFLECTION_SETTING_HIGH: + m_settings.reflectionQuality = ReflectionQuality::HIGH; + break; + case EntityComponents::ReflectionSetting::REFLECTION_SETTING_ULTRA: + m_settings.reflectionQuality = ReflectionQuality::ULTRA; + break; + default: + break; + } +} + +EveSpaceSceneRenderDriver::Features EveSpaceSceneRenderDriver::Features::Main() +{ + Features features; + features.upscaling = true; + features.antiAliasing = true; + features.ssao = true; + features.raytracedShadows = true; + features.velocityMap = true; + features.background = true; + features.overlay = true; + features.dynamicLights = true; + return features; } +EveSpaceSceneRenderDriver::Features EveSpaceSceneRenderDriver::Features::ReflectionFace() +{ + // faces are rendered without TAA/upscaling, SSAO, velocity or the render nodes + Features features; + features.upscaling = false; + features.antiAliasing = false; + features.ssao = false; + features.raytracedShadows = false; + features.velocityMap = false; + features.background = false; + features.overlay = false; + features.dynamicLights = true; + return features; +} + +// -------------------------------------------------------------------------------------- +// Description: +// Set the camera's view transform to the renderer and the camera to the scene, which +// also applies its projection to the renderer. +// -------------------------------------------------------------------------------------- +void EveSpaceSceneRenderDriver::ApplyCamera( const EveSceneView::Camera& camera ) const +{ + Tr2Renderer::SetViewTransform( camera.view ); + m_scene->SetCamera( camera ); +} + +// -------------------------------------------------------------------------------------- +// Description: +// The scene's per-frame update, done once and from the main camera before any view is +// rendered, so the frame's object updates and LOD follow the main view. +// -------------------------------------------------------------------------------------- +void EveSpaceSceneRenderDriver::UpdateSceneFrame( const EveSceneView::Camera& mainCamera, Be::Time realTime, Be::Time simTime, const Tr2ProfileTimer& rootTimer, Tr2RenderContext& renderContext ) +{ + ApplyCamera( mainCamera ); + { + TimeSection updateSection( m_timers.update, "Update", rootTimer, renderContext ); + m_scene->Update( realTime, simTime ); + } + UpdateGpuParticleSystem( renderContext ); +} bool EveSpaceSceneRenderDriver::Validate( const Span& destDimensions, const Span& outputs, Be::Time realTime, Be::Time simTime ) { @@ -366,9 +449,8 @@ Tr2GpuResourcePool::Texture EveSpaceSceneRenderDriver::RenderSSAO( const Tr2Text { renderContext.SetReadOnlyDepth( true ); - auto upscalingInfo = renderContext.GetPrimaryRenderContext().GetUpscalingInfo( m_upscalingContext ? m_upscalingContext->GetID() : Tr2UpscalingAL::INVALID_CONTEXT_ID ); - auto scenePostProcess = m_scene->m_sceneDefaultPostProcess; - bool temporal = upscalingInfo.temporal || ( scenePostProcess != nullptr && scenePostProcess->GetTaaIfAvailable() != nullptr ); + const auto& scenePostProcess = m_scene->GetDefaultPostProcess(); + bool temporal = m_upscalingInfo.temporal || ( scenePostProcess != nullptr && scenePostProcess->GetTaaIfAvailable() != nullptr ); ssao = m_ssao->Filter( depthMap, normalMap, m_gpuResourcePool, renderContext, temporal ); renderContext.SetReadOnlyDepth( false ); @@ -377,10 +459,7 @@ Tr2GpuResourcePool::Texture EveSpaceSceneRenderDriver::RenderSSAO( const Tr2Text { return ssao; } - else - { - return GetEmptySSAO( m_gpuResourcePool ); - } + return GetEmptySSAO( m_gpuResourcePool ); } void EveSpaceSceneRenderDriver::SetCameraToRenderer( Tr2RenderContext& renderContext ) const @@ -403,6 +482,15 @@ TextureSize2D EveSpaceSceneRenderDriver::GetRenderSize( const TextureSize2D& dis return displaySize; } +EveSceneView::Camera EveSpaceSceneRenderDriver::GetMainCamera( const TextureSize2D& renderSize, const Matrix& jitterMatrix, float mipLodBias ) const +{ + auto projection = m_camera ? m_camera->GetProjection() : m_projection; + auto view = m_camera ? m_camera->GetViewMatrix() : m_view; + const float upscalingAmount = m_upscalingContext ? m_upscalingContext->GetUpscalingAmount() : 1.0f; + + return EveSceneView::Camera( view->GetTransform(), projection->GetTransform(), renderSize, jitterMatrix, mipLodBias, upscalingAmount ); +} + void EveSpaceSceneRenderDriver::Execute( const Span& destinations, const Span& outputs, Be::Time realTime, Be::Time simTime, const Tr2ProfileTimer& rootTimer, Tr2RenderContext& renderContext ) { const bool hasCamera = m_camera || ( m_view && m_projection ); @@ -414,7 +502,8 @@ void EveSpaceSceneRenderDriver::Execute( const Span& destina return; } - SetCameraToRenderer( renderContext ); + const TextureSize2D displaySize = destinations.size > 0 ? TextureSize2D( destinations.data->GetDesc() ) : TextureSize2D(); + ApplyCamera( GetMainCamera( GetRenderSize( displaySize ), IdentityMatrix(), 0.0f ) ); m_scene->Update( realTime, simTime ); UpdateGpuParticleSystem( renderContext ); return; @@ -426,10 +515,8 @@ void EveSpaceSceneRenderDriver::Execute( const Span& destina return; } - GlobalStore().RegisterVariable( "EveSpaceSceneReflectionCorrectionLookupTable", m_reflectionCorrectionEnabled ? m_reflectionCorrectionMap : m_blackReflectionCorrectionMap ); - m_scene->m_viewLast = m_viewLast; m_scene->m_projectionLast = m_projectionLast; @@ -448,7 +535,6 @@ void EveSpaceSceneRenderDriver::Execute( const Span& destina renderContext.m_esm.SetInvertedDepthTest( true ); ON_BLOCK_EXIT( [&] { renderContext.m_esm.SetInvertedDepthTest( false ); } ); - renderContext.m_esm.PushRenderTarget( 0 ); renderContext.m_esm.PushRenderTarget( 1 ); renderContext.m_esm.PushDepthStencilBuffer(); @@ -458,101 +544,218 @@ void EveSpaceSceneRenderDriver::Execute( const Span& destina renderContext.m_esm.PopRenderTarget( 0 ); } ); - Tr2Renderer::SetUpscalingContextID( m_upscalingContext ? m_upscalingContext->GetID() : Tr2UpscalingAL::INVALID_CONTEXT_ID ); + PrepareLightManager(); - auto customBackBuffer = m_gpuResourcePool.GetTempTexture( "customBackBuffer", renderSize, m_internalPixelFormat, Tr2GpuUsage::RENDER_TARGET | Tr2GpuUsage::SHADER_RESOURCE ); + const RenderInfo mainView = { + GetMainCamera( renderSize, GetJitterMatrix( renderSize.width, renderSize.height, renderContext ), GetMipLodBias() ), + EveSceneView::ViewInfo::Main( m_settings.enableDistortion ), + Features::Main() + }; + auto colorTarget = m_gpuResourcePool.GetTempTexture( "customBackBuffer", renderSize, m_internalPixelFormat, Tr2GpuUsage::RENDER_TARGET | Tr2GpuUsage::SHADER_RESOURCE ); - if( m_background ) + ViewTargets targets; { - BeginRenderPass( renderContext, { customBackBuffer }, {} ); - renderContext.Clear( Tr2RenderContextEnum::CLEARFLAGS_TARGET, m_settings.clearColor, 0 ); + auto prevVisualizeMethod = m_scene->m_visualizeMethod; + m_scene->m_visualizeMethod = m_settings.visualizeMethod; + ON_BLOCK_EXIT( [&] { m_scene->m_visualizeMethod = prevVisualizeMethod; } ); + BeginRenderPass( renderContext, { { colorTarget, 0 } }, {} ); + + UpdateSceneFrame( mainView.camera, realTime, simTime, rootTimer, renderContext ); + ReflectionFaces reflectionFaces; + { + TimeSection beginFrameSection( m_timers.beginFrame, "BeginFrame", rootTimer, renderContext ); + m_scene->BeginFrame( renderContext ); + + reflectionFaces = PrepareReflectionFaces( renderContext ); - m_background->Execute( { &customBackBuffer.Get(), 1 }, {}, realTime, simTime, rootTimer, renderContext ); + // the lights are gathered once, for all views + SetLightViews( mainView, reflectionFaces, renderContext ); + m_scene->GatherDynamicLights( renderContext ); + } + { + TimeSection reflectionsSection( m_timers.reflections, "RenderReflections", rootTimer, renderContext ); + // set the frame reflection timer as the root timer so we get correct timer paths + RenderReflectionFaces( reflectionFaces, realTime, simTime, m_timers.reflections, renderContext ); + } + { + TimeSection totalSection( m_timers.mainView.total, "MainView", rootTimer, renderContext ); + targets = RenderView( mainView, colorTarget, outputs, m_timers.mainView, realTime, simTime, m_timers.mainView.total, renderContext ); + } } - auto depthBuffer = m_gpuResourcePool.GetTempTexture( "depthBuffer", renderSize, ImageIO::PIXEL_FORMAT_D32_FLOAT, Tr2GpuUsage::DEPTH_STENCIL | Tr2GpuUsage::SHADER_RESOURCE ); + // store the last view and projection matrices for the next frame, so the scene can use them for temporal effects + m_viewLast = m_scene->m_viewLast; + m_projectionLast = m_scene->m_projectionLast; - BeginRenderPass( renderContext, { customBackBuffer }, depthBuffer ); - renderContext.Clear( m_background ? Tr2RenderContextEnum::CLEARFLAGS_ZBUFFER : ( Tr2RenderContextEnum::CLEARFLAGS_TARGET | Tr2RenderContextEnum::CLEARFLAGS_ZBUFFER ), m_settings.clearColor, 0 ); + renderContext.m_esm.SetRenderTarget( 0, *destinations.data ); + renderContext.m_esm.SetDepthStencilBuffer( {} ); + { + TimeSection postProcessSection( m_timers.postProcess, "PostProcess", rootTimer, renderContext ); + m_postProcess->Execute( *destinations.data, std::move( colorTarget ), targets.depth, std::move( targets.velocity ), std::move( targets.opaqueColor ), m_scene, m_upscalingContext, m_gpuResourcePool, renderContext ); + } SetCameraToRenderer( renderContext ); { - TimeSection updateSection( m_timers.update, "Update", rootTimer, renderContext ); - m_scene->Update( realTime, simTime ); + TimeSection ui3dSection( m_timers.ui3d, "Render3dUI", rootTimer, renderContext ); + m_scene->Render3DUI( renderContext ); } - UpdateGpuParticleSystem( renderContext ); - auto prevVisualizeMethod = m_scene->m_visualizeMethod; - m_scene->m_visualizeMethod = m_settings.visualizeMethod; - ON_BLOCK_EXIT( [&] { m_scene->m_visualizeMethod = prevVisualizeMethod; } ); + for( auto& toolScene : m_toolsScenes ) + { + if( ITr2UpdateablePtr updateable = BlueCastPtr( toolScene ) ) + { + updateable->Update( realTime, simTime ); + } + toolScene->Render( renderContext ); + } + + GlobalStore().RegisterVariable( "SSAOMap", GetEmptySSAO( m_gpuResourcePool ) ); + GlobalStore().RegisterVariable( "DepthMap", Tr2TextureAL{} ); + + if( m_settings.showFPS ) + { + m_fpsRenderer->Execute( realTime, simTime, renderContext ); + } +} + +Matrix EveSpaceSceneRenderDriver::GetJitterMatrix( uint32_t renderWidth, uint32_t renderHeight, Tr2RenderContext& renderContext ) const +{ + Matrix jitterMatrix; + // The TAA is read from the scene's default post process, which PropagateSettings has set + // up for this driver. The combined post process only picks it up in the scene update, + // which runs after the main view's camera is built. + if( m_upscalingInfo.temporal ) + { + float jitterX = 0.0f; + float jitterY = 0.0f; + m_upscalingContext->GetJitter( jitterX, jitterY ); + jitterMatrix = TranslationMatrix( Vector3( jitterX, jitterY, 0 ) ); + } + else if( m_scene && m_scene->GetDefaultPostProcess() && m_scene->GetDefaultPostProcess()->GetTaaIfAvailable() != nullptr ) + { + auto frameIndex = renderContext.GetPrimaryRenderContext().GetRecordingFrameNumber(); + jitterMatrix = Tr2PostProcessRenderer::GetJitter( frameIndex, renderWidth, renderHeight ); + } + else { - TimeSection beginRenderSection( m_timers.beginRender, "BeginRender", rootTimer, renderContext ); + jitterMatrix = IdentityMatrix(); + } - if( g_eveSpaceSceneDynamicLighting ) + return jitterMatrix; +} + +float EveSpaceSceneRenderDriver::GetMipLodBias() const +{ + // 0 if no upscaling is enabled + float mipLodBias = m_upscalingInfo.mipLevelBias; + + // The post process bias belongs to its TAA. A temporal upscaler replaces the TAA and + // its bias already accounts for it. Read from the default post process, as in + // GetJitterMatrix. + if( !m_upscalingInfo.temporal && m_scene ) + { + const auto& postprocess = m_scene->GetDefaultPostProcess(); + if( postprocess && postprocess->GetTaaIfAvailable() != nullptr ) { - if( auto lightManager = Tr2LightManager::GetOrCreateInstance( "res:/graphics/effect/managed/space/system/computelightlists.fx" ) ) - { - lightManager->SetVariableStore(); - lightManager->SetLightingQuality( m_settings.lightingQuality ); - } + mipLodBias += postprocess->GetMipLodBias(); } - else + } + return mipLodBias; +} + +// -------------------------------------------------------------------------------------- +// Description: +// Render the scene from one view (the main view or a reflection probe face) into +// colorTarget. Returns the targets the view rendered, for post processing. +// -------------------------------------------------------------------------------------- +EveSpaceSceneRenderDriver::ViewTargets EveSpaceSceneRenderDriver::RenderView( const RenderInfo& view, const Tr2TextureAL& colorTarget, const Span& outputs, ViewTimers& timers, Be::Time realTime, Be::Time simTime, const Tr2ProfileTimer& rootTimer, Tr2RenderContext& renderContext, const uint32_t colorTargetSlice ) +{ + const TextureSize2D& renderSize = view.camera.renderSize; + + ViewTargets targets; + const Tr2TextureAL& backBuffer = colorTarget; + ApplyCamera( view.camera ); + + // every view with lights makes its own view active, so it can't read another view's lights + if( view.features.dynamicLights ) + { + if( auto lightManager = Tr2LightManager::GetInstance() ) { - Tr2LightManager::DeleteInstance(); + lightManager->SetActiveView( view.lightView ); } + } + + const bool renderBackgroundNode = m_background && view.features.background; + if( renderBackgroundNode ) + { + // render background effect to back buffer before rendering the scene + BeginRenderPass( renderContext, { { backBuffer, colorTargetSlice } }, {} ); + renderContext.Clear( Tr2RenderContextEnum::CLEARFLAGS_TARGET, m_settings.clearColor, 0 ); + + m_background->Execute( { &backBuffer, 1 }, {}, realTime, simTime, rootTimer, renderContext ); - m_scene->BeginRender( m_settings.enableDistortion, renderContext ); + // the background node can set its own camera + ApplyCamera( view.camera ); } + + targets.depth = m_gpuResourcePool.GetTempTexture( "depthBuffer", renderSize, ImageIO::PIXEL_FORMAT_D32_FLOAT, Tr2GpuUsage::DEPTH_STENCIL | Tr2GpuUsage::SHADER_RESOURCE ); + const Tr2TextureAL& depthBuffer = targets.depth; + + BeginRenderPass( renderContext, { { backBuffer, colorTargetSlice } }, depthBuffer ); + renderContext.Clear( renderBackgroundNode ? Tr2RenderContextEnum::CLEARFLAGS_ZBUFFER : ( Tr2RenderContextEnum::CLEARFLAGS_TARGET | Tr2RenderContextEnum::CLEARFLAGS_ZBUFFER ), m_settings.clearColor, 0 ); + { - TimeSection reflectionsSection( m_timers.reflections, "RenderReflections", rootTimer, renderContext ); - m_scene->RenderReflectionPass( m_gpuResourcePool, renderContext ); + TimeSection beginViewSection( timers.beginView, "BeginView", rootTimer, renderContext ); + m_scene->BeginView( view.scene, renderContext ); } - GlobalStore().RegisterVariable( "DepthMap", depthBuffer ); - SetNamedOutput( outputs, "DepthMap", depthBuffer ); + GlobalStore().RegisterVariable( "DepthMap", targets.depth ); + + SetNamedOutput( outputs, "DepthMap", targets.depth ); - Tr2GpuResourcePool::Texture distortionMap = GetDistortionMapIfNeeded( renderSize ); - Tr2GpuResourcePool::Texture velocityMap = GetVelocityMapIfNeeded( renderSize, outputs ); + Tr2GpuResourcePool::Texture distortionMap = GetDistortionMapIfNeeded( renderSize, view.scene.distortion ); + targets.velocity = GetVelocityMapIfNeeded( renderSize, outputs, view.features ); + const Tr2TextureAL& velocityMap = targets.velocity; bool hasBackgroundDistortionBatches = false; { - TimeSection backgroundSection( m_timers.background, "BackgroundPass", rootTimer, renderContext ); - hasBackgroundDistortionBatches = m_scene->RenderBackgroundPass( depthBuffer, distortionMap, velocityMap, renderContext ); + TimeSection backgroundSection( timers.background, "BackgroundPass", rootTimer, renderContext ); + hasBackgroundDistortionBatches = m_scene->RenderBackgroundPass( depthBuffer, distortionMap, velocityMap, view.scene, renderContext ); } - if( m_settings.enableDistortion && hasBackgroundDistortionBatches ) + if( view.scene.distortion && hasBackgroundDistortionBatches ) { - ApplyDistortion( customBackBuffer, distortionMap, m_gpuResourcePool, m_distortionEffect, renderContext ); + ApplyDistortion( backBuffer, distortionMap, m_gpuResourcePool, m_distortionEffect, renderContext ); renderContext.m_esm.SetDepthStencilBuffer( depthBuffer ); } distortionMap = {}; - Tr2GpuResourcePool::Texture normalMap = GetNormalMapIfNeeded( renderSize, outputs ); + Tr2GpuResourcePool::Texture normalMap = GetNormalMapIfNeeded( renderSize, outputs, view.features ); Tr2GpuResourcePool::Texture customStencil = GetCustomStencilMapIfNeeded( renderSize, outputs ); { - TimeSection depthPassSection( m_timers.depthPass, "DepthPass", rootTimer, renderContext ); - m_scene->RenderDepthPass( depthBuffer, normalMap, customStencil, renderContext, m_depthPassTechnique ); + TimeSection depthPassSection( timers.depthPass, "DepthPass", rootTimer, renderContext ); + m_scene->RenderDepthPass( depthBuffer, normalMap, customStencil, view.scene, renderContext, m_depthPassTechnique ); } GlobalStore().RegisterVariable( "SpaceSceneNormalMap", normalMap ); GlobalStore().RegisterVariable( "SpaceSceneCustomStencil", customStencil ); ON_BLOCK_EXIT( [&] { GlobalStore().RegisterVariable( "SpaceSceneCustomStencil", Tr2TextureAL{} ); } ); - Tr2GpuResourcePool::Texture opaqueBackBuffer; EveSpaceScene::ShadowResources shadowResources; if( m_scene->m_display && m_mainPassRenderingEnabled ) { { - TimeSection shadowsSection( m_timers.shadows, "Shadows", rootTimer, renderContext ); - shadowResources = m_scene->RenderShadows( depthBuffer, normalMap, m_gpuResourcePool, renderContext ); + TimeSection shadowsSection( timers.shadows, "Shadows", rootTimer, renderContext ); + shadowResources = m_scene->RenderShadows( depthBuffer, normalMap, m_gpuResourcePool, view.scene, renderContext ); } - TimeSection ssaoSection( m_timers.ssao, "SSAO", rootTimer, renderContext ); - auto ssao = RenderSSAO( depthBuffer, normalMap, renderContext ); + TimeSection ssaoSection( timers.ssao, "SSAO", rootTimer, renderContext ); + auto ssao = view.features.ssao ? RenderSSAO( depthBuffer, normalMap, renderContext ) : GetEmptySSAO( m_gpuResourcePool ); ssaoSection.Stop(); GlobalStore().RegisterVariable( "SSAOMap", ssao ); - if( auto lightManager = Tr2LightManager::GetInstance() ) + auto lightManager = Tr2LightManager::GetInstance(); + if( lightManager && view.features.dynamicLights ) { GPU_REGION( renderContext, "Lighting" ); TRINITY_STATS_SCOPED_TIME( updateDynamicLightLists ); @@ -561,20 +764,20 @@ void EveSpaceSceneRenderDriver::Execute( const Span& destina renderContext.SetReadOnlyDepth( false ); } - opaqueBackBuffer = GetOpaqueColorMapIfNeeded( renderSize, outputs ); + targets.opaqueColor = GetOpaqueColorMapIfNeeded( renderSize, outputs, view.features ); RegisterWithVariableStore( shadowResources, m_gpuResourcePool ); bool hasDistortionBatches = false; - distortionMap = GetDistortionMapIfNeeded( renderSize ); + distortionMap = GetDistortionMapIfNeeded( renderSize, view.scene.distortion ); { - TimeSection mainPassSection( m_timers.mainPass, "MainPass", rootTimer, renderContext ); - hasDistortionBatches = m_scene->RenderMainPass( customBackBuffer, depthBuffer, distortionMap, velocityMap, opaqueBackBuffer, m_gpuResourcePool, renderContext ); + TimeSection mainPassSection( timers.mainPass, "MainPass", rootTimer, renderContext ); + hasDistortionBatches = m_scene->RenderMainPass( backBuffer, depthBuffer, distortionMap, velocityMap, targets.opaqueColor, m_gpuResourcePool, view.scene, renderContext ); } RegisterWithVariableStore( {}, m_gpuResourcePool ); - if( m_settings.enableDistortion && hasDistortionBatches ) + if( view.scene.distortion && hasDistortionBatches ) { - ApplyDistortion( customBackBuffer, distortionMap, m_gpuResourcePool, m_distortionEffect, renderContext ); + ApplyDistortion( backBuffer, distortionMap, m_gpuResourcePool, m_distortionEffect, renderContext ); renderContext.m_esm.SetDepthStencilBuffer( depthBuffer ); } distortionMap = {}; @@ -583,67 +786,143 @@ void EveSpaceSceneRenderDriver::Execute( const Span& destina else { // Even if we skip main pass rendering, we might need opaqueBackBuffer for upscaling or TAA - opaqueBackBuffer = GetOpaqueColorMapIfNeeded( renderSize, outputs ); - if( opaqueBackBuffer.IsValid() ) + targets.opaqueColor = GetOpaqueColorMapIfNeeded( renderSize, outputs, view.features ); + if( targets.opaqueColor.IsValid() ) { renderContext.RenderPassHint( { Tr2LoadAction::DONT_CARE, Tr2StoreAction::STORE }, {} ); - BeginRenderPass( renderContext, { opaqueBackBuffer }, {} ); + BeginRenderPass( renderContext, { { targets.opaqueColor.Get(), 0 } }, {} ); renderContext.m_esm.ApplyStandardStates( Tr2EffectStateManager::RM_FULLSCREEN ); - Tr2Renderer::DrawTexture( renderContext, customBackBuffer ); + Tr2Renderer::DrawTexture( renderContext, backBuffer ); } } - if( m_sceneOverlay ) + if( m_sceneOverlay && view.features.overlay ) { - Tr2TextureAL targets[] = { customBackBuffer.Get(), velocityMap.Get() }; - m_sceneOverlay->Execute( { targets, 2 }, {}, realTime, simTime, rootTimer, renderContext ); + Tr2TextureAL overlayTargets[] = { backBuffer, velocityMap }; + m_sceneOverlay->Execute( { overlayTargets, 2 }, {}, realTime, simTime, rootTimer, renderContext ); } GlobalStore().RegisterVariable( "SpaceSceneNormalMap", Tr2TextureAL{} ); normalMap = {}; - BeginRenderPass( renderContext, { customBackBuffer }, depthBuffer ); + BeginRenderPass( renderContext, { { backBuffer, colorTargetSlice } }, depthBuffer ); + // each lens flare has one occlusion result, which belongs to the main view + if( view.scene.temporal ) { renderContext.SetReadOnlyDepth( true ); m_scene->RunLensflareOcclusionQueries( depthBuffer, renderContext ); renderContext.SetReadOnlyDepth( false ); } { - TimeSection endRenderSection( m_timers.endRender, "EndRender", rootTimer, renderContext ); - m_scene->EndRender( renderContext ); + TimeSection endViewSection( timers.endView, "EndView", rootTimer, renderContext ); + m_scene->EndView( view.scene, renderContext ); } - m_viewLast = m_scene->m_viewLast; - m_projectionLast = m_scene->m_projectionLast; m_scene->PopulateAndApplyPerFrameData( renderContext ); - renderContext.m_esm.SetRenderTarget( 0, *destinations.data ); - renderContext.m_esm.SetDepthStencilBuffer( {} ); - GlobalStore().RegisterVariable( "EveSpaceSceneOpaqueMap", Tr2TextureAL{} ); + return targets; +} + +EveSpaceSceneRenderDriver::ReflectionFaces EveSpaceSceneRenderDriver::PrepareReflectionFaces( Tr2RenderContext& renderContext ) +{ + ReflectionFaces faces; + + Tr2ReflectionProbe* probe = m_scene->GetReflectionProbe(); + if( !probe || !probe->IsValid() || !m_scene->m_display ) { - TimeSection postProcessSection( m_timers.postProcess, "PostProcess", rootTimer, renderContext ); - m_postProcess->Execute( *destinations.data, std::move( customBackBuffer ), depthBuffer, std::move( velocityMap ), std::move( opaqueBackBuffer ), m_scene, m_upscalingContext, m_gpuResourcePool, renderContext ); + return faces; } + SetCameraToRenderer( renderContext ); + probe->UpdatePosition( Tr2Renderer::GetViewPosition() ); + const Matrix faceProjection = probe->GetFaceProjection( Tr2Renderer::GetProjectionTransform() ); + const TextureSize2D faceRenderSize = probe->GetCubeTarget().GetDesc(); + + faces.probe = probe; + faces.startFace = probe->GetStartFace(); + faces.viewDirection = Tr2Renderer::GetInverseViewTransform().GetZ(); + + const bool lensflares = g_lensflaresInReflections && m_settings.reflectionQuality == ReflectionQuality::ULTRA; + for( unsigned face = probe->GetStartFace(); face < probe->GetEndFace(); ++face ) { - TimeSection ui3dSection( m_timers.ui3d, "Render3dUI", rootTimer, renderContext ); - m_scene->Render3DUI( renderContext ); + const EveSceneView::Camera camera = EveSceneView::Camera( probe->GetFaceViewTransform( face ), faceProjection, faceRenderSize ); + faces.views.push_back( { camera, EveSceneView::ViewInfo::ReflectionFace( lensflares ), Features::ReflectionFace() } ); } - for( auto& toolScene : m_toolsScenes ) + return faces; +} + +// -------------------------------------------------------------------------------------- +// Description: +// Give the main view and every reflection face that has dynamic lights a view in the +// light manager, in that order. A view's lightView is its position in that list. +// -------------------------------------------------------------------------------------- +void EveSpaceSceneRenderDriver::SetLightViews( const RenderInfo& mainView, ReflectionFaces& faces, Tr2RenderContext& renderContext ) +{ + auto lightManager = Tr2LightManager::GetInstance(); + if( !lightManager ) { - if( ITr2UpdateablePtr updateable = BlueCastPtr( toolScene ) ) + return; + } + + // the main view is first, matching its lightView of 0 + std::vector frustums = { mainView.camera.frustum }; + for( RenderInfo& faceView : faces.views ) + { + if( faceView.features.dynamicLights ) { - updateable->Update( realTime, simTime ); + faceView.lightView = static_cast( frustums.size() ); + frustums.push_back( faceView.camera.frustum ); } - toolScene->Render( renderContext ); } + lightManager->SetViews( frustums, renderContext ); +} - GlobalStore().RegisterVariable( "SSAOMap", GetEmptySSAO( m_gpuResourcePool ) ); - GlobalStore().RegisterVariable( "DepthMap", Tr2TextureAL{} ); +void EveSpaceSceneRenderDriver::RenderReflectionFaces( const ReflectionFaces& faces, Be::Time realTime, Be::Time simTime, const Tr2ProfileTimer& rootTimer, Tr2RenderContext& renderContext ) +{ + Tr2ReflectionProbe* probe = faces.probe; + if( !probe ) + { + return; + } - if( m_settings.showFPS ) + GPU_REGION( renderContext, "Reflection" ); + + const bool prevCullInversion = renderContext.m_esm.IsCullModeInverted(); + renderContext.m_esm.SetInvertedCullMode( true ); + ON_BLOCK_EXIT( [&] { renderContext.m_esm.SetInvertedCullMode( prevCullInversion ); } ); + + const Tr2TextureAL& faceTarget = probe->GetCubeTarget(); + unsigned face = faces.startFace; + for( const RenderInfo& faceView : faces.views ) { - m_fpsRenderer->Execute( realTime, simTime, renderContext ); + auto& timers = m_timers.reflectionTimers[face]; + const std::string regionName = "Face" + std::to_string( face ); + GPU_REGION( renderContext, regionName.c_str() ); + TimeSection totalSection( timers.total, regionName.c_str(), rootTimer, renderContext ); + // render straight into the probe's face + RenderView( faceView, faceTarget, {}, timers, realTime, simTime, timers.total, renderContext, face ); + ++face; + } + + // unbind the probe cube so it can be used as a shader resource in the main view + renderContext.m_esm.SetRenderTarget( 0, Tr2TextureAL{} ); + probe->FinishFaces( faces.viewDirection, renderContext ); + SetCameraToRenderer( renderContext ); +} + +void EveSpaceSceneRenderDriver::PrepareLightManager() +{ + if( g_eveSpaceSceneDynamicLighting ) + { + if( auto lightManager = Tr2LightManager::GetOrCreateInstance( "res:/graphics/effect/managed/space/system/computelightlists.fx" ) ) + { + lightManager->SetLightingQuality( m_settings.lightingQuality ); + lightManager->SetReflectionQuality( m_settings.reflectionQuality ); + } + } + else + { + Tr2LightManager::DeleteInstance(); } } @@ -657,20 +936,24 @@ void EveSpaceSceneRenderDriver::UpdateGpuParticleSystem( Tr2RenderContext& rende } } -Tr2GpuResourcePool::Texture EveSpaceSceneRenderDriver::GetDistortionMapIfNeeded( const TextureSize2D& size ) +Tr2GpuResourcePool::Texture EveSpaceSceneRenderDriver::GetDistortionMapIfNeeded( const TextureSize2D& size, bool distortion ) { - if( m_settings.enableDistortion ) + if( distortion ) { return m_gpuResourcePool.GetTempTexture( "distortionMap", size, ImageIO::PIXEL_FORMAT_B8G8R8A8_UNORM, Tr2GpuUsage::RENDER_TARGET | Tr2GpuUsage::SHADER_RESOURCE ); } return {}; } -Tr2GpuResourcePool::Texture EveSpaceSceneRenderDriver::GetVelocityMapIfNeeded( const TextureSize2D& size, const Span& outputs ) +Tr2GpuResourcePool::Texture EveSpaceSceneRenderDriver::GetVelocityMapIfNeeded( const TextureSize2D& size, const Span& outputs, const Features& featureSet ) { - USE_MAIN_THREAD_RENDER_CONTEXT(); - auto upscalingInfo = renderContext.GetUpscalingInfo( m_upscalingContext ? m_upscalingContext->GetID() : Tr2UpscalingAL::INVALID_CONTEXT_ID ); - bool needsVelocityMap = m_settings.antiAliasingQuality != AntiAliasingQuality::Disabled || ( upscalingInfo.technique != Tr2UpscalingAL::NONE && upscalingInfo.temporal ); + if( !featureSet.velocityMap ) + { + return {}; + } + + // TAA and temporal upscaling reproject with the velocity map + const bool needsVelocityMap = ( featureSet.antiAliasing && m_settings.antiAliasingQuality != AntiAliasingQuality::Disabled ) || ( featureSet.upscaling && m_upscalingInfo.temporal ); auto namedOutput = FindNamedOutput( outputs, "VelocityMap" ); Tr2GpuResourcePool::Texture velocityMap; if( needsVelocityMap || m_settings.forceVelocityMap || namedOutput ) @@ -685,10 +968,12 @@ Tr2GpuResourcePool::Texture EveSpaceSceneRenderDriver::GetVelocityMapIfNeeded( c return {}; } -Tr2GpuResourcePool::Texture EveSpaceSceneRenderDriver::GetNormalMapIfNeeded( const TextureSize2D& size, const Span& outputs ) +Tr2GpuResourcePool::Texture EveSpaceSceneRenderDriver::GetNormalMapIfNeeded( const TextureSize2D& size, const Span& outputs, const Features& featureSet ) { + // SSAO and raytraced shadows read the normal map + const bool needsNormalMap = ( featureSet.ssao && m_settings.aoQuality != AmbientOcclusionQuality::Disabled ) || ( featureSet.raytracedShadows && m_settings.shadowQuality == ShadowQuality::SHADOW_RAYTRACED ); auto namedOutput = FindNamedOutput( outputs, "NormalMap" ); - if( ( ( m_settings.aoQuality != AmbientOcclusionQuality::Disabled || m_settings.shadowQuality == ShadowQuality::SHADOW_RAYTRACED ) && Tr2Renderer::GetShaderModel() != TR2SM_3_0_LO ) || m_settings.forceNormalMap || namedOutput ) + if( ( needsNormalMap && Tr2Renderer::GetShaderModel() != TR2SM_3_0_LO ) || m_settings.forceNormalMap || namedOutput ) { auto texture = m_gpuResourcePool.GetTempTexture( "normalMap", size, ImageIO::PIXEL_FORMAT_R10G10B10A2_UNORM, Tr2GpuUsage::RENDER_TARGET | Tr2GpuUsage::SHADER_RESOURCE ); if( namedOutput ) @@ -715,8 +1000,14 @@ Tr2GpuResourcePool::Texture EveSpaceSceneRenderDriver::GetCustomStencilMapIfNeed return {}; } -Tr2GpuResourcePool::Texture EveSpaceSceneRenderDriver::GetOpaqueColorMapIfNeeded( const TextureSize2D& size, const Span& outputs ) +Tr2GpuResourcePool::Texture EveSpaceSceneRenderDriver::GetOpaqueColorMapIfNeeded( const TextureSize2D& size, const Span& outputs, const Features& featureSet ) { + // the opaque copy is only used by views that can be upscaled or anti-aliased + if( !featureSet.upscaling && !featureSet.antiAliasing ) + { + return {}; + } + auto namedOutput = FindNamedOutput( outputs, "OpaqueColorMap" ); bool needsOpaqueBackBuffer = m_upscalingContext != nullptr || m_settings.antiAliasingQuality >= AntiAliasingQuality::Medium || m_settings.forceOpaqueBuffer || namedOutput; if( !needsOpaqueBackBuffer ) diff --git a/trinity/Eve/EveSpaceSceneRenderDriver.h b/trinity/Eve/EveSpaceSceneRenderDriver.h index 722c242f0..249a64beb 100644 --- a/trinity/Eve/EveSpaceSceneRenderDriver.h +++ b/trinity/Eve/EveSpaceSceneRenderDriver.h @@ -43,14 +43,6 @@ BLUE_CLASS( EveSpaceSceneRenderDriver ) : Medium, High, }; - enum class ReflectionQuality - { - Disabled, - Low, - Medium, - High, - Ultra, - }; struct Settings { ShadowQuality shadowQuality = ShadowQuality::SHADOW_HIGH; @@ -59,6 +51,7 @@ BLUE_CLASS( EveSpaceSceneRenderDriver ) : PostProcess::Quality postProcessingQuality = PostProcess::Quality::HIGH; Tr2VolumerticQuality volumetricQuality = Tr2VolumerticQuality::High; LightingQuality lightingQuality = LightingQuality::HIGH; + ReflectionQuality reflectionQuality = ReflectionQuality::OFF; Color clearColor = Color( 0.0f, 0.0f, 0.0f, 1.0f ); @@ -95,18 +88,93 @@ BLUE_CLASS( EveSpaceSceneRenderDriver ) : EXPOSE_TO_BLUE(); private: + // The driver's features a view uses. What the scene renders is described by the + // EveSceneView::ViewInfo in RenderInfo. The features are off by default and every factory + // sets every feature, so a new feature has to be decided for each kind of view. + struct Features + { + bool upscaling = false; + bool antiAliasing = false; + bool ssao = false; + // the view gets raytraced shadows when they are selected, which read the normal map; + // reflection faces get the scene's cascaded reflection shadows instead + bool raytracedShadows = false; + bool velocityMap = false; + // special background rendering (f.ex texture background) + bool background = false; + // special overlay rendering (f.ex texture overlay) + bool overlay = false; + bool dynamicLights = false; + + static Features Main(); + static Features ReflectionFace(); + }; + + // Timer collection for the view's rendering, used by RenderView and RenderReflectionFaces + struct ViewTimers + { + Tr2ProfileTimer beginView; + Tr2ProfileTimer endView; + Tr2ProfileTimer background; + Tr2ProfileTimer depthPass; + Tr2ProfileTimer mainPass; + Tr2ProfileTimer shadows; + Tr2ProfileTimer ssao; + Tr2ProfileTimer total; + }; + + // A view rendered by RenderView: its camera and what the scene renders from it, the + // driver's features, and its view in the Tr2LightManager + struct RenderInfo + { + EveSceneView::Camera camera; + EveSceneView::ViewInfo scene; + Features features; + // the view's index in the Tr2LightManager, set by SetLightViews: 0 is the main view, + // then the reflection faces that have dynamic lights + uint32_t lightView = 0; + }; + + // Targets rendered by RenderView + struct ViewTargets + { + Tr2GpuResourcePool::Texture depth; + Tr2GpuResourcePool::Texture velocity; + Tr2GpuResourcePool::Texture opaqueColor; + }; + + // The faces the reflection probe renders in a frame, built before the lights are gathered + struct ReflectionFaces + { + Tr2ReflectionProbe* probe = nullptr; + unsigned startFace = 0; + Vector3 viewDirection = Vector3( 0.0f, 0.0f, 0.0f ); + std::vector views; + }; + + ViewTargets RenderView( const RenderInfo& view, const Tr2TextureAL& colorTarget, const Span& outputs, ViewTimers& timers, Be::Time realTime, Be::Time simTime, const Tr2ProfileTimer& rootTimer, Tr2RenderContext& renderContext, const uint32_t colorTargetSlice = 0 ); + ReflectionFaces PrepareReflectionFaces( Tr2RenderContext & renderContext ); + void SetLightViews( const RenderInfo& mainView, ReflectionFaces& faces, Tr2RenderContext& renderContext ); + void RenderReflectionFaces( const ReflectionFaces& faces, Be::Time realTime, Be::Time simTime, const Tr2ProfileTimer& rootTimer, Tr2RenderContext& renderContext ); void SetupUpscaling( const TextureSize2D& displaySize ); void PropagateSettings(); + void ApplyCamera( const EveSceneView::Camera& camera ) const; + void UpdateSceneFrame( const EveSceneView::Camera& mainCamera, Be::Time realTime, Be::Time simTime, const Tr2ProfileTimer& rootTimer, Tr2RenderContext& renderContext ); void SetCameraToRenderer( Tr2RenderContext & renderContext ) const; TextureSize2D GetRenderSize( const TextureSize2D& displaySize ) const; + EveSceneView::Camera GetMainCamera( const TextureSize2D& renderSize, const Matrix& jitterMatrix, float mipLodBias ) const; - Tr2GpuResourcePool::Texture GetDistortionMapIfNeeded( const TextureSize2D& size ); - Tr2GpuResourcePool::Texture GetVelocityMapIfNeeded( const TextureSize2D& size, const Span& outputs ); - Tr2GpuResourcePool::Texture GetNormalMapIfNeeded( const TextureSize2D& size, const Span& outputs ); + Matrix GetJitterMatrix( uint32_t renderWidth, uint32_t renderHeight, Tr2RenderContext& renderContext ) const; + float GetMipLodBias() const; + + Tr2GpuResourcePool::Texture GetDistortionMapIfNeeded( const TextureSize2D& size, bool distortion ); + Tr2GpuResourcePool::Texture GetVelocityMapIfNeeded( const TextureSize2D& size, const Span& outputs, const Features& featureSet ); + Tr2GpuResourcePool::Texture GetNormalMapIfNeeded( const TextureSize2D& size, const Span& outputs, const Features& featureSet ); Tr2GpuResourcePool::Texture GetCustomStencilMapIfNeeded( const TextureSize2D& size, const Span& outputs ); - Tr2GpuResourcePool::Texture GetOpaqueColorMapIfNeeded( const TextureSize2D& size, const Span& outputs ); + Tr2GpuResourcePool::Texture GetOpaqueColorMapIfNeeded( const TextureSize2D& size, const Span& outputs, const Features& featureSet ); void UpdateGpuParticleSystem( Tr2RenderContext & renderContext ); + void PrepareLightManager(); Tr2GpuResourcePool::Texture RenderSSAO( const Tr2TextureAL& depthMap, const Tr2TextureAL& normalMap, Tr2RenderContext& renderContext ); @@ -122,10 +190,12 @@ BLUE_CLASS( EveSpaceSceneRenderDriver ) : TriProjectionPtr m_projection; TriViewPtr m_view; + EveCameraPtr m_camera; ImageIO::PixelFormat m_internalPixelFormat = ImageIO::PIXEL_FORMAT_R16G16B16A16_FLOAT; Tr2UpscalingContextAL* m_upscalingContext = nullptr; + Tr2UpscalingAL::UpscalingInfo m_upscalingInfo; Settings m_settings; @@ -153,20 +223,20 @@ BLUE_CLASS( EveSpaceSceneRenderDriver ) : TriTextureResPtr m_reflectionCorrectionMap; TriTextureResPtr m_blackReflectionCorrectionMap; - struct + struct FrameTimers { + // once per frame timers + Tr2ProfileTimer beginFrame; Tr2ProfileTimer update; - Tr2ProfileTimer beginRender; - Tr2ProfileTimer endRender; + Tr2ProfileTimer reflections; Tr2ProfileTimer postProcess; Tr2ProfileTimer ui3d; - Tr2ProfileTimer background; - Tr2ProfileTimer depthPass; - Tr2ProfileTimer mainPass; - Tr2ProfileTimer reflections; - Tr2ProfileTimer shadows; - Tr2ProfileTimer ssao; - } m_timers; + + // one set of timers for the main rendering view, and one set of timers for each reflection cube face + ViewTimers mainView; + std::array reflectionTimers; + }; + FrameTimers m_timers; }; TYPEDEF_BLUECLASS( EveSpaceSceneRenderDriver ); diff --git a/trinity/Lights/Tr2FactionLight_Blue.cpp b/trinity/Lights/Tr2FactionLight_Blue.cpp index c666e10de..7de06d48d 100644 --- a/trinity/Lights/Tr2FactionLight_Blue.cpp +++ b/trinity/Lights/Tr2FactionLight_Blue.cpp @@ -39,6 +39,7 @@ const Be::ClassInfo* Tr2FactionLight::ExposeToBlue() MAP_ATTRIBUTE_WITH_CHOOSER( "lightingQuality", m_lightData.lightingQuality.m_filter, "Filter for lighting quality settings. Controls whether light is active with a certain lighting quality setting.", Be::READWRITE | Be::PERSIST | Be::NOTIFY, LightingQualityFilterChooser ); MAP_ATTRIBUTE_WITH_CHOOSER( "castsShadows", m_lightData.castsShadows.m_filter, "Casts shadows. If the light is also volumetric, it will create godrays around the affected objects.", Be::READWRITE | Be::PERSIST | Be::NOTIFY, PerLightShadowSettingChooser ); + MAP_ATTRIBUTE_WITH_CHOOSER( "reflectionMode", m_lightData.tags.reflects.m_filter, "Reflection quality settings. Controls whether light is active with a certain reflection quality setting.", Be::READWRITE | Be::PERSIST | Be::NOTIFY, ReflectionQualityFilterChooser ); MAP_ATTRIBUTE( "isVolumetric", m_lightData.isVolumetric, "Volumetric lights affect participating media such as fog.", Be::READWRITE | Be::NOTIFY | Be::PERSIST ) MAP_ATTRIBUTE( "scaleBrightness", m_scaleBrightness, "Scale light brightness by its radius", Be::READWRITE | Be::PERSIST ) diff --git a/trinity/Lights/Tr2Light.cpp b/trinity/Lights/Tr2Light.cpp index d6fde3c18..831820f26 100644 --- a/trinity/Lights/Tr2Light.cpp +++ b/trinity/Lights/Tr2Light.cpp @@ -147,12 +147,12 @@ void Tr2Light::AddLight( Tr2LightManager& lightManager, CXMMATRIX transform, flo if( m_type == Tr2Light::POINT_LIGHT ) { auto data = m_lightData.AsPerPointLightData( lightTransform, features, lightManager.GetCurrentSpaceSceneShadowQuality() ); - lightManager.AddLight( data, m_scaleBrightness ); + lightManager.AddLight( data, m_scaleBrightness, m_lightData.tags ); } else if( m_type == Tr2Light::SPOT_LIGHT ) { auto data = m_lightData.AsPerSpotLightData( lightTransform, features, lightManager.GetCurrentSpaceSceneShadowQuality() ); - lightManager.AddLight( data, m_scaleBrightness ); + lightManager.AddLight( data, m_scaleBrightness, m_lightData.tags ); } } diff --git a/trinity/Lights/Tr2Light.h b/trinity/Lights/Tr2Light.h index 1ed08c067..2f00493b0 100644 --- a/trinity/Lights/Tr2Light.h +++ b/trinity/Lights/Tr2Light.h @@ -50,6 +50,8 @@ struct LightData EnumFilter castsShadows; bool isVolumetric; + Tr2LightTags tags; + Be::Time startTime; }; @@ -113,4 +115,5 @@ TYPEDEF_BLUECLASS( Tr2Light ); extern const Be::VarChooser PerLightShadowSettingChooser[]; extern const Be::VarChooser Tr2LightFlagChooser[]; -extern const Be::VarChooser LightingQualityFilterChooser[]; \ No newline at end of file +extern const Be::VarChooser LightingQualityFilterChooser[]; +extern const Be::VarChooser ReflectionQualityFilterChooser[]; \ No newline at end of file diff --git a/trinity/Lights/Tr2Light_Blue.cpp b/trinity/Lights/Tr2Light_Blue.cpp index 0806a5571..614bab86c 100644 --- a/trinity/Lights/Tr2Light_Blue.cpp +++ b/trinity/Lights/Tr2Light_Blue.cpp @@ -21,6 +21,15 @@ const Be::VarChooser LightingQualityFilterChooser[] = { }; BLUE_REGISTER_ENUM_EX( "LightingQualityFilter", uint8_t, LightingQualityFilterChooser, ENUM_REG_ENUM_OBJECT_ON_MODULE ); +const Be::VarChooser ReflectionQualityFilterChooser[] = { + { "Low", BeCast( 1u << static_cast( ReflectionQuality::LOW ) ), "Active when reflection quality is Low." }, + { "Medium", BeCast( 1u << static_cast( ReflectionQuality::MEDIUM ) ), "Active when reflection quality is Medium." }, + { "High", BeCast( 1u << static_cast( ReflectionQuality::HIGH ) ), "Active when reflection quality is High." }, + { "Ultra", BeCast( 1u << static_cast( ReflectionQuality::ULTRA ) ), "Active when reflection quality is Ultra." }, + { 0 } +}; +BLUE_REGISTER_ENUM_EX( "ReflectionQualityFilter", uint8_t, ReflectionQualityFilterChooser, ENUM_REG_ENUM_OBJECT_ON_MODULE ); + const Be::VarChooser Tr2LightFlagChooser[] = { { "AFFECTS_SURFACES", BeCast( Tr2LightManager::FLAG_AFFECTS_SURFACES ), "Affects surfaces" }, diff --git a/trinity/Lights/Tr2PointLight_Blue.cpp b/trinity/Lights/Tr2PointLight_Blue.cpp index 238316983..ebbb3335f 100644 --- a/trinity/Lights/Tr2PointLight_Blue.cpp +++ b/trinity/Lights/Tr2PointLight_Blue.cpp @@ -32,10 +32,12 @@ const Be::ClassInfo* Tr2PointLight::ExposeToBlue() MAP_ATTRIBUTE_WITH_CHOOSER( "lightingQuality", m_lightData.lightingQuality.m_filter, "Filter for lighting quality settings. Controls whether light is active with a certain lighting quality setting.", Be::READWRITE | Be::PERSIST | Be::NOTIFY, LightingQualityFilterChooser ); MAP_ATTRIBUTE_WITH_CHOOSER( "castsShadows", m_lightData.castsShadows.m_filter, "Casts shadows. If the light is also volumetric, it will create godrays around the affected objects.", Be::READWRITE | Be::PERSIST | Be::NOTIFY, PerLightShadowSettingChooser ); + MAP_ATTRIBUTE_WITH_CHOOSER( "reflectionMode", m_lightData.tags.reflects.m_filter, "Reflection quality settings. Controls whether light is active with a certain reflection quality setting.", Be::READWRITE | Be::PERSIST | Be::NOTIFY, ReflectionQualityFilterChooser ); MAP_ATTRIBUTE( "isVolumetric", m_lightData.isVolumetric, "Volumetric lights affect participating media such as fog.", Be::READWRITE | Be::NOTIFY | Be::PERSIST ) MAP_ATTRIBUTE( "scaleBrightness", m_scaleBrightness, "Scale light brightness by its radius", Be::READWRITE | Be::PERSIST ) + MAP_ATTRIBUTE( "lightProfilePath", m_lightProfilePath, diff --git a/trinity/Lights/Tr2SpotLight_Blue.cpp b/trinity/Lights/Tr2SpotLight_Blue.cpp index 816d61331..087bae398 100644 --- a/trinity/Lights/Tr2SpotLight_Blue.cpp +++ b/trinity/Lights/Tr2SpotLight_Blue.cpp @@ -35,6 +35,7 @@ const Be::ClassInfo* Tr2SpotLight::ExposeToBlue() MAP_ATTRIBUTE_WITH_CHOOSER( "lightingQuality", m_lightData.lightingQuality.m_filter, "Filter for lighting quality settings. Controls whether light is active with a certain lighting quality setting.", Be::READWRITE | Be::PERSIST | Be::NOTIFY, LightingQualityFilterChooser ); MAP_ATTRIBUTE_WITH_CHOOSER( "castsShadows", m_lightData.castsShadows.m_filter, "Casts shadows. If the light is also volumetric, it will create godrays around the affected objects.", Be::READWRITE | Be::PERSIST | Be::NOTIFY, PerLightShadowSettingChooser ); + MAP_ATTRIBUTE_WITH_CHOOSER( "reflectionMode", m_lightData.tags.reflects.m_filter, "Reflection quality settings. Controls whether light is active with a certain reflection quality setting.", Be::READWRITE | Be::PERSIST | Be::NOTIFY, ReflectionQualityFilterChooser ); MAP_ATTRIBUTE( "isVolumetric", m_lightData.isVolumetric, "Volumetric lights affect participating media such as fog.", Be::READWRITE | Be::NOTIFY | Be::PERSIST ) MAP_ATTRIBUTE( "scaleBrightness", m_scaleBrightness, "Scale light brightness by its radius", Be::READWRITE | Be::PERSIST ) diff --git a/trinity/Lights/Tr2TexturedPointLight_Blue.cpp b/trinity/Lights/Tr2TexturedPointLight_Blue.cpp index 7eaed997b..a4351d8cd 100644 --- a/trinity/Lights/Tr2TexturedPointLight_Blue.cpp +++ b/trinity/Lights/Tr2TexturedPointLight_Blue.cpp @@ -35,6 +35,7 @@ const Be::ClassInfo* Tr2TexturedPointLight::ExposeToBlue() MAP_ATTRIBUTE_WITH_CHOOSER( "lightingQuality", m_lightData.lightingQuality.m_filter, "Filter for lighting quality settings. Controls whether light is active with a certain lighting quality setting.", Be::READWRITE | Be::PERSIST | Be::NOTIFY, LightingQualityFilterChooser ); MAP_ATTRIBUTE_WITH_CHOOSER( "castsShadows", m_lightData.castsShadows.m_filter, "Casts shadows. If the light is also volumetric, it will create godrays around the affected objects.", Be::READWRITE | Be::PERSIST | Be::NOTIFY, PerLightShadowSettingChooser ); + MAP_ATTRIBUTE_WITH_CHOOSER( "reflectionMode", m_lightData.tags.reflects.m_filter, "Reflection quality settings. Controls whether light is active with a certain reflection quality setting.", Be::READWRITE | Be::PERSIST | Be::NOTIFY, ReflectionQualityFilterChooser ); MAP_ATTRIBUTE( "isVolumetric", m_lightData.isVolumetric, "Volumetric lights affect participating media such as fog.", Be::READWRITE | Be::NOTIFY | Be::PERSIST ) MAP_ATTRIBUTE( "scaleBrightness", m_scaleBrightness, "Scale light brightness by its radius", Be::READWRITE | Be::PERSIST ) diff --git a/trinity/PostProcess/Tr2PostProcessRenderer.cpp b/trinity/PostProcess/Tr2PostProcessRenderer.cpp index 60fdf3726..5f0ce279b 100644 --- a/trinity/PostProcess/Tr2PostProcessRenderer.cpp +++ b/trinity/PostProcess/Tr2PostProcessRenderer.cpp @@ -644,6 +644,18 @@ void Tr2PostProcessRenderer::SetPostProcessingQuality( PostProcess::Quality qual m_quality = quality; } +Matrix Tr2PostProcessRenderer::GetJitter( uint64_t frameIndex, uint32_t renderWidth, uint32_t renderHeight ) +{ + const Vector2 samplingPatterns[] = { Vector2( .125f, -.375f ), + Vector2( -.125f, .375f ), + Vector2( .375f, .125f ), + Vector2( -.375f, -.125f ) }; + + auto samplingIndex = frameIndex % ( sizeof( samplingPatterns ) / sizeof( samplingPatterns[0] ) ); + const auto& sample = samplingPatterns[samplingIndex]; + return TranslationMatrix( Vector3( 2.0f * sample.x / static_cast( renderWidth ), 2.0f * sample.y / static_cast( renderHeight ), 0.f ) ); +} + void Tr2PostProcessRenderer::Execute( const Tr2TextureAL& destination, diff --git a/trinity/PostProcess/Tr2PostProcessRenderer.h b/trinity/PostProcess/Tr2PostProcessRenderer.h index f02aae6cd..9d4c3c9c1 100644 --- a/trinity/PostProcess/Tr2PostProcessRenderer.h +++ b/trinity/PostProcess/Tr2PostProcessRenderer.h @@ -134,6 +134,7 @@ BLUE_CLASS( Tr2PostProcessRenderer ) : PostProcess::Quality GetPostProcessingQuality() const; void SetPostProcessingQuality( PostProcess::Quality quality ); + static Matrix GetJitter( uint64_t frameIndex, uint32_t renderWidth, uint32_t renderHeight ); private: struct DownsampleData diff --git a/trinity/Tr2LightManager.cpp b/trinity/Tr2LightManager.cpp index 34e38630c..a028b58ad 100644 --- a/trinity/Tr2LightManager.cpp +++ b/trinity/Tr2LightManager.cpp @@ -30,6 +30,9 @@ namespace const uint32_t LIGHT_BUFFER_SIZE = 1024; // Size of the light index buffer (number of indices) const uint32_t INDEX_BUFFER_SIZE = 8 * 1024 * 1024; +// Size of the light index buffer of a reflection probe face (number of indices) +const uint32_t INDEX_BUFFER_SIZE_IN_REFLECTIONS = 1024 * 1024; + // Minimal size of a point light sphere in pixels before it is culled out const float CUTOFF_PIXEL_SIZE = 7.f; // Size (in pixels) for the light to stat dimming out before disappeating @@ -44,6 +47,7 @@ const uint32_t HIGH_QUALITY_ATLAS_SIZE_LOG2 = 14; const uint32_t HIGH_QUALITY_ATLAS_ENTRY_MAX_SIZE = 1 << 13; const uint32_t MAX_NUM_SHADOWCASTING_LIGHTS = 16; const uint32_t MAX_NUM_VOLUMETRIC_LIGHTS = 16; +const uint32_t MAX_NUM_VOLUMETRIC_LIGHTS_IN_REFLECTIONS = 16; const uint32_t RAYTRACING_DUMMY_RESOLUTION = 4; @@ -143,18 +147,27 @@ const BlueSharedString RtSceneTechniqueName = BlueSharedString( "RtShadowScene" } +Tr2LightManager::View::View( uint32_t maxVolumetricLights, uint32_t maxShadowCastingLights, uint32_t indexBufferSize, bool reflection ) : + reflection( reflection ), + maxVolumetricLights( maxVolumetricLights ), + maxShadowCastingLights( maxShadowCastingLights ), + indexBufferSize( indexBufferSize ) +{ + lightBuffer.CreateInstance(); + indexBuffer.CreateInstance(); + indexBufferCounter.CreateInstance(); +} + Tr2LightManager::Tr2LightManager( const char* effectPath ) { m_effect.CreateInstance(); m_effect->SetEffectPathName( effectPath ); - m_lightBuffer.CreateInstance(); - m_indexBuffer.CreateInstance(); - - m_indexBufferCounter.CreateInstance(); + m_views.emplace_back( MAX_NUM_VOLUMETRIC_LIGHTS, MAX_NUM_SHADOWCASTING_LIGHTS, INDEX_BUFFER_SIZE, false ); + View& mainView = m_views[MAIN_VIEW]; - m_lightBufferVariable.Register( "LightBuffer", m_lightBuffer ); - m_indexBufferVariable.Register( "LightIndexBuffer", m_indexBuffer ); + m_lightBufferVariable.Register( "LightBuffer", mainView.lightBuffer ); + m_indexBufferVariable.Register( "LightIndexBuffer", mainView.indexBuffer ); GlobalStore().RegisterVariable( "LightProfileArray", &GetLightProfileArray() ); m_currentSpaceSceneShadowQuality = ShadowQuality::SHADOW_DISABLED; @@ -211,31 +224,94 @@ void Tr2LightManager::DeleteInstance() singleton = nullptr; } -void Tr2LightManager::SetVariableStore() +uint16_t Tr2LightManager::PackFlags( uint16_t flags, uint16_t profileIndex ) { - m_lightBufferVariable = m_lightBuffer; - m_indexBufferVariable = m_indexBuffer; + return ( flags & ( ( 1 << FLAG_BITS ) - 1 ) ) | ( profileIndex << FLAG_BITS ); } -uint16_t Tr2LightManager::PackFlags( uint16_t flags, uint16_t profileIndex ) +Tr2LightManager::View& Tr2LightManager::GetActiveView() { - return ( flags & ( ( 1 << FLAG_BITS ) - 1 ) ) | ( profileIndex << FLAG_BITS ); + return m_views[m_activeView]; } -void Tr2LightManager::Clear( Tr2RenderContext& renderContext ) +const Tr2LightManager::View& Tr2LightManager::GetActiveView() const { - m_lightBufferVariable = m_lightBuffer; - m_indexBufferVariable = m_indexBuffer; - ClearLightIndices( renderContext ); + return m_views[m_activeView]; +} - for( auto& data : m_tlsLightData ) +void Tr2LightManager::CreateViewBuffers( View& view, Tr2RenderContext& renderContext ) +{ + view.lightBuffer->Create( LIGHT_BUFFER_SIZE, sizeof( PerLightData ), Tr2GpuBuffer::CPU_WRITABLE ); + view.lightBuffer->SetName( "Light buffer" ); + view.indexBuffer->Create( view.indexBufferSize, sizeof( uint32_t ), Tr2GpuStructuredBuffer::GPU_WRITABLE ); + view.indexBuffer->SetName( "Light indices" ); + view.indexBufferCounter->Create( 1, Tr2RenderContextEnum::PIXEL_FORMAT_R32_SINT, Tr2GpuBuffer::GPU_WRITABLE ); + ClearLightIndices( view, renderContext ); +} + +void Tr2LightManager::SetViews( const std::vector& frustums, Tr2RenderContext& renderContext ) +{ + if( frustums.empty() ) { - data.clear(); + CCP_LOGERR( "Tr2LightManager: SetViews needs at least the main view" ); + return; + } + + if( m_views.size() > frustums.size() ) + { + m_views.erase( m_views.begin() + frustums.size(), m_views.end() ); + } + while( m_views.size() < frustums.size() ) + { + m_views.emplace_back( MAX_NUM_VOLUMETRIC_LIGHTS_IN_REFLECTIONS, 0u, INDEX_BUFFER_SIZE_IN_REFLECTIONS, true ); + // during a device reset OnPrepareResources creates the buffers later + if( Tr2Renderer::IsResourceCreationAllowed() ) + { + CreateViewBuffers( m_views.back(), renderContext ); + } + } + + auto frustum = frustums.begin(); + for( auto& view : m_views ) + { + view.frustum = *frustum++; + } + + if( m_activeView >= m_views.size() ) + { + SetActiveView( MAIN_VIEW ); + } +} + +void Tr2LightManager::SetActiveView( uint32_t viewIndex ) +{ + if( viewIndex >= m_views.size() ) + { + CCP_LOGERR( "Tr2LightManager: invalid view index %u", viewIndex ); + return; + } + + m_activeView = viewIndex; + View& view = m_views[viewIndex]; + m_lightBufferVariable = view.lightBuffer; + m_indexBufferVariable = view.indexBuffer; +} + +void Tr2LightManager::Clear( Tr2RenderContext& renderContext ) +{ + for( auto& view : m_views ) + { + ClearLightIndices( view, renderContext ); + for( auto& data : view.tlsLightData ) + { + data.clear(); + } + view.lightData.clear(); + view.shadowCastingLights.clear(); + view.volumetricLights.clear(); } - m_lightData.clear(); - m_shadowCastingLights.clear(); - m_volumetricLights.clear(); + // shadow map is only on the main view currently m_ShadowMap.m_atlasNodes.resize( 1 ); m_ShadowMap.m_atlasNodes[0].x = 0; m_ShadowMap.m_atlasNodes[0].y = 0; @@ -246,14 +322,14 @@ void Tr2LightManager::Clear( Tr2RenderContext& renderContext ) m_ShadowMap.m_atlasNodes[0].lightIndex = -1; } -void Tr2LightManager::SetFrustum( const TriFrustum& frustum ) +void Tr2LightManager::AdjustLightCutoff( float lodFactor ) { - m_frustum = frustum; + m_adjustedCutoff = CUTOFF_PIXEL_SIZE * lodFactor; } -void Tr2LightManager::AdjustLightCutoff( float lodFactor ) +void Tr2LightManager::SetReflectionQuality( ReflectionQuality reflectionQuality ) { - m_adjustedCutoff = CUTOFF_PIXEL_SIZE * lodFactor; + m_reflectionQuality = reflectionQuality; } // This entire thing is annoyingly complex for what should be a simple thing... @@ -323,12 +399,15 @@ void Tr2LightManager::AddPointLight( const Vector3& position, float radius, cons { return; } - if( !m_frustum.IsSphereVisible( reinterpret_cast( &data.position ) ) ) + + View& mainView = m_views[MAIN_VIEW]; + + if( !mainView.frustum.IsSphereVisible( reinterpret_cast( &data.position ) ) ) { return; } - float size = m_frustum.GetPixelSizeAccross( reinterpret_cast( &data.position ) ); + float size = mainView.frustum.GetPixelSizeAccross( reinterpret_cast( &data.position ) ); if( size > m_adjustedCutoff ) { @@ -346,11 +425,11 @@ void Tr2LightManager::AddPointLight( const Vector3& position, float radius, cons data.direction = Vector3_16( Vector3( 1.f, 0.f, 0.f ) ); data.outerAngle = Float_16( 0.f ); data.innerAngle = Float_16( 0.f ); - m_tlsLightData.local().push_back( data ); + mainView.tlsLightData.local().push_back( data ); } } -void Tr2LightManager::AddLight( PerLightData& data, bool scaleBrightness ) +void Tr2LightManager::AddLight( PerLightData& data, bool scaleBrightness, const Tr2LightTags& tags ) { if( !AreLightFlagsValid( data.flags ) ) { @@ -362,73 +441,93 @@ void Tr2LightManager::AddLight( PerLightData& data, bool scaleBrightness ) { return; } - if( !m_frustum.IsSphereVisible( reinterpret_cast( &data.position ) ) ) - { - return; - } - float size = m_frustum.GetPixelSizeAccross( reinterpret_cast( &data.position ) ); - if( size > m_adjustedCutoff ) + for( auto& view : m_views ) { - float dimming = std::min( ( size - m_adjustedCutoff ) / FADE_SIZE, 1.f ); - if( scaleBrightness ) + // a face view only takes the lights that are tagged for the current reflection quality + if( view.reflection && !tags.reflects.HasBit( m_reflectionQuality ) ) { - dimming *= data.radius; + continue; } - data.color.x *= dimming; - data.color.y *= dimming; - data.color.z *= dimming; - bool usingShadowMap = m_currentSpaceSceneShadowQuality == ShadowQuality::SHADOW_LOW || m_currentSpaceSceneShadowQuality == ShadowQuality::SHADOW_HIGH; - if( m_currentSpaceSceneShadowQuality == ShadowQuality::SHADOW_DISABLED || - ( usingShadowMap && m_ShadowMap.m_qualityUsedByAtlas == ShadowQuality::SHADOW_DISABLED ) || - !g_useDynamicLightsShadows ) + + if( !view.frustum.IsSphereVisible( reinterpret_cast( &data.position ) ) ) + { + continue; + } + float size = view.frustum.GetPixelSizeAccross( reinterpret_cast( &data.position ) ); + if( size > m_adjustedCutoff ) { - data.flags &= ~Tr2LightManager::FLAG_CASTS_SHADOWS; + // every view works on its own copy, as the fade and the flags differ per view + PerLightData viewData = data; + float dimming = std::min( ( size - m_adjustedCutoff ) / FADE_SIZE, 1.f ); + if( scaleBrightness ) + { + dimming *= viewData.radius; + } + viewData.color.x *= dimming; + viewData.color.y *= dimming; + viewData.color.z *= dimming; + + bool usingShadowMap = m_currentSpaceSceneShadowQuality == ShadowQuality::SHADOW_LOW || m_currentSpaceSceneShadowQuality == ShadowQuality::SHADOW_HIGH; + if( view.reflection || + m_currentSpaceSceneShadowQuality == ShadowQuality::SHADOW_DISABLED || + ( usingShadowMap && m_ShadowMap.m_qualityUsedByAtlas == ShadowQuality::SHADOW_DISABLED ) || + !g_useDynamicLightsShadows ) + { + viewData.flags &= ~Tr2LightManager::FLAG_CASTS_SHADOWS; + } + view.tlsLightData.local().push_back( viewData ); } - m_tlsLightData.local().push_back( data ); } } -ALResult Tr2LightManager::ClearLightIndices( Tr2RenderContext& renderContext ) +ALResult Tr2LightManager::ClearLightIndices( Tr2LightManager::View& view, Tr2RenderContext& renderContext ) { - if( !m_indexBuffer || !m_indexBuffer->GetGpuBuffer( 0 ) ) + if( !view.indexBuffer || !view.indexBuffer->GetGpuBuffer( 0 ) ) { return S_OK; } const uint32_t clear[] = { 0, 0, 0, 0 }; - return renderContext.ClearUav( *m_indexBuffer->GetGpuBuffer( 0 ), clear ); + return renderContext.ClearUav( *view.indexBuffer->GetGpuBuffer( 0 ), clear ); } ALResult Tr2LightManager::UpdateLightBuffer( Tr2RenderContext& renderContext ) { - if( m_lightBuffer->GetGpuBuffer( 0 )->GetDesc().count < uint32_t( m_lightData.size() ) ) + View& view = GetActiveView(); + if( view.lightBuffer->GetGpuBuffer( 0 )->GetDesc().count < uint32_t( view.lightData.size() ) ) { - CR_RETURN_HR( m_lightBuffer->Create( - std::max( m_lightBuffer->GetGpuBuffer( 0 )->GetDesc().count + 1024, uint32_t( m_lightData.size() ) ), + CR_RETURN_HR( view.lightBuffer->Create( + std::max( view.lightBuffer->GetGpuBuffer( 0 )->GetDesc().count + 1024, uint32_t( view.lightData.size() ) ), sizeof( PerLightData ), Tr2GpuBuffer::CPU_WRITABLE ) ); - m_lightBuffer->SetName( "Light buffer" ); + view.lightBuffer->SetName( "Light buffer" ); } - auto lightCount = std::min( m_lightBuffer->GetGpuBuffer( 0 )->GetDesc().count, uint32_t( m_lightData.size() ) ); + auto lightCount = std::min( view.lightBuffer->GetGpuBuffer( 0 )->GetDesc().count, uint32_t( view.lightData.size() ) ); Vector4* data; - CR_RETURN_HR( m_lightBuffer->GetGpuBuffer( 0 )->MapForWriting( data, renderContext ) ); - memcpy( data, m_lightData.data(), lightCount * sizeof( PerLightData ) ); - CCP_STATS_ADD( lightsGathered, m_lightData.size() ); - m_lightBuffer->GetGpuBuffer( 0 )->UnmapForWriting( renderContext ); + CR_RETURN_HR( view.lightBuffer->GetGpuBuffer( 0 )->MapForWriting( data, renderContext ) ); + memcpy( data, view.lightData.data(), lightCount * sizeof( PerLightData ) ); + CCP_STATS_ADD( lightsGathered, view.lightData.size() ); + view.lightBuffer->GetGpuBuffer( 0 )->UnmapForWriting( renderContext ); return S_OK; } ALResult Tr2LightManager::DoUpdateLists( const Tr2TextureAL& depthMap, Tr2RenderContext& renderContext ) { - if( !m_lightBuffer->IsValid() || !m_indexBuffer->IsValid() || !m_indexBufferCounter->IsValid() ) + View& view = GetActiveView(); + if( !view.lightBuffer->IsValid() || !view.indexBuffer->IsValid() || !view.indexBufferCounter->IsValid() ) { return E_INVALIDCALL; } CR_RETURN_HR( UpdateLightBuffer( renderContext ) ); + // the effect is shared by all the views, so it has to be pointed at the buffers of the active one + m_effect->SetParameter( BlueSharedString( "LightBuffer" ), view.lightBuffer ); + m_effect->SetParameter( BlueSharedString( "LightIndices" ), view.indexBuffer ); + m_effect->SetParameter( BlueSharedString( "LightIndexCount" ), view.indexBufferCounter ); + PerFrameData perFrameData; perFrameData.projInverse = Transpose( Tr2Renderer::GetInverseProjectionTransform() ); perFrameData.viewInverse = Transpose( Tr2Renderer::GetInverseViewTransform() ); @@ -437,8 +536,8 @@ ALResult Tr2LightManager::DoUpdateLists( const Tr2TextureAL& depthMap, Tr2Render perFrameData.height = uint32_t( renderContext.m_esm.GetRenderTargetHeight() ); perFrameData.tilesX = ( perFrameData.width + ( TILE_WIDTH - 1 ) ) / TILE_WIDTH; perFrameData.tilesY = ( perFrameData.height + ( TILE_HEIGHT - 1 ) ) / TILE_HEIGHT; - perFrameData.lightCount = std::min( m_lightBuffer->GetGpuBuffer( 0 )->GetDesc().count, uint32_t( m_lightData.size() ) ); - perFrameData.indexBufferSize = INDEX_BUFFER_SIZE; + perFrameData.lightCount = std::min( view.lightBuffer->GetGpuBuffer( 0 )->GetDesc().count, uint32_t( view.lightData.size() ) ); + perFrameData.indexBufferSize = view.indexBufferSize; m_effect->SetParameter( BlueSharedString( "DepthMap" ), depthMap ); @@ -461,7 +560,7 @@ ALResult Tr2LightManager::DoUpdateLists( const Tr2TextureAL& depthMap, Tr2Render return S_OK; } -void Tr2LightManager::CreateShadowMapAtlas( uint32_t numShadowCastingLights, const std::vector& lightTuples ) +void Tr2LightManager::CreateShadowMapAtlas( std::vector& lightDataList, uint32_t numShadowCastingLights, const std::vector& lightTuples ) { bool everythingFit = false; // 5 iterations are more than enough. With the current values we should actually only need at most 4 iterations. @@ -485,7 +584,7 @@ void Tr2LightManager::CreateShadowMapAtlas( uint32_t numShadowCastingLights, con for( uint32_t i = 0; i < numShadowCastingLights; i++ ) { uint32_t lightIndex = lightTuples[i].lightIndex; - Tr2LightManager::PerLightData& lightData = m_lightData[lightIndex]; + Tr2LightManager::PerLightData& lightData = lightDataList[lightIndex]; uint32_t size = ( (uint32_t)lightTuples[i].sizeAcross ) >> entryInverseScaleFactorLog2; size = ClampUInt( size, m_ShadowMap.m_atlasSettings.entryMinSize, entryMaxSize ); @@ -541,27 +640,36 @@ Tr2RtShaderTableDescriptionAL* Tr2LightManager::GetRaytracingShaderTableDesc() void Tr2LightManager::ResolveLightData() { - m_lightData.clear(); - for( auto& data : m_tlsLightData ) + for( auto& view : m_views ) + { + ResolveView( view ); + } +} + +void Tr2LightManager::ResolveView( View& view ) +{ + std::vector& lightData = view.lightData; + lightData.clear(); + for( auto& data : view.tlsLightData ) { if( !data.empty() ) { - m_lightData.insert( end( m_lightData ), begin( data ), end( data ) ); + lightData.insert( end( lightData ), begin( data ), end( data ) ); } data.clear(); } - m_shadowCastingLights.clear(); - m_volumetricLights.clear(); + view.shadowCastingLights.clear(); + view.volumetricLights.clear(); // filter volumetric lights std::vector lightTuples; { - for( uint32_t i = 0; i < m_lightData.size(); i++ ) + for( uint32_t i = 0; i < lightData.size(); i++ ) { - if( ( m_lightData[i].flags & FLAG_IS_VOLUMETRIC ) != 0 ) + if( ( lightData[i].flags & FLAG_IS_VOLUMETRIC ) != 0 ) { - float sizeAcross = m_frustum.GetPixelSizeAccrossEst( reinterpret_cast( &m_lightData[i].position ) ); + float sizeAcross = view.frustum.GetPixelSizeAccrossEst( reinterpret_cast( &lightData[i].position ) ); sizeAcross = sizeAcross == std::numeric_limits::max() ? ( 1 << HIGH_QUALITY_ATLAS_SIZE_LOG2 ) : sizeAcross; lightTuples.push_back( LightScreenSizeTuple{ i, sizeAcross } ); } @@ -573,21 +681,22 @@ void Tr2LightManager::ResolveLightData() } ); // keep only the largest lights - uint32_t numVolumetricLights = min( MAX_NUM_VOLUMETRIC_LIGHTS, (uint32_t)lightTuples.size() ); - m_volumetricLights.resize( numVolumetricLights ); + uint32_t numVolumetricLights = min( view.maxVolumetricLights, (uint32_t)lightTuples.size() ); + view.volumetricLights.resize( numVolumetricLights ); uint32_t i = 0; for( ; i < numVolumetricLights; i++ ) { - m_volumetricLights[i] = lightTuples[i].lightIndex; + view.volumetricLights[i] = lightTuples[i].lightIndex; } for( ; i < lightTuples.size(); i++ ) { - Tr2LightManager::PerLightData& lightData = m_lightData[lightTuples[i].lightIndex]; - lightData.flags &= ~Tr2LightManager::FLAG_IS_VOLUMETRIC; + Tr2LightManager::PerLightData& light = lightData[lightTuples[i].lightIndex]; + light.flags &= ~Tr2LightManager::FLAG_IS_VOLUMETRIC; } } - if( !g_useDynamicLightsShadows || m_currentSpaceSceneShadowQuality == ShadowQuality::SHADOW_DISABLED ) + // the shadow map atlas is only on the main view currently + if( view.reflection || !g_useDynamicLightsShadows || m_currentSpaceSceneShadowQuality == ShadowQuality::SHADOW_DISABLED ) { return; } @@ -596,11 +705,11 @@ void Tr2LightManager::ResolveLightData() uint32_t numShadowCastingLights = 0; lightTuples.resize( 0 ); { - for( uint32_t i = 0; i < m_lightData.size(); i++ ) + for( uint32_t i = 0; i < lightData.size(); i++ ) { - if( ( m_lightData[i].flags & FLAG_CASTS_SHADOWS ) != 0 ) + if( ( lightData[i].flags & FLAG_CASTS_SHADOWS ) != 0 ) { - float sizeAcross = m_frustum.GetPixelSizeAccrossEst( reinterpret_cast( &m_lightData[i].position ) ); + float sizeAcross = view.frustum.GetPixelSizeAccrossEst( reinterpret_cast( &lightData[i].position ) ); sizeAcross = sizeAcross == std::numeric_limits::max() ? ( 1 << HIGH_QUALITY_ATLAS_SIZE_LOG2 ) : sizeAcross; lightTuples.push_back( LightScreenSizeTuple{ i, sizeAcross } ); } @@ -612,49 +721,47 @@ void Tr2LightManager::ResolveLightData() } ); // keep only the largest lights - numShadowCastingLights = min( MAX_NUM_SHADOWCASTING_LIGHTS, (uint32_t)lightTuples.size() ); - m_shadowCastingLights.resize( numShadowCastingLights ); + numShadowCastingLights = min( view.maxShadowCastingLights, (uint32_t)lightTuples.size() ); + view.shadowCastingLights.resize( numShadowCastingLights ); uint32_t i = 0; for( ; i < numShadowCastingLights; i++ ) { - m_shadowCastingLights[i] = lightTuples[i].lightIndex; + view.shadowCastingLights[i] = lightTuples[i].lightIndex; if( m_currentSpaceSceneShadowQuality == ShadowQuality::SHADOW_RAYTRACED ) { - Tr2LightManager::PerLightData& lightData = m_lightData[lightTuples[i].lightIndex]; - lightData.Raytracing.shadowMask = 1 << i; + Tr2LightManager::PerLightData& light = lightData[lightTuples[i].lightIndex]; + light.Raytracing.shadowMask = 1 << i; } } for( ; i < lightTuples.size(); i++ ) { - Tr2LightManager::PerLightData& lightData = m_lightData[lightTuples[i].lightIndex]; - lightData.flags &= ~Tr2LightManager::FLAG_CASTS_SHADOWS; + Tr2LightManager::PerLightData& light = lightData[lightTuples[i].lightIndex]; + light.flags &= ~Tr2LightManager::FLAG_CASTS_SHADOWS; if( m_currentSpaceSceneShadowQuality == ShadowQuality::SHADOW_RAYTRACED ) { - lightData.Raytracing.shadowMask = 0; + light.Raytracing.shadowMask = 0; } } } if( m_currentSpaceSceneShadowQuality == ShadowQuality::SHADOW_LOW || m_currentSpaceSceneShadowQuality == ShadowQuality::SHADOW_HIGH ) { - CreateShadowMapAtlas( numShadowCastingLights, lightTuples ); + CreateShadowMapAtlas( lightData, numShadowCastingLights, lightTuples ); } } ALResult Tr2LightManager::UpdateLists( const Tr2TextureAL& depthMap, Tr2RenderContext& renderContext ) { - m_lightBufferVariable = m_lightBuffer; - m_indexBufferVariable = m_indexBuffer; - - if( m_lightData.empty() ) + View& view = GetActiveView(); + if( view.lightData.empty() ) { - return ClearLightIndices( renderContext ); + return ClearLightIndices( view, renderContext ); } auto hr = DoUpdateLists( depthMap, renderContext ); if( FAILED( hr ) ) { - ClearLightIndices( renderContext ); + ClearLightIndices( view, renderContext ); } return hr; @@ -680,17 +787,12 @@ bool Tr2LightManager::OnPrepareResources() { USE_MAIN_THREAD_RENDER_CONTEXT(); - m_lightBuffer->Create( LIGHT_BUFFER_SIZE, sizeof( PerLightData ), Tr2GpuBuffer::CPU_WRITABLE ); - m_lightBuffer->SetName( "Light buffer" ); - m_indexBuffer->Create( INDEX_BUFFER_SIZE, sizeof( uint32_t ), Tr2GpuStructuredBuffer::GPU_WRITABLE ); - m_indexBuffer->SetName( "Light indices" ); - m_indexBufferCounter->Create( 1, Tr2RenderContextEnum::PIXEL_FORMAT_R32_SINT, Tr2GpuBuffer::GPU_WRITABLE ); - ClearLightIndices( renderContext ); + for( auto& view : m_views ) + { + CreateViewBuffers( view, renderContext ); + } m_perFrameData.Create( sizeof( PerFrameData ), Tr2ConstantUsageAL::ONE_SHOT, nullptr, renderContext ); - m_effect->SetParameter( BlueSharedString( "LightBuffer" ), m_lightBuffer ); - m_effect->SetParameter( BlueSharedString( "LightIndices" ), m_indexBuffer ); - m_effect->SetParameter( BlueSharedString( "LightIndexCount" ), m_indexBufferCounter ); m_effect->SetParameter( BlueSharedString( "DepthMap" ), Tr2TextureAL() ); return true; @@ -709,17 +811,17 @@ Tr2TextureArray& Tr2LightManager::GetLightProfileArray() const std::vector& Tr2LightManager::GetShadowCastingLights() const { - return m_shadowCastingLights; + return GetActiveView().shadowCastingLights; } const std::vector& Tr2LightManager::GetVolumetricLights() const { - return m_volumetricLights; + return GetActiveView().volumetricLights; } const Tr2LightManager::PerLightData& Tr2LightManager::GetLightData( uint32_t index ) const { - return m_lightData[index]; + return GetActiveView().lightData[index]; } Tr2GpuResourcePool::Texture Tr2LightManager::GetShadowMapAtlas( Tr2GpuResourcePool& gpuResourcePool ) @@ -849,7 +951,8 @@ Tr2GpuResourcePool::Texture Tr2LightManager::RenderRaytracedShadows( GPU_REGION( renderContext, "Raytraced dynamic shadows" ); TRINITY_STATS_ZONE( __FUNCTION__ ); - if( m_shadowCastingLights.size() == 0 || !g_useDynamicLightsShadows ) + const View& view = GetActiveView(); + if( view.shadowCastingLights.size() == 0 || !g_useDynamicLightsShadows ) { return {}; } @@ -910,13 +1013,13 @@ Tr2GpuResourcePool::Texture Tr2LightManager::RenderRaytracedShadows( data->resolution = Vector2( float( destTex->GetWidth() ), float( destTex->GetHeight() ) ); - for( uint32_t j = 0; j < m_shadowCastingLights.size(); j++ ) + for( uint32_t j = 0; j < view.shadowCastingLights.size(); j++ ) { - uint32_t lightIndex = m_shadowCastingLights[j]; - PerLightData& lightData = m_lightData[lightIndex]; + uint32_t lightIndex = view.shadowCastingLights[j]; + const PerLightData& lightData = view.lightData[lightIndex]; data->dynamicLights[j] = lightData; } - data->numDynamicLights = (uint32_t)m_shadowCastingLights.size(); + data->numDynamicLights = (uint32_t)view.shadowCastingLights.size(); m_Raytracing.m_perFrameData.Unlock( renderContext ); } diff --git a/trinity/Tr2LightManager.h b/trinity/Tr2LightManager.h index 260664815..6143cafc1 100644 --- a/trinity/Tr2LightManager.h +++ b/trinity/Tr2LightManager.h @@ -12,6 +12,7 @@ #include "Raytracing/Tr2RaytracingGeometry.h" #include "Tr2Denoiser.h" +#include "EnumFilter.h" BLUE_DECLARE( Tr2Effect ); BLUE_DECLARE( Tr2GpuBuffer ); @@ -38,20 +39,38 @@ enum class LightingQuality : int32_t HIGH, }; +enum class ReflectionQuality : int32_t +{ + OFF, + LOW, + MEDIUM, + HIGH, + ULTRA, +}; + +struct Tr2LightTags +{ + EnumFilter reflects; +}; + // -------------------------------------------------------------------------------------- // Description: // Manages light sources for tiled/clustered lighting. An object of this class does not // store lights permanently. Rather a user adds lights every frame and then the object -// updates GPU buffers needed for tiled lighting. Normally the per-frame operations -// for the light manager would look like: -// lightManager.Clear(); -// lightManager.SetFrustum( frustum ); +// updates GPU buffers needed for tiled lighting. The lights are gathered once per frame +// for every view (the main view and the reflection probe faces), and each view has its +// own lights and buffers. Normally the per-frame operations for the light manager would +// look like: +// lightManager.SetViews( frustums, renderContext ); // the main view first +// lightManager.Clear( renderContext ); // for each in scene.lights -// lightManager.AddPointLight( each ); // this can be done in parallel -// lightManager.ResolveLightData(); -// lightManager.UpdateLists(); -// renderYourStuff(); -// Light manager GPU buffers are accessed through variable store with names +// lightManager.AddLight( each ); // this can be done in parallel +// lightManager.ResolveLightData(); +// for each view +// lightManager.SetActiveView( position of the view in frustums ); +// lightManager.UpdateLists( depthMap, renderContext ); +// renderYourStuff(); +// The active view's GPU buffers are accessed through variable store with names // "LightBuffer" and "LightIndexBuffer". // -------------------------------------------------------------------------------------- class Tr2LightManager : public Tr2DeviceResource @@ -91,6 +110,7 @@ class Tr2LightManager : public Tr2DeviceResource } Raytracing; }; }; + typedef Tr2EnumerableThreadSpecific> ThreadLocalLightData; struct ShadowMapAtlasSettings { @@ -117,15 +137,22 @@ class Tr2LightManager : public Tr2DeviceResource static uint16_t PackFlags( uint16_t flags, uint16_t profileIndex ); void Clear( Tr2RenderContext& renderContext ); - void SetFrustum( const TriFrustum& frustum ); + + // The views the lights are gathered for this frame, set before the gather. The first + // frustum is the main view's, the others are reflection probe faces. A view's index for + // SetActiveView is its position in frustums. Views are created when the list grows and + // released when it shrinks. + void SetViews( const std::vector& frustums, Tr2RenderContext& renderContext ); + void SetActiveView( uint32_t viewIndex ); + void AddPointLight( const Vector3& position, float radius, const Color& color, Float_16 innerRadius = Float_16( 0.f ), uint16_t flags = FLAG_DEFAULT, bool scaleBrightness = true ); - void AddLight( PerLightData& data, bool scaleBrightness = true ); + void AddLight( PerLightData& data, bool scaleBrightness = true, const Tr2LightTags& tags = Tr2LightTags() ); void ResolveLightData(); ALResult UpdateLists( const Tr2TextureAL& depthMap, Tr2RenderContext& renderContext ); - void SetVariableStore(); void AdjustLightCutoff( float lodFactor ); void SetShadowQuality( ShadowQuality shadowQuality, uint64_t frameCounter ); + void SetReflectionQuality( ReflectionQuality reflectionQuality ); void SetLightingQuality( LightingQuality lightingQuality ); LightingQuality GetLightingQuality() const; @@ -182,36 +209,55 @@ class Tr2LightManager : public Tr2DeviceResource float sizeAcross; }; + struct View + { + bool reflection = false; // face view: needs the reflection tag + TriFrustum frustum; + uint32_t maxVolumetricLights; + uint32_t maxShadowCastingLights; + uint32_t indexBufferSize; + + ThreadLocalLightData tlsLightData; + std::vector lightData; + std::vector shadowCastingLights; + std::vector volumetricLights; + Tr2GpuStructuredBufferPtr lightBuffer; + Tr2GpuStructuredBufferPtr indexBuffer; + Tr2GpuBufferPtr indexBufferCounter; + + View( uint32_t maxVolumetricLights, uint32_t maxShadowCastingLights, uint32_t indexBufferSize, bool reflection ); + }; + virtual bool OnPrepareResources(); + View& GetActiveView(); + const View& GetActiveView() const; + void CreateViewBuffers( View& view, Tr2RenderContext& renderContext ); + void ResolveView( View& view ); + ALResult DoUpdateLists( const Tr2TextureAL& depthMap, Tr2RenderContext& renderContext ); - ALResult ClearLightIndices( Tr2RenderContext& renderContext ); + ALResult ClearLightIndices( Tr2LightManager::View& view, Tr2RenderContext& renderContext ); ALResult UpdateLightBuffer( Tr2RenderContext& renderContext ); uint32_t InsertAtlasNode( std::vector& atlasNodes, uint32_t nodeId, uint32_t lightIndex, int32_t width, int32_t height ); bool GetShadowMapAtlasEntry( uint32_t lightIndex, uint32_t width, uint32_t height, uint32_t& out_posX, uint32_t& out_posY ); - void CreateShadowMapAtlas( uint32_t numShadowCastingLights, const std::vector& lightTuples ); + void CreateShadowMapAtlas( std::vector& lightData, uint32_t numShadowCastingLights, const std::vector& lightTuples ); - Tr2EnumerableThreadSpecific> m_tlsLightData; - std::vector m_lightData; - std::vector m_shadowCastingLights; - std::vector m_volumetricLights; - Tr2GpuStructuredBufferPtr m_lightBuffer; - Tr2GpuStructuredBufferPtr m_indexBuffer; + static const uint32_t MAIN_VIEW = 0; // the reflection probe faces follow it + std::vector m_views; + uint32_t m_activeView = MAIN_VIEW; - Tr2GpuBufferPtr m_indexBufferCounter; Tr2ConstantBufferAL m_perFrameData; Tr2EffectPtr m_effect; Tr2Variable m_lightBufferVariable; Tr2Variable m_indexBufferVariable; - TriFrustum m_frustum; - float m_adjustedCutoff; uint32_t nextFrameShadowQuality; // bitmask, collecting ShadowQualities during the current frame ShadowQuality m_currentSpaceSceneShadowQuality; LightingQuality m_lightingQuality = LightingQuality::HIGH; + ReflectionQuality m_reflectionQuality = ReflectionQuality::OFF; uint64_t m_currentFrameCounter; struct diff --git a/trinity/Tr2ReflectionProbe.cpp b/trinity/Tr2ReflectionProbe.cpp index 3b7224ce3..7a5503ed9 100644 --- a/trinity/Tr2ReflectionProbe.cpp +++ b/trinity/Tr2ReflectionProbe.cpp @@ -30,7 +30,6 @@ Tr2ReflectionProbe::Tr2ReflectionProbe( IRoot* lockobj ) : m_hasData( false ), m_position( 0, 0, 0 ), m_intermediateSize( FILTER_SIZE * 4 ), - m_prevCullInversion( false ), m_customSourceTexture(), m_renderFrequency( ONE_SIDE_PER_FRAME ), m_currentFrame( 0 ), @@ -41,11 +40,6 @@ Tr2ReflectionProbe::Tr2ReflectionProbe( IRoot* lockobj ) : m_backlightColor( 1, 1, 1, 1 ), m_backlightContrast( 16 ) { - for( unsigned i = 0; i < 6; i++ ) - { - m_renderTargets[i].CreateInstance(); - } - m_renderTargetCube.CreateInstance(); m_preFilterEffect.CreateInstance(); m_copyMipEffect.CreateInstance(); @@ -70,13 +64,6 @@ bool Tr2ReflectionProbe::HasData() const return m_hasData && m_postFilterTarget->IsValid(); } -TriFrustum Tr2ReflectionProbe::GetFrustum( unsigned face, Tr2RenderContext& renderContext ) -{ - TriFrustum frustum = TriFrustum(); - frustum.DeriveFrustum( &Tr2Renderer::GetViewTransform(), &m_position, &Tr2Renderer::GetProjectionTransform(), renderContext.m_esm.GetViewport() ); - return frustum; -} - uint8_t Tr2ReflectionProbe::GetStartFace() const { if( m_renderFrequency == ALL_SIDES_PER_FRAME || !m_onePassDone ) @@ -95,64 +82,22 @@ uint8_t Tr2ReflectionProbe::GetEndFace() const return m_currentFrame + 1; } - -void Tr2ReflectionProbe::InitRenderPass( Tr2RenderContext& renderContext ) +Matrix Tr2ReflectionProbe::GetFaceProjection( const Matrix& currentProjection ) const { - if( !m_lockPosition ) - { - m_position = Tr2Renderer::GetViewPosition(); - } - - renderContext.m_esm.PushViewport(); - Tr2Renderer::PushViewTransform(); - Tr2Renderer::PushProjection(); - renderContext.m_esm.PushRenderTarget(); - renderContext.m_esm.PushDepthStencilBuffer(); - - renderContext.m_esm.SetViewport( m_intermediateSize, m_intermediateSize, 0, 0, 0, 1 ); - // Square projection matrix, with near and far clip planes from the current projection Matrix newProjection = IdentityMatrix(); - const Matrix& currentProjection = Tr2Renderer::GetProjectionTransform(); newProjection.m[2][2] = currentProjection.m[2][2]; newProjection.m[2][3] = -1.0f; newProjection.m[3][2] = currentProjection.m[3][2]; newProjection.m[3][3] = 0.0f; - - Tr2Renderer::SetProjectionTransform( newProjection ); - - // We need to invert cull-mode because of the left-handed coordinates of the cube rendertarget - m_prevCullInversion = renderContext.m_esm.IsCullModeInverted(); - renderContext.m_esm.SetInvertedCullMode( true ); - -#if !TRINITY_PLATFORM_SUPPORTS_RENDER_PASS_HINTS - { - GPU_REGION( renderContext, "Reflection Depth Clear" ); - - for( int i = GetStartFace(); i < GetEndFace(); i++ ) - { - renderContext.SetRenderTarget( *m_renderTargets[i], 0 ); - renderContext.m_esm.SetDepthStencilBuffer( m_stencilMaps[i] ); - CR( renderContext.Clear( CLEARFLAGS_ZBUFFER | CLEARFLAGS_TARGET, 0, 0, 0 ) ); - } - } -#endif -} - -Tr2TextureAL Tr2ReflectionProbe::GetDepthBuffer( unsigned face ) -{ - return m_stencilMaps[face]; + return newProjection; } -void Tr2ReflectionProbe::StartRenderFace( unsigned face, Tr2RenderContext& renderContext ) +Matrix Tr2ReflectionProbe::GetFaceViewTransform( unsigned face ) const { - renderContext.RenderPassHint( { Tr2LoadAction::CLEAR, Tr2StoreAction::STORE, 0 }, { Tr2LoadAction::CLEAR, Tr2StoreAction::DONT_CARE, 0.F } ); - - renderContext.m_esm.SetRenderTarget( 0, *m_renderTargets[face] ); - renderContext.m_esm.SetDepthStencilBuffer( m_stencilMaps[face] ); - static const Vector3 faceDirections[] = { - Vector3( 0, 1, 0 ), Vector3( 1, 0, 0 ), //+X + Vector3( 0, 1, 0 ), + Vector3( 1, 0, 0 ), //+X Vector3( 0, 1, 0 ), Vector3( -1, 0, 0 ), //-X Vector3( 0, 0, -1 ), @@ -171,29 +116,25 @@ void Tr2ReflectionProbe::StartRenderFace( unsigned face, Tr2RenderContext& rende const Vector3& up = faceDirections[face * 2]; const Vector3& at = faceDirections[face * 2 + 1]; - Tr2Renderer::SetViewTransform( LookAtMatrix( m_position, m_position + at, up ) * xInverter ); + return LookAtMatrix( m_position, m_position + at, up ) * xInverter; } -void Tr2ReflectionProbe::EndRenderPass( Tr2RenderContext& renderContext ) +void Tr2ReflectionProbe::UpdatePosition( const Vector3& viewPosition ) { + if( !m_lockPosition ) { - GPU_REGION( renderContext, "Reflection RT Copy" ); - - for( int i = GetStartFace(); i < GetEndFace(); i++ ) - { - CR( m_renderTargetCube->GetRenderTarget().CopySubresourceRegion( Tr2TextureSubresource( i, 0 ), *m_renderTargets[i], Tr2TextureSubresource( 0, 0 ), renderContext ) ); - } + m_position = viewPosition; } +} - renderContext.m_esm.SetInvertedCullMode( m_prevCullInversion ); - - renderContext.m_esm.PopDepthStencilBuffer(); - renderContext.m_esm.PopRenderTarget(); - Tr2Renderer::PopProjection(); - Tr2Renderer::PopViewTransform(); - renderContext.m_esm.PopViewport(); +const Tr2TextureAL& Tr2ReflectionProbe::GetCubeTarget() const +{ + return m_renderTargetCube->GetRenderTarget(); +} - Filter( renderContext ); +void Tr2ReflectionProbe::FinishFaces( const Vector3& viewDirection, Tr2RenderContext& renderContext ) +{ + Filter( viewDirection, renderContext ); m_hasData = true; m_onePassDone = true; @@ -242,21 +183,6 @@ bool Tr2ReflectionProbe::OnPrepareResources() bool Tr2ReflectionProbe::DoPrepareResources( ImageIO::PixelFormat rtFormat, Tr2PrimaryRenderContext& renderContext ) { - - for( int i = 0; i < 6; i++ ) - { - if( !m_renderTargets[i]->IsValid() ) - { - CR_RETURN_VAL( m_renderTargets[i]->CreateManual( m_intermediateSize, m_intermediateSize, 1, rtFormat, 0, 0, EX_BIND_UNORDERED_ACCESS, TEX_TYPE_2D, Tr2CpuUsage::NONE, Tr2GpuUsage::RENDER_TARGET ), false ); - } - - if( !m_stencilMaps[i].IsValid() ) - { - int stencilSize = ( m_customSourceTexture && m_customSourceTexture->IsValid() ) ? m_customSourceTexture->GetWidth() : m_intermediateSize; - CR_RETURN_VAL( m_stencilMaps[i].Create( Tr2BitmapDimensions( stencilSize, stencilSize, 1, PIXEL_FORMAT_D32_FLOAT ), Tr2GpuUsage::DEPTH_STENCIL | Tr2GpuUsage::SHADER_RESOURCE, renderContext ), false ); - } - } - if( !m_renderTargetCube->IsValid() ) { CR_RETURN_VAL( m_renderTargetCube->Create( m_intermediateSize, m_intermediateSize, 1, rtFormat, 0, 0, EX_BIND_UNORDERED_ACCESS, TEX_TYPE_CUBE ), false ); @@ -330,12 +256,6 @@ bool Tr2ReflectionProbe::OnModified( Be::Var* value ) void Tr2ReflectionProbe::DestroyRenderTargets() { - for( unsigned i = 0; i < 6; i++ ) - { - m_renderTargets[i]->Destroy(); - m_stencilMaps[i] = Tr2TextureAL(); - } - m_renderTargetCube->Destroy(); m_preFilterTarget->Destroy(); m_postFilterTarget->Destroy(); @@ -344,7 +264,7 @@ void Tr2ReflectionProbe::DestroyRenderTargets() m_currentFrame = 0; } -void Tr2ReflectionProbe::Filter( Tr2RenderContext& renderContext ) +void Tr2ReflectionProbe::Filter( const Vector3& viewDirection, Tr2RenderContext& renderContext ) { if( !IsValid() ) return; @@ -356,11 +276,11 @@ void Tr2ReflectionProbe::Filter( Tr2RenderContext& renderContext ) { m_preFilterEffect->SetParameter( s_backLightColorName, Vector4( m_backlightColor ) ); m_preFilterEffect->SetParameter( s_backLightContrastName, m_backlightContrast ); - m_preFilterEffect->SetParameter( s_viewDirectionName, Tr2Renderer::GetInverseViewTransform().GetZ() ); + m_preFilterEffect->SetParameter( s_viewDirectionName, viewDirection ); m_copyMipEffect->SetParameter( s_backLightColorName, Vector4( m_backlightColor ) ); m_copyMipEffect->SetParameter( s_backLightContrastName, m_backlightContrast ); - m_copyMipEffect->SetParameter( s_viewDirectionName, Tr2Renderer::GetInverseViewTransform().GetZ() ); + m_copyMipEffect->SetParameter( s_viewDirectionName, viewDirection ); } Tr2Renderer::RunComputeShader( m_preFilterEffect, FILTER_SIZE * 2 / 8, FILTER_SIZE * 2 / 8, 6, renderContext ); @@ -385,7 +305,7 @@ void Tr2ReflectionProbe::RunFilter() USE_MAIN_THREAD_RENDER_CONTEXT(); DestroyRenderTargets(); DoPrepareResources( PIXEL_FORMAT_R16G16B16A16_FLOAT, renderContext ); - Filter( renderContext ); + Filter( Tr2Renderer::GetInverseViewTransform().GetZ(), renderContext ); } bool Tr2ReflectionProbe::IsHollyWoodModeOn() const diff --git a/trinity/Tr2ReflectionProbe.h b/trinity/Tr2ReflectionProbe.h index ba1883391..93346d2f6 100644 --- a/trinity/Tr2ReflectionProbe.h +++ b/trinity/Tr2ReflectionProbe.h @@ -3,8 +3,6 @@ #pragma once #include "Tr2DeviceResource.h" -#include -#include "TriFrustum.h" BLUE_DECLARE( Tr2Effect ); BLUE_DECLARE( Tr2TextureReference ); @@ -33,12 +31,6 @@ BLUE_CLASS( Tr2ReflectionProbe ) : bool IsValid(); bool HasData() const; - void InitRenderPass( Tr2RenderContext & renderContext ); - void StartRenderFace( unsigned face, Tr2RenderContext& renderContext ); - void EndRenderPass( Tr2RenderContext & renderContext ); - Tr2TextureAL GetDepthBuffer( unsigned face ); - - TriFrustum GetFrustum( unsigned face, Tr2RenderContext& renderContext ); Tr2RenderTargetPtr GetReflection(); void SetPosition( Vector3 position ); @@ -55,14 +47,16 @@ BLUE_CLASS( Tr2ReflectionProbe ) : uint8_t GetStartFace() const; uint8_t GetEndFace() const; - const Tr2TextureAL& GetDepthBuffer( unsigned face ) const - { - return m_stencilMaps[face]; - } + Matrix GetFaceViewTransform( unsigned face ) const; + Matrix GetFaceProjection( const Matrix& currentProjection ) const; + void UpdatePosition( const Vector3& viewPosition ); + const Tr2TextureAL& GetCubeTarget() const; + // Filters the rendered faces and advances to the next faces. + void FinishFaces( const Vector3& viewDirection, Tr2RenderContext& renderContext ); private: void RunFilter(); - void Filter( Tr2RenderContext & renderContext ); + void Filter( const Vector3& viewDirection, Tr2RenderContext& renderContext ); bool DoPrepareResources( ImageIO::PixelFormat targetFormat, Tr2PrimaryRenderContext & renderContext ); void DestroyRenderTargets(); @@ -72,8 +66,6 @@ BLUE_CLASS( Tr2ReflectionProbe ) : Vector3 m_position; int m_intermediateSize; - std::array m_renderTargets; - std::array m_stencilMaps; Tr2RenderTargetPtr m_renderTargetCube; Tr2EffectPtr m_preFilterEffect; @@ -84,7 +76,6 @@ BLUE_CLASS( Tr2ReflectionProbe ) : ITriTextureResPtr m_customSourceTexture; - bool m_prevCullInversion; bool m_hdrOutput; ReflectionProbeRenderFrequency m_renderFrequency; uint8_t m_currentFrame; diff --git a/trinityal/metal/MetalWorkQueue.mm b/trinityal/metal/MetalWorkQueue.mm index 8b6706bbe..ab5100cc9 100644 --- a/trinityal/metal/MetalWorkQueue.mm +++ b/trinityal/metal/MetalWorkQueue.mm @@ -1440,7 +1440,7 @@ for( uint32_t i = 0; i < numOldColorAttachments; ++i ) { oldColorAttachments[i] = m_currentRenderPassDescriptor.colorAttachments[i].texture; - oldAttachmentSlice[i] = uint32_t( m_currentRenderPassDescriptor.colorAttachments[i].depthPlane ); + oldAttachmentSlice[i] = uint32_t( m_currentRenderPassDescriptor.colorAttachments[i].slice ); SetRenderAttachments( nil, i ); } @@ -2003,7 +2003,8 @@ CCP_ASSERT( index < METAL_MAX_RENDER_TARGETS ); // Check if there's soemthing to actually do. - if( texture == m_currentRenderPassDescriptor.colorAttachments[index].texture ) + if( texture == m_currentRenderPassDescriptor.colorAttachments[index].texture && + slice == m_currentRenderPassDescriptor.colorAttachments[index].slice ) { return; }