mirror of
				https://github.com/TES3MP/openmw-tes3mp.git
				synced 2025-10-30 02:56:50 +00:00 
			
		
		
		
	shadows
This commit is contained in:
		
							parent
							
								
									07fd9986ef
								
							
						
					
					
						commit
						1a9f59d5d4
					
				
					 7 changed files with 169 additions and 11 deletions
				
			
		|  | @ -54,6 +54,8 @@ RenderingManager::RenderingManager (OEngine::Render::OgreRenderer& _rend, const | ||||||
|     sh::OgrePlatform* platform = new sh::OgrePlatform("General", (resDir / "materials").string()); |     sh::OgrePlatform* platform = new sh::OgrePlatform("General", (resDir / "materials").string()); | ||||||
|     platform->setCacheFolder ("./"); |     platform->setCacheFolder ("./"); | ||||||
|     mFactory = new sh::Factory(platform); |     mFactory = new sh::Factory(platform); | ||||||
|  |     mFactory->setSharedParameter ("pssmSplitPoints", sh::makeProperty<sh::Vector4>(new sh::Vector4(0,0,0,0))); | ||||||
|  |     mFactory->setSharedParameter ("shadowFar_fadeStart", sh::makeProperty<sh::Vector4>(new sh::Vector4(0,0,0,0))); | ||||||
| 
 | 
 | ||||||
|     //The fog type must be set before any terrain objects are created as if the
 |     //The fog type must be set before any terrain objects are created as if the
 | ||||||
|     //fog type is set to FOG_NONE then the initially created terrain won't have any fog
 |     //fog type is set to FOG_NONE then the initially created terrain won't have any fog
 | ||||||
|  |  | ||||||
|  | @ -12,6 +12,8 @@ | ||||||
| #include <OgreOverlayContainer.h> | #include <OgreOverlayContainer.h> | ||||||
| #include <OgreOverlayManager.h> | #include <OgreOverlayManager.h> | ||||||
| 
 | 
 | ||||||
|  | #include <extern/shiny/Main/Factory.hpp> | ||||||
|  | 
 | ||||||
| #include "renderconst.hpp" | #include "renderconst.hpp" | ||||||
| 
 | 
 | ||||||
| using namespace Ogre; | using namespace Ogre; | ||||||
|  | @ -84,6 +86,12 @@ void Shadows::recreate() | ||||||
|                 mSceneMgr->setShadowTextureConfig(i, texsize/4, texsize/4, Ogre::PF_FLOAT32_R);*/ |                 mSceneMgr->setShadowTextureConfig(i, texsize/4, texsize/4, Ogre::PF_FLOAT32_R);*/ | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  |         // Populate from split point 1, not 0, since split 0 isn't useful (usually 0)
 | ||||||
|  |         const PSSMShadowCameraSetup::SplitPointList& splitPointList = getPSSMSetup()->getSplitPoints(); | ||||||
|  |         sh::Vector4* splitPoints = new sh::Vector4(splitPointList[1], splitPointList[2], splitPointList[3], 1.0); | ||||||
|  | 
 | ||||||
|  |         sh::Factory::getInstance ().setSharedParameter ("pssmSplitPoints", sh::makeProperty<sh::Vector4>(splitPoints)); | ||||||
|  | 
 | ||||||
|         shadowCameraSetup = ShadowCameraSetupPtr(mPSSMSetup); |         shadowCameraSetup = ShadowCameraSetupPtr(mPSSMSetup); | ||||||
|     } |     } | ||||||
|     else |     else | ||||||
|  | @ -96,6 +104,9 @@ void Shadows::recreate() | ||||||
|     } |     } | ||||||
|     mSceneMgr->setShadowCameraSetup(shadowCameraSetup); |     mSceneMgr->setShadowCameraSetup(shadowCameraSetup); | ||||||
| 
 | 
 | ||||||
|  |     sh::Vector4* shadowFar_fadeStart = new sh::Vector4(mShadowFar, mFadeStart * mShadowFar, 0, 0); | ||||||
|  |     sh::Factory::getInstance ().setSharedParameter ("shadowFar_fadeStart", sh::makeProperty<sh::Vector4>(shadowFar_fadeStart)); | ||||||
|  | 
 | ||||||
|     // Set visibility mask for the shadow render textures
 |     // Set visibility mask for the shadow render textures
 | ||||||
|     int visibilityMask = RV_Actors * Settings::Manager::getBool("actor shadows", "Shadows") |     int visibilityMask = RV_Actors * Settings::Manager::getBool("actor shadows", "Shadows") | ||||||
|                             + (RV_Statics + RV_StaticsSmall) * Settings::Manager::getBool("statics shadows", "Shadows") |                             + (RV_Statics + RV_StaticsSmall) * Settings::Manager::getBool("statics shadows", "Shadows") | ||||||
|  | @ -111,7 +122,7 @@ void Shadows::recreate() | ||||||
|     // --------------------------------------------------------------------------------------------------------------------
 |     // --------------------------------------------------------------------------------------------------------------------
 | ||||||
|     // --------------------------- Debug overlays to display the content of shadow maps -----------------------------------
 |     // --------------------------- Debug overlays to display the content of shadow maps -----------------------------------
 | ||||||
|     // --------------------------------------------------------------------------------------------------------------------
 |     // --------------------------------------------------------------------------------------------------------------------
 | ||||||
| /*
 | 
 | ||||||
| 	OverlayManager& mgr = OverlayManager::getSingleton(); | 	OverlayManager& mgr = OverlayManager::getSingleton(); | ||||||
| 	Overlay* overlay; | 	Overlay* overlay; | ||||||
| 	 | 	 | ||||||
|  | @ -157,7 +168,7 @@ void Shadows::recreate() | ||||||
| 		overlay->add2D(debugPanel); | 		overlay->add2D(debugPanel); | ||||||
| 		overlay->show(); | 		overlay->show(); | ||||||
| 	} | 	} | ||||||
| */ | 
 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| PSSMShadowCameraSetup* Shadows::getPSSMSetup() | PSSMShadowCameraSetup* Shadows::getPSSMSetup() | ||||||
|  |  | ||||||
							
								
								
									
										2
									
								
								extern/shiny
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								extern/shiny
									
									
									
									
										vendored
									
									
								
							|  | @ -1 +1 @@ | ||||||
| Subproject commit 3e7e02a846ce6c3de7e2344a82d346293115eb7d | Subproject commit b3cfd41dff2758e268ce16f366b7e7857eee80ea | ||||||
|  | @ -42,10 +42,30 @@ material openmw_objects_base | ||||||
| 
 | 
 | ||||||
|         ffp_vertex_colour_ambient $has_vertex_colour |         ffp_vertex_colour_ambient $has_vertex_colour | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|         texture_unit diffuseMap |         texture_unit diffuseMap | ||||||
|         { |         { | ||||||
|             direct_texture $diffuseMap |             direct_texture $diffuseMap | ||||||
|             create_in_ffp true |             create_in_ffp true | ||||||
|         } |         } | ||||||
|  |        | ||||||
|  |         texture_unit shadowMap0 | ||||||
|  |         { | ||||||
|  |             content_type shadow | ||||||
|  |             tex_address_mode clamp | ||||||
|  |             filtering none | ||||||
|  |         } | ||||||
|  |         texture_unit shadowMap1 | ||||||
|  |         { | ||||||
|  |             content_type shadow | ||||||
|  |             tex_address_mode clamp | ||||||
|  |             filtering none | ||||||
|  |         } | ||||||
|  |         texture_unit shadowMap2 | ||||||
|  |         { | ||||||
|  |             content_type shadow | ||||||
|  |             tex_address_mode clamp | ||||||
|  |             filtering none | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,20 +1,28 @@ | ||||||
| #include "core.h" | #include "core.h" | ||||||
| 
 | 
 | ||||||
|  | #include "shadows.h" | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| #define FOG @shPropertyBool(fog) | #define FOG @shPropertyBool(fog) | ||||||
| #define MRT @shPropertyNotBool(is_transparent) && @shPropertyBool(mrt_output) | #define MRT @shPropertyNotBool(is_transparent) && @shPropertyBool(mrt_output) | ||||||
| #define LIGHTING @shPropertyBool(lighting) | #define LIGHTING @shPropertyBool(lighting) | ||||||
| 
 | 
 | ||||||
| #if FOG || MRT | #define SHADOWS LIGHTING && 0 | ||||||
|  | #define SHADOWS_PSSM LIGHTING | ||||||
|  | 
 | ||||||
|  | #define SHADOWS 1 && LIGHTING | ||||||
|  | #define SHADOWS_PSSM 0 && LIGHTING | ||||||
|  | 
 | ||||||
|  | #if FOG || MRT || SHADOWS_PSSM | ||||||
| #define NEED_DEPTH | #define NEED_DEPTH | ||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
| #define NUM_LIGHTS 8 |  | ||||||
| 
 |  | ||||||
| #define HAS_VERTEXCOLOR @shPropertyBool(has_vertex_colour) | #define HAS_VERTEXCOLOR @shPropertyBool(has_vertex_colour) | ||||||
| 
 | 
 | ||||||
| #ifdef SH_VERTEX_SHADER | #ifdef SH_VERTEX_SHADER | ||||||
| 
 | 
 | ||||||
|  |     // ------------------------------------- VERTEX --------------------------------------- | ||||||
|  | 
 | ||||||
|     SH_BEGIN_PROGRAM |     SH_BEGIN_PROGRAM | ||||||
|         shUniform(float4x4 wvp) @shAutoConstant(wvp, worldviewproj_matrix) |         shUniform(float4x4 wvp) @shAutoConstant(wvp, worldviewproj_matrix) | ||||||
|         shInput(float2, uv0) |         shInput(float2, uv0) | ||||||
|  | @ -33,11 +41,28 @@ | ||||||
|         shColourInput(float4) |         shColourInput(float4) | ||||||
|         shOutput(float4, colorPassthrough) |         shOutput(float4, colorPassthrough) | ||||||
| #endif | #endif | ||||||
|  | 
 | ||||||
|  | #if SHADOWS | ||||||
|  |         shOutput(float4, lightSpacePos0) | ||||||
|  |         shUniform(float4x4 texViewProjMatrix0) @shAutoConstant(texViewProjMatrix0, texture_viewproj_matrix) | ||||||
|  |         shUniform(float4x4 worldMatrix) @shAutoConstant(worldMatrix, world_matrix) | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
|  | #if SHADOWS_PSSM | ||||||
|  |     @shForeach(3) | ||||||
|  |         shOutput(float4, lightSpacePos@shIterator) | ||||||
|  |         shUniform(float4x4 texViewProjMatrix@shIterator) @shAutoConstant(texViewProjMatrix@shIterator, texture_viewproj_matrix, @shIterator) | ||||||
|  |     @shEndForeach | ||||||
|  |         shUniform(float4x4 worldMatrix) @shAutoConstant(worldMatrix, world_matrix) | ||||||
|  | #endif | ||||||
|     SH_START_PROGRAM |     SH_START_PROGRAM | ||||||
|     { |     { | ||||||
| 	    shOutputPosition = shMatrixMult(wvp, shInputPosition); | 	    shOutputPosition = shMatrixMult(wvp, shInputPosition); | ||||||
| 	    UV = uv0; | 	    UV = uv0; | ||||||
|  | #if LIGHTING | ||||||
|         normalPassthrough = normal.xyz; |         normalPassthrough = normal.xyz; | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
| #ifdef NEED_DEPTH | #ifdef NEED_DEPTH | ||||||
|         depthPassthrough = shOutputPosition.z; |         depthPassthrough = shOutputPosition.z; | ||||||
| #endif | #endif | ||||||
|  | @ -49,10 +74,22 @@ | ||||||
| #if HAS_VERTEXCOLOR | #if HAS_VERTEXCOLOR | ||||||
|         colorPassthrough = colour; |         colorPassthrough = colour; | ||||||
| #endif | #endif | ||||||
|  | 
 | ||||||
|  | #if SHADOWS | ||||||
|  |         lightSpacePos0 = shMatrixMult(texViewProjMatrix0, shMatrixMult(worldMatrix, shInputPosition)); | ||||||
|  | #endif | ||||||
|  | #if SHADOWS_PSSM | ||||||
|  |         float4 wPos = shMatrixMult(worldMatrix, shInputPosition); | ||||||
|  |     @shForeach(3) | ||||||
|  |         lightSpacePos@shIterator = shMatrixMult(texViewProjMatrix@shIterator, wPos); | ||||||
|  |     @shEndForeach | ||||||
|  | #endif | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| #else | #else | ||||||
| 
 | 
 | ||||||
|  |     // ----------------------------------- FRAGMENT ------------------------------------------ | ||||||
|  | 
 | ||||||
|     SH_BEGIN_PROGRAM |     SH_BEGIN_PROGRAM | ||||||
| 		shSampler2D(diffuseMap) | 		shSampler2D(diffuseMap) | ||||||
| 		shInput(float2, UV) | 		shInput(float2, UV) | ||||||
|  | @ -76,7 +113,7 @@ | ||||||
|         shUniform(float4 materialAmbient)                    @shAutoConstant(materialAmbient, surface_ambient_colour) |         shUniform(float4 materialAmbient)                    @shAutoConstant(materialAmbient, surface_ambient_colour) | ||||||
|         shUniform(float4 materialDiffuse)                    @shAutoConstant(materialDiffuse, surface_diffuse_colour) |         shUniform(float4 materialDiffuse)                    @shAutoConstant(materialDiffuse, surface_diffuse_colour) | ||||||
|         shUniform(float4 materialEmissive)                   @shAutoConstant(materialEmissive, surface_emissive_colour) |         shUniform(float4 materialEmissive)                   @shAutoConstant(materialEmissive, surface_emissive_colour) | ||||||
|     @shForeach(NUM_LIGHTS) |     @shForeach(8) | ||||||
|         shUniform(float4 lightPosObjSpace@shIterator)        @shAutoConstant(lightPosObjSpace@shIterator, light_position_object_space, @shIterator) |         shUniform(float4 lightPosObjSpace@shIterator)        @shAutoConstant(lightPosObjSpace@shIterator, light_position_object_space, @shIterator) | ||||||
|         shUniform(float4 lightAttenuation@shIterator)        @shAutoConstant(lightAttenuation@shIterator, light_attenuation, @shIterator) |         shUniform(float4 lightAttenuation@shIterator)        @shAutoConstant(lightAttenuation@shIterator, light_attenuation, @shIterator) | ||||||
|         shUniform(float4 lightDiffuse@shIterator)            @shAutoConstant(lightDiffuse@shIterator, light_diffuse_colour, @shIterator) |         shUniform(float4 lightDiffuse@shIterator)            @shAutoConstant(lightDiffuse@shIterator, light_diffuse_colour, @shIterator) | ||||||
|  | @ -91,6 +128,24 @@ | ||||||
| #ifdef HAS_VERTEXCOLOR | #ifdef HAS_VERTEXCOLOR | ||||||
|         shInput(float4, colorPassthrough) |         shInput(float4, colorPassthrough) | ||||||
| #endif | #endif | ||||||
|  | 
 | ||||||
|  | #if SHADOWS | ||||||
|  |         shInput(float4, lightSpacePos0) | ||||||
|  |         shSampler2D(shadowMap0) | ||||||
|  |         shUniform(float2 invShadowmapSize0)   @shAutoConstant(invShadowmapSize0, inverse_texture_size, 0) | ||||||
|  | #endif | ||||||
|  | #if SHADOWS_PSSM | ||||||
|  |     @shForeach(3) | ||||||
|  |         shInput(float4, lightSpacePos@shIterator) | ||||||
|  |         shSampler2D(shadowMap@shIterator) | ||||||
|  |         shUniform(float2 invShadowmapSize@shIterator)  @shAutoConstant(invShadowmapSize@shIterator, inverse_texture_size, @shIterator) | ||||||
|  |     @shEndForeach | ||||||
|  |     shUniform(float4 pssmSplitPoints)  @shSharedParameter(pssmSplitPoints) | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
|  | #if SHADOWS || SHADOWS_PSSM | ||||||
|  |         shUniform(float4 shadowFar_fadeStart) @shSharedParameter(shadowFar_fadeStart) | ||||||
|  | #endif | ||||||
|     SH_START_PROGRAM |     SH_START_PROGRAM | ||||||
|     { |     { | ||||||
|         shOutputColor(0) = shSample(diffuseMap, UV); |         shOutputColor(0) = shSample(diffuseMap, UV); | ||||||
|  | @ -101,15 +156,38 @@ | ||||||
|         float d; |         float d; | ||||||
|         float3 ambient = materialAmbient.xyz * lightAmbient.xyz; |         float3 ambient = materialAmbient.xyz * lightAmbient.xyz; | ||||||
|      |      | ||||||
|     @shForeach(NUM_LIGHTS) |     @shForeach(8) | ||||||
|  |      | ||||||
|  |         // shadows only for the first (directional) light | ||||||
|  | #if @shIterator == 0 | ||||||
|  |     #if SHADOWS | ||||||
|  |             float shadow = depthShadowPCF (shadowMap0, lightSpacePos0, invShadowmapSize0); | ||||||
|  |     #endif | ||||||
|  |     #if SHADOWS_PSSM | ||||||
|  |             float shadow = pssmDepthShadow (lightSpacePos0, invShadowmapSize0, shadowMap0, lightSpacePos1, invShadowmapSize1, shadowMap1, lightSpacePos2, invShadowmapSize2, shadowMap2, depthPassthrough, pssmSplitPoints); | ||||||
|  |     #endif | ||||||
|  | 
 | ||||||
|  |     #if SHADOWS || SHADOWS_PSSM | ||||||
|  |             float fadeRange = shadowFar_fadeStart.x - shadowFar_fadeStart.y; | ||||||
|  |             float fade = 1-((depthPassthrough - shadowFar_fadeStart.y) / fadeRange); | ||||||
|  |             shadow = (depthPassthrough > shadowFar_fadeStart.x) ? 1 : ((depthPassthrough > shadowFar_fadeStart.y) ? 1-((1-shadow)*fade) : shadow); | ||||||
|  |     #endif | ||||||
|  | 
 | ||||||
|  |     #if !SHADOWS && !SHADOWS_PSSM | ||||||
|  |             float shadow = 1.0; | ||||||
|  |     #endif | ||||||
|  | #endif | ||||||
|      |      | ||||||
|         lightDir = lightPosObjSpace@shIterator.xyz - (objSpacePositionPassthrough.xyz * lightPosObjSpace@shIterator.w); |         lightDir = lightPosObjSpace@shIterator.xyz - (objSpacePositionPassthrough.xyz * lightPosObjSpace@shIterator.w); | ||||||
|         d = length(lightDir); |         d = length(lightDir); | ||||||
|          |          | ||||||
|         lightDir = normalize(lightDir); |         lightDir = normalize(lightDir); | ||||||
| 
 | 
 | ||||||
|  | #if @shIterator == 0 | ||||||
|  |         diffuse += materialDiffuse.xyz * lightDiffuse@shIterator.xyz * (1.0 / ((lightAttenuation@shIterator.y) + (lightAttenuation@shIterator.z * d) + (lightAttenuation@shIterator.w * d * d))) * max(dot(normal, lightDir), 0) * shadow; | ||||||
|  | #else | ||||||
|         diffuse += materialDiffuse.xyz * lightDiffuse@shIterator.xyz * (1.0 / ((lightAttenuation@shIterator.y) + (lightAttenuation@shIterator.z * d) + (lightAttenuation@shIterator.w * d * d))) * max(dot(normal, lightDir), 0); |         diffuse += materialDiffuse.xyz * lightDiffuse@shIterator.xyz * (1.0 / ((lightAttenuation@shIterator.y) + (lightAttenuation@shIterator.z * d) + (lightAttenuation@shIterator.w * d * d))) * max(dot(normal, lightDir), 0); | ||||||
|      | #endif | ||||||
|     @shEndForeach |     @shEndForeach | ||||||
|      |      | ||||||
| #if HAS_VERTEXCOLOR | #if HAS_VERTEXCOLOR | ||||||
|  |  | ||||||
|  | @ -2,7 +2,7 @@ shader_set openmw_objects_vertex | ||||||
| { | { | ||||||
|     source objects.shader |     source objects.shader | ||||||
|     type vertex |     type vertex | ||||||
|     profiles_cg vs_2_0 arbvp1 |     profiles_cg vs_2_0 vp40 arbvp1 | ||||||
|     profiles_hlsl vs_2_0 |     profiles_hlsl vs_2_0 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -10,6 +10,6 @@ shader_set openmw_objects_fragment | ||||||
| { | { | ||||||
|     source objects.shader |     source objects.shader | ||||||
|     type fragment |     type fragment | ||||||
|     profiles_cg ps_2_x ps_2_0 ps arbfp1 |     profiles_cg ps_2_x ps_2_0 ps fp40 arbfp1 | ||||||
|     profiles_hlsl ps_2_0 |     profiles_hlsl ps_2_0 | ||||||
| } | } | ||||||
|  |  | ||||||
							
								
								
									
										47
									
								
								files/materials/shadows.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								files/materials/shadows.h
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,47 @@ | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | float depthShadowPCF (shTexture2D shadowMap, float4 shadowMapPos, float2 offset) | ||||||
|  | { | ||||||
|  |     shadowMapPos /= shadowMapPos.w; | ||||||
|  |     float3 o = float3(offset.xy, -offset.x) * 0.3; | ||||||
|  |     //float3 o = float3(0,0,0);
 | ||||||
|  |     float c =   (shadowMapPos.z <= shSample(shadowMap, shadowMapPos.xy - o.xy).r) ? 1 : 0; // top left
 | ||||||
|  |     c +=        (shadowMapPos.z <= shSample(shadowMap, shadowMapPos.xy + o.xy).r) ? 1 : 0; // bottom right
 | ||||||
|  |     c +=        (shadowMapPos.z <= shSample(shadowMap, shadowMapPos.xy + o.zy).r) ? 1 : 0; // bottom left
 | ||||||
|  |     c +=        (shadowMapPos.z <= shSample(shadowMap, shadowMapPos.xy - o.zy).r) ? 1 : 0; // top right
 | ||||||
|  |     return c / 4; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | float pssmDepthShadow ( | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     float4 lightSpacePos0, | ||||||
|  |     float2 invShadowmapSize0, | ||||||
|  |     shTexture2D shadowMap0, | ||||||
|  |      | ||||||
|  |     float4 lightSpacePos1, | ||||||
|  |     float2 invShadowmapSize1, | ||||||
|  |     shTexture2D shadowMap1, | ||||||
|  |      | ||||||
|  |     float4 lightSpacePos2, | ||||||
|  |     float2 invShadowmapSize2, | ||||||
|  |     shTexture2D shadowMap2, | ||||||
|  |      | ||||||
|  |     float depth, | ||||||
|  |     float4 pssmSplitPoints) | ||||||
|  | 
 | ||||||
|  | { | ||||||
|  |     float shadow; | ||||||
|  |      | ||||||
|  |     if (depth < pssmSplitPoints.x) | ||||||
|  |         shadow = depthShadowPCF(shadowMap0, lightSpacePos0, invShadowmapSize0); | ||||||
|  |     else if (depth < pssmSplitPoints.y) | ||||||
|  |         shadow = depthShadowPCF(shadowMap1, lightSpacePos1, invShadowmapSize1); | ||||||
|  |     else | ||||||
|  |         shadow = depthShadowPCF(shadowMap2, lightSpacePos2, invShadowmapSize2); | ||||||
|  | 
 | ||||||
|  |     return shadow; | ||||||
|  | } | ||||||
		Loading…
	
		Reference in a new issue