├── .gitattributes ├── .gitignore ├── Config ├── DefaultEditor.ini ├── DefaultEngine.ini ├── DefaultGame.ini ├── SteamVRBindings │ ├── gamepad.json │ ├── holographic_controller.json │ ├── indexhmd.json │ ├── knuckles.json │ ├── oculus_touch.json │ ├── rift.json │ ├── steamvr_manifest.json │ ├── vive.json │ ├── vive_controller.json │ ├── vive_cosmos_controller.json │ ├── vive_pro.json │ └── vive_tracker_camera.json └── steamvr_ue_editor_app.json ├── Content ├── Map_0_5_million.umap ├── Map_1_million.umap ├── Map_iOS.umap └── cone.uasset ├── README.md ├── Shaders ├── BitonicSort_sort.usf ├── BitonicSort_sortInner.usf ├── BitonicSort_sortStep.usf ├── Boid.usf ├── CopyPositions.usf └── HashedGrid.usf ├── Source ├── UnrealGPUSwarm.Target.cs ├── UnrealGPUSwarm │ ├── ComputeShaderTestComponent.cpp │ ├── ComputeShaderTestComponent.h │ ├── DrawPositionsComponent.cpp │ ├── DrawPositionsComponent.h │ ├── GPUBitonicSort.cpp │ ├── GPUBitonicSort.h │ ├── InstanceBufferMeshComponent.h │ ├── Private │ │ ├── InstanceBufferMesh.cpp │ │ ├── InstanceBufferMesh.h │ │ └── InstanceBufferMesh_StaticMeshInstanceData.h │ ├── UnrealGPUSwarm.Build.cs │ ├── UnrealGPUSwarm.cpp │ ├── UnrealGPUSwarm.h │ ├── UnrealGPUSwarmGameModeBase.cpp │ └── UnrealGPUSwarmGameModeBase.h └── UnrealGPUSwarmEditor.Target.cs ├── UnrealGPUSwarm.code-workspace └── UnrealGPUSwarm.uproject /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Visual Studio 2015 user specific files 2 | .vs/ 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | 22 | # Compiled Static libraries 23 | *.lai 24 | *.la 25 | *.a 26 | *.lib 27 | 28 | # Executables 29 | *.exe 30 | *.out 31 | *.app 32 | *.ipa 33 | 34 | # These project files can be generated by the engine 35 | *.xcodeproj 36 | *.xcworkspace 37 | *.sln 38 | *.suo 39 | *.opensdf 40 | *.sdf 41 | *.VC.db 42 | *.VC.opendb 43 | 44 | # Precompiled Assets 45 | SourceArt/**/*.png 46 | SourceArt/**/*.tga 47 | 48 | # Binary Files 49 | Binaries/* 50 | Plugins/*/Binaries/* 51 | 52 | # Builds 53 | Build/* 54 | 55 | # Whitelist PakBlacklist-.txt files 56 | !Build/*/ 57 | Build/*/** 58 | !Build/*/PakBlacklist*.txt 59 | 60 | # Don't ignore icon files in Build 61 | !Build/**/*.ico 62 | 63 | # Built data for maps 64 | *_BuiltData.uasset 65 | 66 | # Configuration files generated by the Editor 67 | Saved/* 68 | 69 | # Compiled source files for the engine to use 70 | Intermediate/* 71 | Plugins/*/Intermediate/* 72 | 73 | # Cache files for the editor to use 74 | DerivedDataCache/* 75 | -------------------------------------------------------------------------------- /Config/DefaultEditor.ini: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /Config/DefaultEngine.ini: -------------------------------------------------------------------------------- 1 | [/Script/Engine.Engine] 2 | +ActiveGameNameRedirects=(OldGameName="TP_Blank",NewGameName="/Script/UnrealGPUSwarm") 3 | +ActiveGameNameRedirects=(OldGameName="/Script/TP_Blank",NewGameName="/Script/UnrealGPUSwarm") 4 | +ActiveClassRedirects=(OldClassName="TP_BlankGameModeBase",NewClassName="UnrealGPUSwarmGameModeBase") 5 | 6 | [/Script/IOSRuntimeSettings.IOSRuntimeSettings] 7 | MinimumiOSVersion=IOS_12 8 | MaxShaderLanguageVersion=4 9 | bSupportsMetalMRT=False 10 | SigningCertificate= 11 | MobileProvision=Unreal_Provisioning_Profile (1).mobileprovision 12 | bAutomaticSigning=True 13 | IOSTeamID=FD68PMPTMT 14 | bGeneratedSYMFile=True 15 | bGenerateXCArchive=True 16 | bBuildAsFramework=False 17 | 18 | [/Script/Engine.RendererSettings] 19 | r.MobileHDR=True 20 | r.Mobile.DisableVertexFog=True 21 | r.Shadow.CSM.MaxMobileCascades=2 22 | r.MobileMSAA=1 23 | r.Mobile.UseLegacyShadingModel=False 24 | r.Mobile.UseHWsRGBEncoding=False 25 | r.Mobile.AllowDitheredLODTransition=False 26 | r.Mobile.AllowSoftwareOcclusion=False 27 | r.DiscardUnusedQuality=False 28 | r.AllowOcclusionQueries=True 29 | r.MinScreenRadiusForLights=0.030000 30 | r.MinScreenRadiusForDepthPrepass=0.030000 31 | r.MinScreenRadiusForCSMDepth=0.010000 32 | r.PrecomputedVisibilityWarning=False 33 | r.TextureStreaming=True 34 | Compat.UseDXT5NormalMaps=False 35 | r.VirtualTextures=False 36 | r.VirtualTexturedLightmaps=False 37 | r.VT.TileSize=128 38 | r.VT.TileBorderSize=4 39 | r.vt.FeedbackFactor=16 40 | r.VT.EnableCompressZlib=True 41 | r.VT.EnableCompressCrunch=False 42 | r.ClearCoatNormal=False 43 | r.ReflectionCaptureResolution=128 44 | r.ReflectionEnvironmentLightmapMixBasedOnRoughness=True 45 | r.ForwardShading=False 46 | r.VertexFoggingForOpaque=True 47 | r.AllowStaticLighting=True 48 | r.NormalMapsForStaticLighting=False 49 | r.GenerateMeshDistanceFields=False 50 | r.DistanceFieldBuild.EightBit=False 51 | r.GenerateLandscapeGIData=False 52 | r.DistanceFieldBuild.Compress=False 53 | r.TessellationAdaptivePixelsPerTriangle=48.000000 54 | r.SeparateTranslucency=False 55 | r.TranslucentSortPolicy=0 56 | TranslucentSortAxis=(X=0.000000,Y=-1.000000,Z=0.000000) 57 | r.CustomDepth=1 58 | r.CustomDepthTemporalAAJitter=True 59 | r.PostProcessing.PropagateAlpha=0 60 | r.DefaultFeature.Bloom=True 61 | r.DefaultFeature.AmbientOcclusion=False 62 | r.DefaultFeature.AmbientOcclusionStaticFraction=False 63 | r.DefaultFeature.AutoExposure=False 64 | r.DefaultFeature.AutoExposure.Method=0 65 | r.DefaultFeature.AutoExposure.ExtendDefaultLuminanceRange=True 66 | r.UsePreExposure=False 67 | r.EyeAdaptation.EditorOnly=False 68 | r.DefaultFeature.MotionBlur=False 69 | r.DefaultFeature.LensFlare=False 70 | r.TemporalAA.Upsampling=False 71 | r.SSGI.Enable=False 72 | r.DefaultFeature.AntiAliasing=0 73 | r.DefaultFeature.LightUnits=1 74 | r.DefaultBackBufferPixelFormat=4 75 | r.Shadow.UnbuiltPreviewInGame=True 76 | r.StencilForLODDither=False 77 | r.EarlyZPass=3 78 | r.EarlyZPassOnlyMaterialMasking=False 79 | r.DBuffer=True 80 | r.ClearSceneMethod=1 81 | r.BasePassOutputsVelocity=False 82 | r.SelectiveBasePassOutputs=False 83 | bDefaultParticleCutouts=False 84 | fx.GPUSimulationTextureSizeX=1024 85 | fx.GPUSimulationTextureSizeY=1024 86 | r.AllowGlobalClipPlane=False 87 | r.GBufferFormat=1 88 | r.MorphTarget.Mode=True 89 | r.GPUCrashDebugging=False 90 | vr.InstancedStereo=False 91 | vr.MultiView=False 92 | vr.MobileMultiView=False 93 | vr.MobileMultiView.Direct=False 94 | vr.RoundRobinOcclusion=False 95 | vr.ODSCapture=False 96 | r.WireframeCullThreshold=5.000000 97 | r.RayTracing=False 98 | r.RayTracing.UseTextureLod=False 99 | r.SupportStationarySkylight=True 100 | r.SupportLowQualityLightmaps=True 101 | r.SupportPointLightWholeSceneShadows=True 102 | r.SupportAtmosphericFog=True 103 | r.SupportSkyAtmosphere=True 104 | r.SupportSkyAtmosphereAffectsHeightFog=False 105 | r.SkinCache.CompileShaders=False 106 | r.Mobile.EnableStaticAndCSMShadowReceivers=True 107 | r.Mobile.EnableMovableLightCSMShaderCulling=True 108 | r.Mobile.AllowDistanceFieldShadows=True 109 | r.Mobile.AllowMovableDirectionalLights=True 110 | r.MobileNumDynamicPointLights=4 111 | r.MobileDynamicPointLightsUseStaticBranch=True 112 | r.Mobile.EnableMovableSpotlights=False 113 | r.SkinCache.SceneMemoryLimitInMB=128.000000 114 | r.GPUSkin.Limit2BoneInfluences=False 115 | r.SupportDepthOnlyIndexBuffers=True 116 | r.SupportReversedIndexBuffers=True 117 | r.SupportMaterialLayers=False 118 | r.LightPropagationVolume=False 119 | 120 | [/Script/EngineSettings.GameMapsSettings] 121 | EditorStartupMap=/Game/Map_iOS.Map_iOS 122 | LocalMapOptions= 123 | TransitionMap=None 124 | bUseSplitscreen=False 125 | TwoPlayerSplitscreenLayout=Horizontal 126 | ThreePlayerSplitscreenLayout=FavorTop 127 | FourPlayerSplitscreenLayout=Grid 128 | bOffsetPlayerGamepadIds=False 129 | GameInstanceClass=/Script/Engine.GameInstance 130 | GameDefaultMap=/Game/Map_iOS.Map_iOS 131 | ServerDefaultMap=/Engine/Maps/Entry.Entry 132 | GlobalDefaultGameMode=/Script/Engine.GameModeBase 133 | GlobalDefaultServerGameMode=None 134 | 135 | [/Script/Slate.SlateSettings] 136 | bExplicitCanvasChildZOrder=True 137 | 138 | [/Script/HardwareTargeting.HardwareTargetingSettings] 139 | TargetedHardwareClass=Mobile 140 | AppliedTargetedHardwareClass=Mobile 141 | DefaultGraphicsPerformance=Maximum 142 | AppliedDefaultGraphicsPerformance=Maximum 143 | 144 | [/Script/MacTargetPlatform.MacTargetSettings] 145 | -TargetedRHIs=SF_METAL_SM5 146 | +TargetedRHIs=SF_METAL_SM5 147 | MaxShaderLanguageVersion=4 148 | UseFastIntrinsics=False 149 | ForceFloats=False 150 | EnableMathOptimisations=True 151 | IndirectArgumentTier=0 152 | AudioSampleRate=0 153 | AudioCallbackBufferFrameSize=0 154 | AudioNumBuffersToEnqueue=0 155 | AudioMaxChannels=0 156 | AudioNumSourceWorkers=0 157 | SpatializationPlugin= 158 | ReverbPlugin= 159 | OcclusionPlugin= 160 | SoundCueCookQualityIndex=-1 161 | -------------------------------------------------------------------------------- /Config/DefaultGame.ini: -------------------------------------------------------------------------------- 1 | 2 | [/Script/EngineSettings.GeneralProjectSettings] 3 | ProjectID=15D42DAF4AB7DA72FC085FAEA8EAAAEC 4 | 5 | [/Script/UnrealEd.ProjectPackagingSettings] 6 | IncludeDebugFiles=True 7 | 8 | -------------------------------------------------------------------------------- /Config/SteamVRBindings/gamepad.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Default bindings for Gamepads", 3 | "controller_type": "gamepad", 4 | "last_edited_by": "UnrealEngine", 5 | "bindings": 6 | { 7 | "/actions/main": 8 | { 9 | "sources": [] 10 | } 11 | }, 12 | "description": "" 13 | } -------------------------------------------------------------------------------- /Config/SteamVRBindings/holographic_controller.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Default bindings for MixedReality", 3 | "controller_type": "holographic_controller", 4 | "last_edited_by": "UnrealEngine", 5 | "bindings": 6 | { 7 | "/actions/main": 8 | { 9 | "sources": [], 10 | "poses": [ 11 | { 12 | "output": "/actions/main/in/controllerleft", 13 | "path": "/user/hand/left/pose/raw", 14 | "requirement": "optional" 15 | }, 16 | { 17 | "output": "/actions/main/in/controllerright", 18 | "path": "/user/hand/right/pose/raw" 19 | } 20 | ], 21 | "skeleton": [ 22 | { 23 | "output": "/actions/main/in/skeletonleft", 24 | "path": "/user/hand/left/input/skeleton/left" 25 | }, 26 | { 27 | "output": "/actions/main/in/skeletonright", 28 | "path": "/user/hand/right/input/skeleton/right" 29 | } 30 | ], 31 | "haptics": [ 32 | { 33 | "output": "/actions/main/out/vibrateleft", 34 | "path": "/user/hand/left/output/haptic" 35 | }, 36 | { 37 | "output": "/actions/main/out/vibrateright", 38 | "path": "/user/hand/right/output/haptic" 39 | } 40 | ] 41 | } 42 | }, 43 | "description": "" 44 | } -------------------------------------------------------------------------------- /Config/SteamVRBindings/indexhmd.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Default bindings for Valve Index Headset", 3 | "controller_type": "indexhmd", 4 | "last_edited_by": "UnrealEngine", 5 | "bindings": 6 | { 7 | "/actions/main": 8 | { 9 | "sources": [] 10 | } 11 | }, 12 | "description": "" 13 | } -------------------------------------------------------------------------------- /Config/SteamVRBindings/knuckles.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Default bindings for ValveIndex", 3 | "controller_type": "knuckles", 4 | "last_edited_by": "UnrealEngine", 5 | "bindings": 6 | { 7 | "/actions/main": 8 | { 9 | "sources": [], 10 | "poses": [ 11 | { 12 | "output": "/actions/main/in/controllerleft", 13 | "path": "/user/hand/left/pose/raw", 14 | "requirement": "optional" 15 | }, 16 | { 17 | "output": "/actions/main/in/controllerright", 18 | "path": "/user/hand/right/pose/raw" 19 | } 20 | ], 21 | "skeleton": [ 22 | { 23 | "output": "/actions/main/in/skeletonleft", 24 | "path": "/user/hand/left/input/skeleton/left" 25 | }, 26 | { 27 | "output": "/actions/main/in/skeletonright", 28 | "path": "/user/hand/right/input/skeleton/right" 29 | } 30 | ], 31 | "haptics": [ 32 | { 33 | "output": "/actions/main/out/vibrateleft", 34 | "path": "/user/hand/left/output/haptic" 35 | }, 36 | { 37 | "output": "/actions/main/out/vibrateright", 38 | "path": "/user/hand/right/output/haptic" 39 | } 40 | ] 41 | } 42 | }, 43 | "description": "" 44 | } -------------------------------------------------------------------------------- /Config/SteamVRBindings/oculus_touch.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Default bindings for OculusTouch", 3 | "controller_type": "oculus_touch", 4 | "last_edited_by": "UnrealEngine", 5 | "bindings": 6 | { 7 | "/actions/main": 8 | { 9 | "sources": [], 10 | "poses": [ 11 | { 12 | "output": "/actions/main/in/controllerleft", 13 | "path": "/user/hand/left/pose/raw", 14 | "requirement": "optional" 15 | }, 16 | { 17 | "output": "/actions/main/in/controllerright", 18 | "path": "/user/hand/right/pose/raw" 19 | } 20 | ], 21 | "skeleton": [ 22 | { 23 | "output": "/actions/main/in/skeletonleft", 24 | "path": "/user/hand/left/input/skeleton/left" 25 | }, 26 | { 27 | "output": "/actions/main/in/skeletonright", 28 | "path": "/user/hand/right/input/skeleton/right" 29 | } 30 | ], 31 | "haptics": [ 32 | { 33 | "output": "/actions/main/out/vibrateleft", 34 | "path": "/user/hand/left/output/haptic" 35 | }, 36 | { 37 | "output": "/actions/main/out/vibrateright", 38 | "path": "/user/hand/right/output/haptic" 39 | } 40 | ] 41 | } 42 | }, 43 | "description": "" 44 | } -------------------------------------------------------------------------------- /Config/SteamVRBindings/rift.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Default bindings for Rift Headset", 3 | "controller_type": "rift", 4 | "last_edited_by": "UnrealEngine", 5 | "bindings": 6 | { 7 | "/actions/main": 8 | { 9 | "sources": [] 10 | } 11 | }, 12 | "description": "" 13 | } -------------------------------------------------------------------------------- /Config/SteamVRBindings/steamvr_manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "actions": [ 3 | { 4 | "name": "/actions/main/in/controllerleft", 5 | "type": "pose", 6 | "requirement": "optional" 7 | }, 8 | { 9 | "name": "/actions/main/in/controllerright", 10 | "type": "pose", 11 | "requirement": "optional" 12 | }, 13 | { 14 | "name": "/actions/main/in/special1", 15 | "type": "pose", 16 | "requirement": "optional" 17 | }, 18 | { 19 | "name": "/actions/main/in/special2", 20 | "type": "pose", 21 | "requirement": "optional" 22 | }, 23 | { 24 | "name": "/actions/main/in/special3", 25 | "type": "pose", 26 | "requirement": "optional" 27 | }, 28 | { 29 | "name": "/actions/main/in/special4", 30 | "type": "pose", 31 | "requirement": "optional" 32 | }, 33 | { 34 | "name": "/actions/main/in/special5", 35 | "type": "pose", 36 | "requirement": "optional" 37 | }, 38 | { 39 | "name": "/actions/main/in/special6", 40 | "type": "pose", 41 | "requirement": "optional" 42 | }, 43 | { 44 | "name": "/actions/main/in/special7", 45 | "type": "pose", 46 | "requirement": "optional" 47 | }, 48 | { 49 | "name": "/actions/main/in/special8", 50 | "type": "pose", 51 | "requirement": "optional" 52 | }, 53 | { 54 | "name": "/actions/main/in/skeletonleft", 55 | "type": "skeleton", 56 | "skeleton": "/skeleton/hand/left", 57 | "requirement": "optional" 58 | }, 59 | { 60 | "name": "/actions/main/in/skeletonright", 61 | "type": "skeleton", 62 | "skeleton": "/skeleton/hand/right", 63 | "requirement": "optional" 64 | }, 65 | { 66 | "name": "/actions/main/out/vibrateleft", 67 | "type": "vibration", 68 | "requirement": "optional" 69 | }, 70 | { 71 | "name": "/actions/main/out/vibrateright", 72 | "type": "vibration", 73 | "requirement": "optional" 74 | }, 75 | { 76 | "name": "/actions/main/in/open_console", 77 | "type": "boolean", 78 | "requirement": "optional" 79 | } 80 | ], 81 | "action_sets": [ 82 | { 83 | "name": "/actions/main", 84 | "usage": "leftright" 85 | } 86 | ], 87 | "default_bindings": [ 88 | { 89 | "controller_type": "knuckles", 90 | "binding_url": "knuckles.json" 91 | }, 92 | { 93 | "controller_type": "vive_controller", 94 | "binding_url": "vive_controller.json" 95 | }, 96 | { 97 | "controller_type": "vive_cosmos_controller", 98 | "binding_url": "vive_cosmos_controller.json" 99 | }, 100 | { 101 | "controller_type": "oculus_touch", 102 | "binding_url": "oculus_touch.json" 103 | }, 104 | { 105 | "controller_type": "holographic_controller", 106 | "binding_url": "holographic_controller.json" 107 | }, 108 | { 109 | "controller_type": "indexhmd", 110 | "binding_url": "indexhmd.json" 111 | }, 112 | { 113 | "controller_type": "vive", 114 | "binding_url": "vive.json" 115 | }, 116 | { 117 | "controller_type": "vive_pro", 118 | "binding_url": "vive_pro.json" 119 | }, 120 | { 121 | "controller_type": "rift", 122 | "binding_url": "rift.json" 123 | }, 124 | { 125 | "controller_type": "vive_tracker_camera", 126 | "binding_url": "vive_tracker_camera.json" 127 | }, 128 | { 129 | "controller_type": "gamepad", 130 | "binding_url": "gamepad.json" 131 | } 132 | ], 133 | "localization": [ 134 | { 135 | "language_tag": "en_us", 136 | "/actions/main/in/controllerleft": "Left Controller [Pose]", 137 | "/actions/main/in/controllerright": "Right Controller [Pose]", 138 | "/actions/main/in/special1": "Special 1 [Tracker]", 139 | "/actions/main/in/special2": "Special 2 [Tracker]", 140 | "/actions/main/in/special3": "Special 3 [Tracker]", 141 | "/actions/main/in/special4": "Special 4 [Tracker]", 142 | "/actions/main/in/special5": "Special 5 [Tracker]", 143 | "/actions/main/in/special6": "Special 6 [Tracker]", 144 | "/actions/main/in/special7": "Special 7 [Tracker]", 145 | "/actions/main/in/special8": "Special 8 [Tracker]", 146 | "/actions/main/in/skeletonleft": "Skeleton (Left)", 147 | "/actions/main/in/skeletonright": "Skeleton (Right)", 148 | "/actions/main/out/vibrateleft": "Haptic (Left)", 149 | "/actions/main/out/vibrateright": "Haptic (Right)", 150 | "/actions/main/in/open_console": "Open Console", 151 | "/actions/main": "Main Game Actions" 152 | } 153 | ] 154 | } -------------------------------------------------------------------------------- /Config/SteamVRBindings/vive.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Default bindings for Vive Headset", 3 | "controller_type": "vive", 4 | "last_edited_by": "UnrealEngine", 5 | "bindings": 6 | { 7 | "/actions/main": 8 | { 9 | "sources": [] 10 | } 11 | }, 12 | "description": "" 13 | } -------------------------------------------------------------------------------- /Config/SteamVRBindings/vive_controller.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Default bindings for Vive", 3 | "controller_type": "vive_controller", 4 | "last_edited_by": "UnrealEngine", 5 | "bindings": 6 | { 7 | "/actions/main": 8 | { 9 | "sources": [], 10 | "poses": [ 11 | { 12 | "output": "/actions/main/in/controllerleft", 13 | "path": "/user/hand/left/pose/raw", 14 | "requirement": "optional" 15 | }, 16 | { 17 | "output": "/actions/main/in/controllerright", 18 | "path": "/user/hand/right/pose/raw" 19 | } 20 | ], 21 | "skeleton": [ 22 | { 23 | "output": "/actions/main/in/skeletonleft", 24 | "path": "/user/hand/left/input/skeleton/left" 25 | }, 26 | { 27 | "output": "/actions/main/in/skeletonright", 28 | "path": "/user/hand/right/input/skeleton/right" 29 | } 30 | ], 31 | "haptics": [ 32 | { 33 | "output": "/actions/main/out/vibrateleft", 34 | "path": "/user/hand/left/output/haptic" 35 | }, 36 | { 37 | "output": "/actions/main/out/vibrateright", 38 | "path": "/user/hand/right/output/haptic" 39 | } 40 | ] 41 | } 42 | }, 43 | "description": "" 44 | } -------------------------------------------------------------------------------- /Config/SteamVRBindings/vive_cosmos_controller.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Default bindings for Cosmos", 3 | "controller_type": "vive_cosmos_controller", 4 | "last_edited_by": "UnrealEngine", 5 | "bindings": 6 | { 7 | "/actions/main": 8 | { 9 | "sources": [], 10 | "poses": [ 11 | { 12 | "output": "/actions/main/in/controllerleft", 13 | "path": "/user/hand/left/pose/raw", 14 | "requirement": "optional" 15 | }, 16 | { 17 | "output": "/actions/main/in/controllerright", 18 | "path": "/user/hand/right/pose/raw" 19 | } 20 | ], 21 | "skeleton": [ 22 | { 23 | "output": "/actions/main/in/skeletonleft", 24 | "path": "/user/hand/left/input/skeleton/left" 25 | }, 26 | { 27 | "output": "/actions/main/in/skeletonright", 28 | "path": "/user/hand/right/input/skeleton/right" 29 | } 30 | ], 31 | "haptics": [ 32 | { 33 | "output": "/actions/main/out/vibrateleft", 34 | "path": "/user/hand/left/output/haptic" 35 | }, 36 | { 37 | "output": "/actions/main/out/vibrateright", 38 | "path": "/user/hand/right/output/haptic" 39 | } 40 | ] 41 | } 42 | }, 43 | "description": "" 44 | } -------------------------------------------------------------------------------- /Config/SteamVRBindings/vive_pro.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Default bindings for Vive Pro Headset", 3 | "controller_type": "vive_pro", 4 | "last_edited_by": "UnrealEngine", 5 | "bindings": 6 | { 7 | "/actions/main": 8 | { 9 | "sources": [] 10 | } 11 | }, 12 | "description": "" 13 | } -------------------------------------------------------------------------------- /Config/SteamVRBindings/vive_tracker_camera.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Default bindings for Vive Trackers", 3 | "controller_type": "vive_tracker_camera", 4 | "last_edited_by": "UnrealEngine", 5 | "bindings": 6 | { 7 | "/actions/main": 8 | { 9 | "sources": [], 10 | "poses": [ 11 | { 12 | "output": "/actions/main/in/special1", 13 | "path": "/user/hand/left/pose/back", 14 | "requirement": "optional" 15 | }, 16 | { 17 | "output": "/actions/main/in/special2", 18 | "path": "/user/hand/right/pose/back", 19 | "requirement": "optional" 20 | }, 21 | { 22 | "output": "/actions/main/in/special3", 23 | "path": "/user/hand/left/pose/front", 24 | "requirement": "optional" 25 | }, 26 | { 27 | "output": "/actions/main/in/special4", 28 | "path": "/user/hand/right/pose/front", 29 | "requirement": "optional" 30 | }, 31 | { 32 | "output": "/actions/main/in/special5", 33 | "path": "/user/hand/left/pose/frontandrolled", 34 | "requirement": "optional" 35 | }, 36 | { 37 | "output": "/actions/main/in/special6", 38 | "path": "/user/hand/right/pose/frontandrolled", 39 | "requirement": "optional" 40 | }, 41 | { 42 | "output": "/actions/main/in/special7", 43 | "path": "/user/hand/left/pose/pistolgrip", 44 | "requirement": "optional" 45 | }, 46 | { 47 | "output": "/actions/main/in/special8", 48 | "path": "/user/hand/right/pose/pistolgrip", 49 | "requirement": "optional" 50 | } 51 | ] 52 | } 53 | }, 54 | "description": "" 55 | } -------------------------------------------------------------------------------- /Config/steamvr_ue_editor_app.json: -------------------------------------------------------------------------------- 1 | { 2 | "source": "UE", 3 | "applications": [ 4 | { 5 | "app_key": "application.generated.ue.unrealgpuswarm-11590370.ue4editor.exe", 6 | "launch_type": "url", 7 | "url": "steam://launch/", 8 | "action_manifest_path": "C:/Users/timothyd/Documents/GitHub/UnrealGPUSwarm/Config/SteamVRBindings/steamvr_manifest.json", 9 | "strings": 10 | { 11 | "en_us": 12 | { 13 | "name": "UnrealGPUSwarm-11590370 [UE Editor]" 14 | } 15 | } 16 | } 17 | ] 18 | } -------------------------------------------------------------------------------- /Content/Map_0_5_million.umap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timdecode/UnrealGPUSwarm/8d3a20d2a91a4b6f943c28563b05fc6776435ebc/Content/Map_0_5_million.umap -------------------------------------------------------------------------------- /Content/Map_1_million.umap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timdecode/UnrealGPUSwarm/8d3a20d2a91a4b6f943c28563b05fc6776435ebc/Content/Map_1_million.umap -------------------------------------------------------------------------------- /Content/Map_iOS.umap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timdecode/UnrealGPUSwarm/8d3a20d2a91a4b6f943c28563b05fc6776435ebc/Content/Map_iOS.umap -------------------------------------------------------------------------------- /Content/cone.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timdecode/UnrealGPUSwarm/8d3a20d2a91a4b6f943c28563b05fc6776435ebc/Content/cone.uasset -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This project is a good starting point for learning how to write compute shaders in Unreal. It implements a boid simulation the GPU. It achieves 0.5 million boids at 45 fps on a GTX 1080. 2 | 3 | https://user-images.githubusercontent.com/980432/132757577-500416e4-5f27-4add-9c50-641889336d69.mp4 4 | 5 | [Tweet of the above](https://twitter.com/timd_ca/status/1243941167005192192) 6 | 7 | This project is for Unreal 4.24. It might work in later versions, but you might need to fix a few compiler errors around shader binding. I would be suprised if my ``FIBMInstanceBuffer`` works in 4.26. 8 | 9 | The compute shader magic happens in [ComputeShaderTestComponent.cpp](Source/UnrealGPUSwarm/ComputeShaderTestComponent.cpp). To work with compute shaders in Unreal you need a few things: 10 | - the compute shader/kernel source that runs on the GPU (a .usf file) 11 | - a way to map/bind parameters and buffers to the compute kernel (subclass a `FGlobalShader`) 12 | - something that will dispatch the shader to the GPU (`FComputeShaderUtils::Dispatch`) 13 | 14 | This project was my way of learning to write computer shaders in Unreal, so not everything is perfect, and some things are written for clarity (you should create your ``TShaderMapRef``s once and cache them, I recreate them every frame). 15 | 16 | This project is also quite heavily optimized from the theoretical side of things. Some important bits: 17 | - I'm using a hashed grid (powered by the bitonic GPU sort algorithm) to massively accelerate boid neighbourhood queries on the GPU 18 | - I sort the boids by their grid cell every frame to greatly improve cache coherence (it really makes a big difference, try disabling it) 19 | - I rewrote UInstanceStaticMeshComponent to work directly with GPU side position buffers 20 | 21 | The hashed grid implementation was inspired by the one from [Wicked Engine](https://wickedengine.net/page/2/). 22 | -------------------------------------------------------------------------------- /Shaders/BitonicSort_sort.usf: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 Advanced Micro Devices, Inc. All rights reserved. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | // 21 | 22 | #include "/Engine/Private/Common.ush" 23 | 24 | #define SORT_SIZE 512 25 | 26 | #if( SORT_SIZE>4096 ) 27 | // won't work for arrays>4096 28 | #error due to LDS size SORT_SIZE must be 4096 or smaller 29 | #else 30 | #define ITEMS_PER_GROUP ( SORT_SIZE ) 31 | #endif 32 | 33 | #define HALF_SIZE (SORT_SIZE/2) 34 | #define ITERATIONS (HALF_SIZE > 1024 ? HALF_SIZE/1024 : 1) 35 | #define NUM_THREADS (HALF_SIZE/ITERATIONS) 36 | #define INVERSION (16*2 + 8*3) 37 | 38 | //-------------------------------------------------------------------------------------- 39 | // Structured Buffers 40 | //-------------------------------------------------------------------------------------- 41 | int3 job_params; 42 | uint itemCount; 43 | 44 | RWStructuredBuffer comparisonBuffer; 45 | RWStructuredBuffer indexBuffer; 46 | 47 | //-------------------------------------------------------------------------------------- 48 | // Bitonic Sort Compute Shader 49 | //-------------------------------------------------------------------------------------- 50 | groupshared float2 g_LDS[SORT_SIZE]; 51 | 52 | 53 | [numthreads(NUM_THREADS, 1, 1)] 54 | void BitonicSort_sort(uint3 Gid : SV_GroupID, 55 | uint3 DTid : SV_DispatchThreadID, 56 | uint3 GTid : SV_GroupThreadID, 57 | uint GI : SV_GroupIndex) 58 | { 59 | uint NumElements = itemCount; 60 | 61 | uint GlobalBaseIndex = (Gid.x * SORT_SIZE) + GTid.x; 62 | uint LocalBaseIndex = GI; 63 | 64 | uint numElementsInThreadGroup = min(SORT_SIZE, NumElements - (Gid.x * SORT_SIZE)); 65 | 66 | // Load shared data 67 | uint i; 68 | [unroll]for (i = 0; i < 2 * ITERATIONS; ++i) 69 | { 70 | if (GI + i * NUM_THREADS < numElementsInThreadGroup) 71 | { 72 | uint particleIndex = indexBuffer[GlobalBaseIndex + i * NUM_THREADS]; 73 | float dist = comparisonBuffer[particleIndex]; 74 | g_LDS[LocalBaseIndex + i * NUM_THREADS] = float2(dist, (float)particleIndex); 75 | } 76 | } 77 | GroupMemoryBarrierWithGroupSync(); 78 | 79 | // Bitonic sort 80 | for (uint nMergeSize = 2; nMergeSize <= SORT_SIZE; nMergeSize = nMergeSize * 2) 81 | { 82 | for (uint nMergeSubSize = nMergeSize >> 1; nMergeSubSize > 0; nMergeSubSize = nMergeSubSize >> 1) 83 | { 84 | [unroll]for (i = 0; i < ITERATIONS; ++i) 85 | { 86 | int tmp_index = GI + NUM_THREADS * i; 87 | int index_low = tmp_index & (nMergeSubSize - 1); 88 | int index_high = 2 * (tmp_index - index_low); 89 | int index = index_high + index_low; 90 | 91 | uint nSwapElem = nMergeSubSize == nMergeSize >> 1 ? index_high + (2 * nMergeSubSize - 1) - index_low : index_high + nMergeSubSize + index_low; 92 | if (nSwapElem < numElementsInThreadGroup) 93 | { 94 | float2 a = g_LDS[index]; 95 | float2 b = g_LDS[nSwapElem]; 96 | 97 | if (a.x > b.x) 98 | { 99 | g_LDS[index] = b; 100 | g_LDS[nSwapElem] = a; 101 | } 102 | } 103 | GroupMemoryBarrierWithGroupSync(); 104 | } 105 | } 106 | } 107 | 108 | // Store shared data 109 | [unroll]for (i = 0; i < 2 * ITERATIONS; ++i) 110 | { 111 | if (GI + i * NUM_THREADS < numElementsInThreadGroup) 112 | { 113 | indexBuffer[GlobalBaseIndex + i * NUM_THREADS] = (uint)g_LDS[LocalBaseIndex + i * NUM_THREADS].y; 114 | } 115 | } 116 | } -------------------------------------------------------------------------------- /Shaders/BitonicSort_sortInner.usf: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 Advanced Micro Devices, Inc. All rights reserved. 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in 12 | // all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | // THE SOFTWARE. 21 | // 22 | 23 | #include "/Engine/Private/Common.ush" 24 | 25 | #define SORT_SIZE 512 26 | 27 | #if( SORT_SIZE>2048 ) 28 | #error 29 | #endif 30 | 31 | #define NUM_THREADS (SORT_SIZE/2) 32 | #define INVERSION (16*2 + 8*3) 33 | 34 | //-------------------------------------------------------------------------------------- 35 | // Structured Buffers 36 | //-------------------------------------------------------------------------------------- 37 | int3 job_params; 38 | uint itemCount; 39 | 40 | RWStructuredBuffer comparisonBuffer; 41 | RWStructuredBuffer indexBuffer; 42 | 43 | 44 | //-------------------------------------------------------------------------------------- 45 | // Bitonic Sort Compute Shader 46 | //-------------------------------------------------------------------------------------- 47 | groupshared float2 g_LDS[SORT_SIZE]; 48 | 49 | 50 | [numthreads(NUM_THREADS, 1, 1)] 51 | void BitonicSort_sortInner( 52 | uint3 Gid : SV_GroupID, 53 | uint3 DTid : SV_DispatchThreadID, 54 | uint3 GTid : SV_GroupThreadID, 55 | uint GI : SV_GroupIndex) 56 | { 57 | uint NumElements = itemCount; 58 | 59 | uint4 tgp; 60 | 61 | tgp.x = Gid.x * 256; 62 | tgp.y = 0; 63 | tgp.z = NumElements; 64 | tgp.w = min(512, max(0, NumElements - Gid.x * 512)); 65 | 66 | uint GlobalBaseIndex = tgp.y + tgp.x * 2 + GTid.x; 67 | uint LocalBaseIndex = GI; 68 | uint i; 69 | 70 | // Load shared data 71 | [unroll]for (i = 0; i < 2; ++i) 72 | { 73 | if (GI + i * NUM_THREADS < tgp.w) 74 | { 75 | uint particleIndex = indexBuffer[GlobalBaseIndex + i * NUM_THREADS]; 76 | float dist = comparisonBuffer[particleIndex]; 77 | g_LDS[LocalBaseIndex + i * NUM_THREADS] = float2(dist, (float)particleIndex); 78 | } 79 | } 80 | GroupMemoryBarrierWithGroupSync(); 81 | 82 | // sort threadgroup shared memory 83 | for (int nMergeSubSize = SORT_SIZE >> 1; nMergeSubSize > 0; nMergeSubSize = nMergeSubSize >> 1) 84 | { 85 | int tmp_index = GI; 86 | int index_low = tmp_index & (nMergeSubSize - 1); 87 | int index_high = 2 * (tmp_index - index_low); 88 | int index = index_high + index_low; 89 | 90 | uint nSwapElem = index_high + nMergeSubSize + index_low; 91 | 92 | if (nSwapElem < tgp.w) 93 | { 94 | float2 a = g_LDS[index]; 95 | float2 b = g_LDS[nSwapElem]; 96 | 97 | if (a.x > b.x) 98 | { 99 | g_LDS[index] = b; 100 | g_LDS[nSwapElem] = a; 101 | } 102 | } 103 | GroupMemoryBarrierWithGroupSync(); 104 | } 105 | 106 | // Store shared data 107 | [unroll]for (i = 0; i < 2; ++i) 108 | { 109 | if (GI + i * NUM_THREADS < tgp.w) 110 | { 111 | indexBuffer[GlobalBaseIndex + i * NUM_THREADS] = (uint)g_LDS[LocalBaseIndex + i * NUM_THREADS].y; 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /Shaders/BitonicSort_sortStep.usf: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 Advanced Micro Devices, Inc. All rights reserved. 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in 12 | // all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | // THE SOFTWARE. 21 | // 22 | 23 | #include "/Engine/Private/Common.ush" 24 | 25 | //-------------------------------------------------------------------------------------- 26 | // Structured Buffers 27 | //-------------------------------------------------------------------------------------- 28 | int3 job_params; 29 | uint itemCount; 30 | 31 | RWStructuredBuffer comparisonBuffer; 32 | RWStructuredBuffer indexBuffer; 33 | 34 | [numthreads(256, 1, 1)] 35 | void BitonicSort_sortStep(uint3 Gid : SV_GroupID, 36 | uint3 GTid : SV_GroupThreadID) 37 | { 38 | uint NumElements = itemCount; 39 | 40 | uint4 tgp; 41 | 42 | tgp.x = Gid.x * 256; 43 | tgp.y = 0; 44 | tgp.z = NumElements; 45 | tgp.w = min(512, max(0, NumElements - Gid.x * 512)); 46 | 47 | uint localID = tgp.x + GTid.x; // calculate threadID within this sortable-array 48 | 49 | uint index_low = localID & (job_params.x - 1); 50 | uint index_high = 2 * (localID - index_low); 51 | 52 | uint index = tgp.y + index_high + index_low; 53 | uint nSwapElem = tgp.y + index_high + job_params.y + job_params.z*index_low; 54 | 55 | if (nSwapElem < tgp.y + tgp.z) 56 | { 57 | uint index_a = indexBuffer[index]; 58 | uint index_b = indexBuffer[nSwapElem]; 59 | float a = comparisonBuffer[index_a]; 60 | float b = comparisonBuffer[index_b]; 61 | 62 | if (a > b) 63 | { 64 | indexBuffer[index] = index_b; 65 | indexBuffer[nSwapElem] = index_a; 66 | } 67 | } 68 | } -------------------------------------------------------------------------------- /Shaders/Boid.usf: -------------------------------------------------------------------------------- 1 | //Since we can't #include private Engine shaders such as Common.ush we have to copy the needed Shaders from the Engine' Shader directory. 2 | //When this gets chaned in the future, we could change this to #include "/Engine/Private/Common.ush". 3 | #include "/Engine/Private/Common.ush" 4 | 5 | #include "HashedGrid.usf" 6 | 7 | //-------------------------------------------------------------------------------------- 8 | // Buffers 9 | //-------------------------------------------------------------------------------------- 10 | 11 | float dt; 12 | float totalTime; 13 | 14 | float boidSpeed; 15 | float boidSpeedVariation; 16 | float boidRotationSpeed; 17 | 18 | float neighbourhoodDistance; 19 | 20 | float separationDistance; 21 | float homeInnerRadius; 22 | 23 | float homeUrge; 24 | float separationUrge; 25 | float cohesionUrge; 26 | float alignmentUrge; 27 | 28 | RWStructuredBuffer positions_other; 29 | RWStructuredBuffer directions; 30 | RWStructuredBuffer directions_other; 31 | 32 | RWStructuredBuffer newDirections; 33 | 34 | 35 | 36 | float distanceSqrd(float3 a, float3 b) 37 | { 38 | float3 dir = a - b; 39 | return dot(dir, dir); 40 | } 41 | 42 | float3 safeNormal(float3 vec) 43 | { 44 | float l = length(vec); 45 | 46 | return l > 0.0f ? vec / l : float3(0.0f, 0.0f, 0.0f); 47 | } 48 | 49 | 50 | //-------------------------------------------------------------------------------------- 51 | // Functions 52 | //-------------------------------------------------------------------------------------- 53 | float hash( float n ) 54 | { 55 | return frac(sin(n)*43758.5453); 56 | } 57 | 58 | // The noise function returns a value in the range -1.0f -> 1.0f 59 | float noise1( float3 x ) 60 | { 61 | float3 p = floor(x); 62 | float3 f = frac(x); 63 | 64 | f = f*f*(3.0-2.0*f); 65 | float n = p.x + p.y*57.0 + 113.0*p.z; 66 | 67 | return lerp(lerp(lerp( hash(n+0.0), hash(n+1.0),f.x), 68 | lerp( hash(n+57.0), hash(n+58.0),f.x),f.y), 69 | lerp(lerp( hash(n+113.0), hash(n+114.0),f.x), 70 | lerp( hash(n+170.0), hash(n+171.0),f.x),f.y),f.z); 71 | } 72 | 73 | // CSVariables: 74 | // float boidSpeed 75 | // float boidSpeedVariation 76 | // float dt 77 | // float totalTime 78 | // float neighbourDistance 79 | 80 | float3 safeNormal(float3 vec, float3 safe) 81 | { 82 | float l = length(vec); 83 | 84 | return l > 0.0f ? vec / l : safe; 85 | } 86 | 87 | [numthreads(256, 1, 1)] 88 | void GridNeighboursBoidUpdate(uint3 ThreadId : SV_DispatchThreadID) 89 | { 90 | int index = ThreadId.x; 91 | 92 | if( index >= numParticles ) 93 | return; 94 | 95 | const float3 position_a = positions[index]; 96 | const float3 direction_a = directions[index]; 97 | 98 | 99 | 100 | float3 separation = float3(0.0, 0.0, 0.0); 101 | float3 alignment = float3(0.0, 0.0, 0.0); 102 | float3 neighboursCentre = position_a; 103 | 104 | int3 cellIndex = positionToCellIndex(position_a); 105 | uint count = 1; 106 | 107 | for(int i = -1; i <= 1; ++i) 108 | { 109 | for(int j = -1; j <= 1; ++j) 110 | { 111 | for(int k = -1; k <= 1; ++k) 112 | { 113 | int3 neighborIndex = cellIndex + int3(k, j, i); 114 | uint flatNeighborIndex = getFlatCellIndex(neighborIndex); 115 | 116 | // look up the offset to the cell: 117 | uint neighborIterator = cellOffsetBuffer[flatNeighborIndex]; 118 | 119 | // iterate through particles in the neighbour cell (if iterator offset is valid) 120 | while(neighborIterator != 0xFFFFFFFF && neighborIterator < numParticles) 121 | { 122 | uint particleIndexB = particleIndexBuffer[neighborIterator]; 123 | 124 | if (cellIndexBuffer[particleIndexB] != flatNeighborIndex) 125 | break; // we hit the end of this neighbourhood list 126 | 127 | float3 position_b = positions[particleIndexB]; 128 | 129 | float dist = distance(position_b, position_a); 130 | 131 | if (dist < neighbourhoodDistance && particleIndexB != index) 132 | { 133 | neighboursCentre += position_b; 134 | 135 | count++; 136 | 137 | alignment += directions[particleIndexB]; 138 | 139 | float3 dir = position_b - position_a; 140 | 141 | if (dist < separationDistance && dist > 0.0f) 142 | { 143 | float d = separationDistance - dist; 144 | 145 | separation -= (dir / dist) * d;// * d; 146 | } 147 | } 148 | 149 | neighborIterator++; // iterate... 150 | } 151 | } 152 | } 153 | } 154 | 155 | // cohesion 156 | float3 cohesion; 157 | 158 | if (count > 0) 159 | { 160 | neighboursCentre *= 1.0f / float(count); 161 | cohesion = neighboursCentre - position_a; 162 | 163 | //cohesion = safeNormal(cohesion); 164 | } 165 | else 166 | cohesion = float3(0.0f, 0.0f, 0.0f); 167 | 168 | 169 | //separation = safeNormal(separation, float3(0.0f, 0.0f, 0.0f)); 170 | alignment = safeNormal(alignment, float3(0.0f, 0.0f, 0.0f)); 171 | 172 | // home 173 | float3 home = float3(0.0f, 0.0f, 0.0f); 174 | 175 | float distFromHome = distance(home, position_a); 176 | 177 | float3 homeDir = float3(0.0f, 0.0f, 0.0f); 178 | 179 | if (distFromHome > homeInnerRadius) 180 | homeDir = safeNormal(home - position_a, float3(0.0f, 0.0f, 0.0f)); 181 | 182 | 183 | 184 | float3 newDirection = alignment * alignmentUrge 185 | + separation * separationUrge 186 | + cohesion * cohesionUrge 187 | + homeDir * homeUrge; 188 | 189 | // newDirection = safeNormal(newDirection, direction_a); 190 | 191 | float ip = exp(-boidRotationSpeed * dt); 192 | newDirection = lerp(newDirection, direction_a, ip); 193 | 194 | newDirection = safeNormal(newDirection, direction_a); 195 | 196 | newDirections[index].xyz = newDirection; 197 | } 198 | 199 | [numthreads(256, 1, 1)] 200 | void IntegrateBoidPosition(uint3 ThreadId : SV_DispatchThreadID) 201 | { 202 | int index = ThreadId.x; 203 | 204 | if (index >= numParticles) 205 | return; 206 | 207 | float3 direction = newDirections[index]; 208 | 209 | directions[index].xyz = direction.xyz; 210 | 211 | float noiseOffset = hash(float(index)); 212 | 213 | float noise = clamp(noise1(totalTime / 100.0 + noiseOffset), -1, 1) * 2.0 - 1.0; 214 | float velocity = boidSpeed * (1.0 + noise * boidSpeedVariation); 215 | 216 | positions[index].xyz = positions[index].xyz + direction * (velocity * dt); 217 | } 218 | 219 | [numthreads(256, 1, 1)] 220 | void rearrangePositions(uint3 ThreadId : SV_DispatchThreadID) 221 | { 222 | int index = ThreadId.x; 223 | 224 | if (index >= numParticles) 225 | return; 226 | 227 | positions_other[index] = positions[particleIndexBuffer[index]]; 228 | directions_other[index] = directions[particleIndexBuffer[index]]; 229 | } 230 | 231 | -------------------------------------------------------------------------------- /Shaders/CopyPositions.usf: -------------------------------------------------------------------------------- 1 | #include "/Engine/Private/Common.ush" 2 | 3 | int numParticles; 4 | float particleScale; 5 | 6 | RWStructuredBuffer positions; 7 | RWStructuredBuffer positions_other; 8 | 9 | RWStructuredBuffer directions; 10 | RWStructuredBuffer transforms_other; 11 | 12 | float4x4 look_at_matrix(float3 at, float3 eye, float3 up) 13 | { 14 | float3 zaxis = normalize(at - eye); 15 | float3 xaxis = normalize(cross(up, zaxis)); 16 | float3 yaxis = cross(zaxis, xaxis); 17 | 18 | float4x4 mat = float4x4( 19 | xaxis.x, yaxis.x, zaxis.x, 0, 20 | xaxis.y, yaxis.y, zaxis.y, 0, 21 | xaxis.z, yaxis.z, zaxis.z, 0, 22 | 0, 0, 0, 1.0f 23 | ) * particleScale; 24 | 25 | mat[3][3] = 1.0f; 26 | 27 | return mat; 28 | } 29 | 30 | [numthreads(256, 1, 1)] 31 | void copyPositions(uint3 ThreadId : SV_DispatchThreadID) 32 | { 33 | int index = ThreadId.x; 34 | 35 | if (index >= numParticles) 36 | return; 37 | 38 | positions_other[index] = positions[index]; 39 | 40 | float4x4 mat = look_at_matrix( 41 | positions[index].xyz, 42 | positions[index].xyz - normalize(directions[index].xyz), 43 | float3(0.0f, 0.0f, 1.0f) 44 | ); 45 | 46 | transforms_other[index * 3 + 0][0] = mat[0][0]; 47 | transforms_other[index * 3 + 0][1] = mat[1][0]; 48 | transforms_other[index * 3 + 0][2] = mat[2][0]; 49 | transforms_other[index * 3 + 0][3] = mat[3][0]; 50 | 51 | transforms_other[index * 3 + 1][0] = mat[0][1]; 52 | transforms_other[index * 3 + 1][1] = mat[1][1]; 53 | transforms_other[index * 3 + 1][2] = mat[2][1]; 54 | transforms_other[index * 3 + 1][3] = mat[3][1]; 55 | 56 | transforms_other[index * 3 + 2][0] = mat[0][2]; 57 | transforms_other[index * 3 + 2][1] = mat[1][2]; 58 | transforms_other[index * 3 + 2][2] = mat[2][2]; 59 | transforms_other[index * 3 + 2][3] = mat[3][2]; 60 | } -------------------------------------------------------------------------------- /Shaders/HashedGrid.usf: -------------------------------------------------------------------------------- 1 | // Copyright Timothy Davison 2020, all rights reserved. 2 | 3 | #include "/Engine/Private/Common.ush" 4 | 5 | // This code is inspired by this blog post on dynamic hashed grids for scalable fluid simulations: 6 | // https://wickedengine.net/2018/05/21/scalabe-gpu-fluid-simulation/ 7 | 8 | uint cellOffsetBufferSize; 9 | uint3 gridDimensions; 10 | uint numParticles; 11 | float cellSizeReciprocal; 12 | 13 | RWStructuredBuffer particleIndexBuffer; 14 | RWStructuredBuffer cellIndexBuffer; 15 | RWStructuredBuffer cellOffsetBuffer; 16 | 17 | RWStructuredBuffer positions; 18 | 19 | float timMod(float x, float y) 20 | { 21 | return x - y * floor(x / y); 22 | } 23 | 24 | uint getFlatCellIndex(int3 cellIndex) 25 | { 26 | int n = cellIndex.x + cellIndex.y * gridDimensions.x + cellIndex.z * gridDimensions.x * gridDimensions.y; 27 | 28 | n = timMod(n, cellOffsetBufferSize); 29 | 30 | return n; 31 | } 32 | 33 | int3 positionToCellIndex(float3 position) 34 | { 35 | return floor(position * cellSizeReciprocal); 36 | } 37 | 38 | [numthreads(256, 1, 1)] 39 | void createUnsortedList(uint3 ThreadId : SV_DispatchThreadID) 40 | { 41 | if (ThreadId.x >= numParticles) 42 | return; 43 | 44 | uint particleIndex = ThreadId.x; 45 | 46 | particleIndexBuffer[ThreadId.x] = ThreadId.x; 47 | 48 | float3 position = positions[particleIndex]; 49 | int3 cellIndex = positionToCellIndex(position); 50 | 51 | uint flatCellIndex = getFlatCellIndex(cellIndex); 52 | 53 | cellIndexBuffer[particleIndex] = flatCellIndex; 54 | } 55 | 56 | [numthreads(256, 1, 1)] 57 | void createOffsetList(uint3 ThreadId : SV_DispatchThreadID) 58 | { 59 | if (ThreadId.x >= numParticles) 60 | return; 61 | 62 | 63 | uint particleIndex = particleIndexBuffer[ThreadId.x]; 64 | 65 | 66 | uint cellIndex = cellIndexBuffer[particleIndex]; 67 | 68 | InterlockedMin(cellOffsetBuffer[cellIndex], ThreadId.x); 69 | } 70 | 71 | [numthreads(256, 1, 1)] 72 | void resetCellOffsetBuffer(uint3 ThreadId : SV_DispatchThreadID) 73 | { 74 | if (ThreadId.x >= cellOffsetBufferSize) 75 | return; 76 | 77 | cellOffsetBuffer[ThreadId.x] = 0xFFFFFFFF; 78 | } 79 | -------------------------------------------------------------------------------- /Source/UnrealGPUSwarm.Target.cs: -------------------------------------------------------------------------------- 1 | // Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. 2 | 3 | using UnrealBuildTool; 4 | using System.Collections.Generic; 5 | 6 | public class UnrealGPUSwarmTarget : TargetRules 7 | { 8 | public UnrealGPUSwarmTarget( TargetInfo Target) : base(Target) 9 | { 10 | Type = TargetType.Game; 11 | DefaultBuildSettings = BuildSettingsVersion.V2; 12 | ExtraModuleNames.AddRange( new string[] { "UnrealGPUSwarm" } ); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Source/UnrealGPUSwarm/ComputeShaderTestComponent.cpp: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | 4 | #include "ComputeShaderTestComponent.h" 5 | 6 | #include "ShaderParameterUtils.h" 7 | #include "RHIStaticStates.h" 8 | #include "Shader.h" 9 | #include "GlobalShader.h" 10 | #include "RenderGraphBuilder.h" 11 | #include "RenderGraphUtils.h" 12 | #include "ShaderParameterStruct.h" 13 | #include "UniformBuffer.h" 14 | #include "RHICommandList.h" 15 | 16 | #include "GPUBitonicSort.h" 17 | 18 | 19 | // Some useful links 20 | // ----------------- 21 | // [Enqueue render commands using lambdas](https://github.com/EpicGames/UnrealEngine/commit/41f6b93892dcf626a5acc155f7d71c756a5624b0) 22 | // 23 | // [Useful tutorial on Unreal compute shaders](https://github.com/Temaran/UE4ShaderPluginDemo) 24 | 25 | 26 | class FBoidsComputeShader : public FGlobalShader 27 | { 28 | public: 29 | DECLARE_GLOBAL_SHADER(FBoidsComputeShader); 30 | SHADER_USE_PARAMETER_STRUCT(FBoidsComputeShader, FGlobalShader); 31 | 32 | BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) 33 | SHADER_PARAMETER(float, dt) 34 | SHADER_PARAMETER(float, totalTime) 35 | SHADER_PARAMETER(float, boidSpeed) 36 | SHADER_PARAMETER(float, boidSpeedVariation) 37 | SHADER_PARAMETER(float, boidRotationSpeed) 38 | SHADER_PARAMETER(float, homeInnerRadius) 39 | SHADER_PARAMETER(float, separationDistance) 40 | SHADER_PARAMETER(float, neighbourhoodDistance) 41 | 42 | SHADER_PARAMETER(float, homeUrge) 43 | SHADER_PARAMETER(float, separationUrge) 44 | SHADER_PARAMETER(float, cohesionUrge) 45 | SHADER_PARAMETER(float, alignmentUrge) 46 | 47 | 48 | SHADER_PARAMETER_UAV(RWStructuredBuffer, positions) 49 | SHADER_PARAMETER_UAV(RWStructuredBuffer, directions) 50 | SHADER_PARAMETER_UAV(RWStructuredBuffer, newDirections) 51 | 52 | 53 | SHADER_PARAMETER(uint32, numParticles) 54 | SHADER_PARAMETER(float, cellSizeReciprocal) 55 | SHADER_PARAMETER(uint32, cellOffsetBufferSize) 56 | SHADER_PARAMETER(FIntVector, gridDimensions) 57 | 58 | SHADER_PARAMETER_UAV(RWStructuredBuffer, particleIndexBuffer) 59 | SHADER_PARAMETER_UAV(RWStructuredBuffer, cellIndexBuffer) 60 | SHADER_PARAMETER_UAV(RWStructuredBuffer, cellOffsetBuffer) 61 | 62 | END_SHADER_PARAMETER_STRUCT() 63 | 64 | public: 65 | static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters) 66 | { 67 | return IsFeatureLevelSupported(Parameters.Platform, ERHIFeatureLevel::ES3_1); 68 | } 69 | }; 70 | 71 | IMPLEMENT_GLOBAL_SHADER(FBoidsComputeShader, "/ComputeShaderPlugin/Boid.usf", "GridNeighboursBoidUpdate", SF_Compute); 72 | 73 | 74 | class FBoids_integratePosition_CS : public FGlobalShader 75 | { 76 | public: 77 | DECLARE_GLOBAL_SHADER(FBoids_integratePosition_CS); 78 | SHADER_USE_PARAMETER_STRUCT(FBoids_integratePosition_CS, FGlobalShader); 79 | 80 | BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) 81 | SHADER_PARAMETER(float, dt) 82 | SHADER_PARAMETER(float, totalTime) 83 | SHADER_PARAMETER(float, boidSpeed) 84 | SHADER_PARAMETER(float, boidSpeedVariation) 85 | SHADER_PARAMETER(float, boidRotationSpeed) 86 | SHADER_PARAMETER(uint32, numParticles) 87 | 88 | SHADER_PARAMETER_UAV(RWStructuredBuffer, positions) 89 | SHADER_PARAMETER_UAV(RWStructuredBuffer, directions) 90 | SHADER_PARAMETER_UAV(RWStructuredBuffer, newDirections) 91 | END_SHADER_PARAMETER_STRUCT() 92 | 93 | public: 94 | static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters) 95 | { 96 | return IsFeatureLevelSupported(Parameters.Platform, ERHIFeatureLevel::ES3_1); 97 | } 98 | }; 99 | 100 | IMPLEMENT_GLOBAL_SHADER(FBoids_integratePosition_CS, "/ComputeShaderPlugin/Boid.usf", "IntegrateBoidPosition", SF_Compute); 101 | 102 | 103 | 104 | 105 | 106 | 107 | class FBoids_rearrangePositions_CS : public FGlobalShader 108 | { 109 | public: 110 | DECLARE_GLOBAL_SHADER(FBoids_rearrangePositions_CS); 111 | SHADER_USE_PARAMETER_STRUCT(FBoids_rearrangePositions_CS, FGlobalShader); 112 | 113 | BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) 114 | SHADER_PARAMETER(uint32, numParticles) 115 | 116 | SHADER_PARAMETER_UAV(RWStructuredBuffer, positions) 117 | SHADER_PARAMETER_UAV(RWStructuredBuffer, directions) 118 | SHADER_PARAMETER_UAV(RWStructuredBuffer, positions_other) 119 | SHADER_PARAMETER_UAV(RWStructuredBuffer, directions_other) 120 | SHADER_PARAMETER_UAV(RWStructuredBuffer, particleIndexBuffer) 121 | END_SHADER_PARAMETER_STRUCT() 122 | 123 | public: 124 | static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters) 125 | { 126 | return IsFeatureLevelSupported(Parameters.Platform, ERHIFeatureLevel::ES3_1); 127 | } 128 | }; 129 | 130 | IMPLEMENT_GLOBAL_SHADER(FBoids_rearrangePositions_CS, "/ComputeShaderPlugin/Boid.usf", "rearrangePositions", SF_Compute); 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | class FHashedGrid_createUnsortedList_CS : public FGlobalShader 144 | { 145 | public: 146 | DECLARE_GLOBAL_SHADER(FHashedGrid_createUnsortedList_CS); 147 | SHADER_USE_PARAMETER_STRUCT(FHashedGrid_createUnsortedList_CS, FGlobalShader); 148 | 149 | BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) 150 | SHADER_PARAMETER(uint32, numParticles) 151 | SHADER_PARAMETER(float, cellSizeReciprocal) 152 | SHADER_PARAMETER(uint32, cellOffsetBufferSize) 153 | SHADER_PARAMETER(FIntVector, gridDimensions) 154 | 155 | SHADER_PARAMETER_UAV(RWStructuredBuffer, positions) 156 | SHADER_PARAMETER_UAV(RWStructuredBuffer, particleIndexBuffer) 157 | SHADER_PARAMETER_UAV(RWStructuredBuffer, cellIndexBuffer) 158 | END_SHADER_PARAMETER_STRUCT() 159 | 160 | public: 161 | static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters) 162 | { 163 | return IsFeatureLevelSupported(Parameters.Platform, ERHIFeatureLevel::ES3_1); 164 | } 165 | }; 166 | 167 | IMPLEMENT_GLOBAL_SHADER(FHashedGrid_createUnsortedList_CS, "/ComputeShaderPlugin/HashedGrid.usf", "createUnsortedList", SF_Compute); 168 | 169 | 170 | 171 | 172 | class FHashedGrid_createOffsetList_CS : public FGlobalShader 173 | { 174 | public: 175 | DECLARE_GLOBAL_SHADER(FHashedGrid_createOffsetList_CS); 176 | SHADER_USE_PARAMETER_STRUCT(FHashedGrid_createOffsetList_CS, FGlobalShader); 177 | 178 | BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) 179 | SHADER_PARAMETER(uint32, numParticles) 180 | SHADER_PARAMETER(float, cellSizeReciprocal) 181 | SHADER_PARAMETER(uint32, cellOffsetBufferSize) 182 | SHADER_PARAMETER(FIntVector, gridDimensions) 183 | 184 | SHADER_PARAMETER_UAV(RWStructuredBuffer, particleIndexBuffer) 185 | SHADER_PARAMETER_UAV(RWStructuredBuffer, cellIndexBuffer) 186 | SHADER_PARAMETER_UAV(RWStructuredBuffer, cellOffsetBuffer) 187 | END_SHADER_PARAMETER_STRUCT() 188 | 189 | public: 190 | static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters) 191 | { 192 | return IsFeatureLevelSupported(Parameters.Platform, ERHIFeatureLevel::ES3_1); 193 | } 194 | }; 195 | 196 | IMPLEMENT_GLOBAL_SHADER(FHashedGrid_createOffsetList_CS, "/ComputeShaderPlugin/HashedGrid.usf", "createOffsetList", SF_Compute); 197 | 198 | class FHashedGrid_resetCellOffsetBuffer_CS : public FGlobalShader 199 | { 200 | public: 201 | DECLARE_GLOBAL_SHADER(FHashedGrid_resetCellOffsetBuffer_CS); 202 | SHADER_USE_PARAMETER_STRUCT(FHashedGrid_resetCellOffsetBuffer_CS, FGlobalShader); 203 | 204 | BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) 205 | SHADER_PARAMETER(uint32, cellOffsetBufferSize) 206 | SHADER_PARAMETER_UAV(RWStructuredBuffer, cellOffsetBuffer) 207 | END_SHADER_PARAMETER_STRUCT() 208 | 209 | public: 210 | static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters) 211 | { 212 | return IsFeatureLevelSupported(Parameters.Platform, ERHIFeatureLevel::ES3_1); 213 | } 214 | }; 215 | 216 | IMPLEMENT_GLOBAL_SHADER(FHashedGrid_resetCellOffsetBuffer_CS, "/ComputeShaderPlugin/HashedGrid.usf", "resetCellOffsetBuffer", SF_Compute); 217 | 218 | 219 | 220 | 221 | 222 | 223 | // Sets default values for this component's properties 224 | UComputeShaderTestComponent::UComputeShaderTestComponent() 225 | { 226 | // Set this component to be initialized when the game starts, and to be ticked every frame. You can turn these features 227 | // off to improve performance if you don't need them. 228 | PrimaryComponentTick.bCanEverTick = true; 229 | 230 | // ... 231 | } 232 | 233 | static FVector unitVectorInSphere(FRandomStream& r) 234 | { 235 | FVector s; 236 | float dSqrd = 1.0f; 237 | 238 | do 239 | { 240 | s.X = r.GetFraction() * 2.f - 1.f; 241 | s.Y = r.GetFraction() * 2.f - 1.f; 242 | s.Z = r.GetFraction() * 2.f - 1.f; 243 | 244 | dSqrd = s.SizeSquared(); 245 | } while (dSqrd > 1.0f); 246 | 247 | return s; 248 | } 249 | 250 | // Called when the game starts 251 | void UComputeShaderTestComponent::BeginPlay() 252 | { 253 | Super::BeginPlay(); 254 | 255 | FRHICommandListImmediate& RHICommands = GRHICommandList.GetImmediateCommandList(); 256 | 257 | FRandomStream rng; 258 | 259 | // positions 260 | { 261 | TResourceArray resourceArray; 262 | const FVector4 zero(0.0f); 263 | resourceArray.Init(zero, numBoids); 264 | 265 | 266 | 267 | for (FVector4& position : resourceArray) 268 | { 269 | position = unitVectorInSphere(rng) * spawnRadius; 270 | } 271 | 272 | FRHIResourceCreateInfo createInfo; 273 | createInfo.ResourceArray = &resourceArray; 274 | 275 | const size_t size = sizeof(FVector4); 276 | 277 | for( int i = 0; i < 2; ++i ) 278 | { 279 | _positionBuffer[i] = RHICreateStructuredBuffer(size, size * numBoids, BUF_UnorderedAccess | BUF_ShaderResource, createInfo); 280 | _positionBufferUAV[i] = RHICreateUnorderedAccessView(_positionBuffer[i], false, false); 281 | 282 | // On platforms, like iOS, the resource array is discarded during RHICreateStructuredBuffer. Kill it. We only need one 283 | // for the first positionBuffer[0]. 284 | createInfo.ResourceArray = nullptr; 285 | } 286 | } 287 | 288 | // directions 289 | { 290 | TResourceArray resourceArray; 291 | const FVector4 zero(0.0f); 292 | 293 | resourceArray.Init(zero, numBoids); 294 | 295 | 296 | for (FVector4& position : resourceArray) 297 | { 298 | position = rng.GetUnitVector(); 299 | } 300 | 301 | FRHIResourceCreateInfo createInfo; 302 | createInfo.ResourceArray = &resourceArray; 303 | 304 | const size_t size = sizeof(FVector4); 305 | 306 | for( int i = 0; i < 2; ++i ) 307 | { 308 | _directionsBuffer[i] = RHICreateStructuredBuffer(size, size * numBoids, BUF_UnorderedAccess | BUF_ShaderResource, createInfo); 309 | _directionsBufferUAV[i] = RHICreateUnorderedAccessView(_directionsBuffer[i], false, false); 310 | 311 | // only platforms, like iOS, the resource array is discarded during RHICreateStructuredBuffer. Kill it. We only need one. 312 | createInfo.ResourceArray = nullptr; 313 | } 314 | 315 | 316 | _newDirectionsBuffer = RHICreateStructuredBuffer(size, size * numBoids, BUF_UnorderedAccess | BUF_ShaderResource, createInfo); 317 | _newDirectionsBufferUAV = RHICreateUnorderedAccessView(_newDirectionsBuffer, false, false); 318 | } 319 | 320 | // particleIndexBuffer 321 | { 322 | TResourceArray resourceArray; 323 | resourceArray.Init(0, numBoids); 324 | 325 | for( int i = 0; i < numBoids; ++i ) 326 | resourceArray[i] = i; 327 | 328 | FRHIResourceCreateInfo createInfo; 329 | createInfo.ResourceArray = &resourceArray; 330 | 331 | const size_t size = sizeof(uint32_t); 332 | 333 | _particleIndexBuffer = RHICreateStructuredBuffer(size, size * numBoids, BUF_UnorderedAccess | BUF_ShaderResource, createInfo); 334 | _particleIndexBufferUAV = RHICreateUnorderedAccessView(_particleIndexBuffer, false, false); 335 | } 336 | 337 | // cellIndexBuffer 338 | { 339 | TResourceArray resourceArray; 340 | resourceArray.Init(0, numBoids); 341 | 342 | FRHIResourceCreateInfo createInfo; 343 | createInfo.ResourceArray = &resourceArray; 344 | 345 | const size_t size = sizeof(uint32_t); 346 | 347 | _cellIndexBuffer = RHICreateStructuredBuffer(size, size * numBoids, BUF_UnorderedAccess | BUF_ShaderResource, createInfo); 348 | _cellIndexBufferUAV = RHICreateUnorderedAccessView(_cellIndexBuffer, false, false); 349 | } 350 | 351 | // cellOffsetBuffer 352 | { 353 | const size_t size = sizeof(uint32_t); 354 | const size_t gridSize = gridDimensions.X * gridDimensions.Y * gridDimensions.Z; 355 | 356 | TResourceArray resourceArray; 357 | resourceArray.Init(0, gridSize); 358 | 359 | FRHIResourceCreateInfo createInfo; 360 | createInfo.ResourceArray = &resourceArray; 361 | 362 | _cellOffsetBuffer = RHICreateStructuredBuffer(size, gridSize * size, BUF_UnorderedAccess | BUF_ShaderResource, createInfo); 363 | _cellOffsetBufferUAV = RHICreateUnorderedAccessView(_cellOffsetBuffer, false, false); 364 | } 365 | 366 | 367 | if (outputPositions.Num() != numBoids) 368 | { 369 | const FVector zero(0.0f); 370 | outputPositions.Init(zero, numBoids); 371 | } 372 | 373 | if (outputDirections.Num() != numBoids) 374 | { 375 | const FVector zero(0.0f); 376 | outputDirections.Init(zero, numBoids); 377 | } 378 | } 379 | 380 | static FIntVector groupSize(int numElements) 381 | { 382 | const int threadCount = 256; 383 | 384 | int count = ((numElements - 1) / threadCount) + 1; 385 | 386 | return FIntVector(count, 1, 1); 387 | } 388 | 389 | // Called every frame 390 | void UComputeShaderTestComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) 391 | { 392 | Super::TickComponent(DeltaTime, TickType, ThisTickFunction); 393 | 394 | float totalTime = GetOwner()->GetWorld()->TimeSeconds; 395 | const float dt = FMath::Min(1.0f / 60.0f, DeltaTime); 396 | 397 | ENQUEUE_RENDER_COMMAND(FComputeShaderRunner)( 398 | [&, totalTime, dt](FRHICommandListImmediate& RHICommands) 399 | { 400 | QUICK_SCOPE_CYCLE_COUNTER(STAT_UComputeShaderTestComponent_TickComponent); 401 | 402 | const uint32_t cellOffsetBufferSize = gridDimensions.X * gridDimensions.Y * gridDimensions.Z; 403 | 404 | auto& positionsBufferUAV = _positionBufferUAV[dualBufferCount]; 405 | auto& directionsBufferUAV = _directionsBufferUAV[dualBufferCount]; 406 | 407 | // calculate the unsorted cell index buffer 408 | { 409 | FHashedGrid_createUnsortedList_CS::FParameters parameters; 410 | parameters.numParticles = numBoids; 411 | parameters.cellSizeReciprocal = 1.0f / gridCellSize; 412 | parameters.cellOffsetBufferSize = cellOffsetBufferSize; 413 | parameters.gridDimensions = gridDimensions; 414 | parameters.positions = positionsBufferUAV; 415 | parameters.particleIndexBuffer = _particleIndexBufferUAV; 416 | parameters.cellIndexBuffer = _cellIndexBufferUAV; 417 | 418 | 419 | TShaderMapRef computeShader(GetGlobalShaderMap(GMaxRHIFeatureLevel)); 420 | FComputeShaderUtils::Dispatch( 421 | RHICommands, 422 | *computeShader, 423 | parameters, 424 | groupSize(numBoids) 425 | ); 426 | 427 | 428 | RHICommands.TransitionResource( 429 | EResourceTransitionAccess::ERWBarrier, 430 | EResourceTransitionPipeline::EGfxToCompute, 431 | _cellIndexBufferUAV 432 | ); 433 | } 434 | 435 | // sort the particle index buffer by cell index 436 | { 437 | FGPUBitonicSort gpuBitonicSort; 438 | 439 | gpuBitonicSort.sort( 440 | numBoids, 441 | numBoids, 442 | _cellIndexBufferUAV, 443 | _particleIndexBufferUAV, 444 | RHICommands 445 | ); 446 | 447 | RHICommands.TransitionResource( 448 | EResourceTransitionAccess::ERWBarrier, 449 | EResourceTransitionPipeline::EGfxToCompute, 450 | _particleIndexBufferUAV 451 | ); 452 | 453 | 454 | } 455 | 456 | // reset the cell offset buffer 457 | { 458 | FHashedGrid_resetCellOffsetBuffer_CS::FParameters parameters; 459 | parameters.cellOffsetBufferSize = cellOffsetBufferSize; 460 | parameters.cellOffsetBuffer = _cellOffsetBufferUAV; 461 | 462 | TShaderMapRef computeShader(GetGlobalShaderMap(GMaxRHIFeatureLevel)); 463 | FComputeShaderUtils::Dispatch( 464 | RHICommands, 465 | *computeShader, 466 | parameters, 467 | groupSize(cellOffsetBufferSize) 468 | ); 469 | 470 | } 471 | 472 | // build the cell offset buffer 473 | { 474 | FHashedGrid_createOffsetList_CS::FParameters parameters; 475 | parameters.numParticles = numBoids; 476 | parameters.cellSizeReciprocal = 1.0f / gridCellSize; 477 | parameters.cellOffsetBufferSize = cellOffsetBufferSize; 478 | parameters.gridDimensions = gridDimensions; 479 | 480 | parameters.particleIndexBuffer = _particleIndexBufferUAV; 481 | parameters.cellIndexBuffer = _cellIndexBufferUAV; 482 | parameters.cellOffsetBuffer = _cellOffsetBufferUAV; 483 | 484 | 485 | TShaderMapRef computeShader(GetGlobalShaderMap(GMaxRHIFeatureLevel)); 486 | FComputeShaderUtils::Dispatch( 487 | RHICommands, 488 | *computeShader, 489 | parameters, 490 | groupSize(numBoids) 491 | ); 492 | 493 | 494 | } 495 | 496 | if (false) 497 | { 498 | TArray cellIndexBuffer; 499 | cellIndexBuffer.Init(0, numBoids); 500 | 501 | TArray particleIndexBuffer; 502 | particleIndexBuffer.Init(0, numBoids); 503 | 504 | TArray cellOffsetBuffer; 505 | cellOffsetBuffer.Init(0, cellOffsetBufferSize); 506 | 507 | uint8* cellIndexData = (uint8*)RHILockStructuredBuffer(_cellIndexBuffer, 0, numBoids * sizeof(uint32_t), RLM_ReadOnly); 508 | FMemory::Memcpy(cellIndexBuffer.GetData(), cellIndexData, numBoids * sizeof(uint32_t)); 509 | RHIUnlockStructuredBuffer(_cellIndexBuffer); 510 | 511 | uint8* particleIndexData = (uint8*)RHILockStructuredBuffer(_particleIndexBuffer, 0, numBoids * sizeof(uint32_t), RLM_ReadOnly); 512 | FMemory::Memcpy(particleIndexBuffer.GetData(), particleIndexData, numBoids * sizeof(uint32_t)); 513 | RHIUnlockStructuredBuffer(_particleIndexBuffer); 514 | 515 | uint8* cellOffsetData = (uint8*)RHILockStructuredBuffer(_cellOffsetBuffer, 0, cellOffsetBufferSize * sizeof(uint32_t), RLM_ReadOnly); 516 | FMemory::Memcpy(cellOffsetBuffer.GetData(), cellOffsetData, cellOffsetBufferSize * sizeof(uint32_t)); 517 | RHIUnlockStructuredBuffer(_cellOffsetBuffer); 518 | } 519 | 520 | 521 | // execute the main compute shader 522 | { 523 | FBoidsComputeShader::FParameters parameters; 524 | parameters.dt = dt; 525 | parameters.totalTime = totalTime; 526 | parameters.separationDistance = separationDistance; 527 | parameters.boidSpeed = boidSpeed; 528 | parameters.boidSpeedVariation = boidSpeedVariation; 529 | parameters.separationDistance = separationDistance; 530 | parameters.boidRotationSpeed = boidRotationSpeed; 531 | parameters.homeInnerRadius = homeInnerRadius; 532 | parameters.neighbourhoodDistance = neighbourDistance; 533 | 534 | parameters.homeUrge = homeUrge; 535 | parameters.separationUrge = separationUrge; 536 | parameters.cohesionUrge = cohesionUrge; 537 | parameters.alignmentUrge = alignmentUrge; 538 | 539 | parameters.numParticles = numBoids; 540 | parameters.cellSizeReciprocal = 1.0f / gridCellSize; 541 | parameters.cellOffsetBufferSize = cellOffsetBufferSize; 542 | parameters.gridDimensions = gridDimensions; 543 | 544 | parameters.positions = positionsBufferUAV; 545 | parameters.directions = directionsBufferUAV; 546 | parameters.newDirections = _newDirectionsBufferUAV; 547 | parameters.cellOffsetBuffer = _cellOffsetBufferUAV; 548 | parameters.cellIndexBuffer = _cellIndexBufferUAV; 549 | parameters.particleIndexBuffer = _particleIndexBufferUAV; 550 | 551 | 552 | TShaderMapRef computeShader(GetGlobalShaderMap(GMaxRHIFeatureLevel)); 553 | FComputeShaderUtils::Dispatch( 554 | RHICommands, 555 | *computeShader, 556 | parameters, 557 | groupSize(numBoids) 558 | ); 559 | } 560 | 561 | // integrate positions 562 | { 563 | FBoids_integratePosition_CS::FParameters parameters; 564 | parameters.dt = dt; 565 | parameters.totalTime = totalTime; 566 | parameters.boidSpeed = boidSpeed; 567 | parameters.boidSpeedVariation = boidSpeedVariation; 568 | 569 | parameters.numParticles = numBoids; 570 | 571 | parameters.positions = positionsBufferUAV; 572 | parameters.directions = directionsBufferUAV; 573 | parameters.newDirections = _newDirectionsBufferUAV; 574 | 575 | TShaderMapRef computeShader(GetGlobalShaderMap(GMaxRHIFeatureLevel)); 576 | FComputeShaderUtils::Dispatch( 577 | RHICommands, 578 | *computeShader, 579 | parameters, 580 | groupSize(numBoids) 581 | ); 582 | } 583 | 584 | // rearrange positions for better cache-coherence on the next run 585 | { 586 | FBoids_rearrangePositions_CS::FParameters parameters; 587 | 588 | parameters.positions = positionsBufferUAV; 589 | parameters.directions = directionsBufferUAV; 590 | 591 | parameters.positions_other = _positionBufferUAV[(dualBufferCount + 1) % 2]; 592 | parameters.directions_other = _directionsBufferUAV[(dualBufferCount + 1) % 2]; 593 | 594 | parameters.particleIndexBuffer = _particleIndexBufferUAV; 595 | parameters.numParticles = numBoids; 596 | 597 | TShaderMapRef computeShader(GetGlobalShaderMap(GMaxRHIFeatureLevel)); 598 | FComputeShaderUtils::Dispatch( 599 | RHICommands, 600 | *computeShader, 601 | parameters, 602 | groupSize(numBoids) 603 | ); 604 | 605 | // rotate our buffers 606 | dualBufferCount = (dualBufferCount + 1) % 2; 607 | } 608 | }); 609 | } 610 | 611 | 612 | 613 | -------------------------------------------------------------------------------- /Source/UnrealGPUSwarm/ComputeShaderTestComponent.h: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "Components/ActorComponent.h" 7 | 8 | #include "GlobalShader.h" 9 | #include "UniformBuffer.h" 10 | #include "RHICommandList.h" 11 | 12 | #include 13 | 14 | #include "ComputeShaderTestComponent.generated.h" 15 | 16 | UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) ) 17 | class UNREALGPUSWARM_API UComputeShaderTestComponent : public UActorComponent 18 | { 19 | GENERATED_BODY() 20 | 21 | public: 22 | // Sets default values for this component's properties 23 | UComputeShaderTestComponent(); 24 | 25 | protected: 26 | // Called when the game starts 27 | virtual void BeginPlay() override; 28 | 29 | public: 30 | // Called every frame 31 | virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override; 32 | 33 | FUnorderedAccessViewRHIRef currentPositionsBuffer() 34 | { 35 | return _positionBufferUAV[dualBufferCount]; 36 | } 37 | 38 | FUnorderedAccessViewRHIRef currentDirectionsBuffer() 39 | { 40 | return _directionsBufferUAV[dualBufferCount]; 41 | } 42 | 43 | public: 44 | UPROPERTY(EditAnywhere, BlueprintReadWrite) 45 | int numBoids = 1000; 46 | 47 | UPROPERTY(EditAnywhere, BlueprintReadWrite) 48 | float neighbourDistance = 10.0f; 49 | 50 | UPROPERTY(EditAnywhere, BlueprintReadWrite) 51 | float separationDistance = 3.0f; 52 | 53 | UPROPERTY(EditAnywhere, BlueprintReadWrite) 54 | float homeInnerRadius = 200.0f; 55 | 56 | UPROPERTY(EditAnywhere, BlueprintReadWrite) 57 | float boidSpeed = 10.0f; 58 | 59 | UPROPERTY(EditAnywhere, BlueprintReadWrite) 60 | float boidSpeedVariation = 1.0f; 61 | 62 | UPROPERTY(EditAnywhere, BlueprintReadWrite) 63 | float boidRotationSpeed = 10.0f; 64 | 65 | UPROPERTY(EditAnywhere, BlueprintReadWrite) 66 | float homeUrge = 0.1f; 67 | 68 | UPROPERTY(EditAnywhere, BlueprintReadWrite) 69 | float separationUrge = 0.1f; 70 | 71 | UPROPERTY(EditAnywhere, BlueprintReadWrite) 72 | float cohesionUrge = 0.01f; 73 | 74 | UPROPERTY(EditAnywhere, BlueprintReadWrite) 75 | float alignmentUrge = 0.1f; 76 | 77 | UPROPERTY(EditAnywhere, BlueprintReadWrite) 78 | float spawnRadius = 600.0f; 79 | 80 | UPROPERTY(EditAnywhere, BlueprintReadWrite) 81 | FIntVector gridDimensions = FIntVector(256, 256, 256); 82 | 83 | UPROPERTY(EditAnywhere, BlueprintReadWrite) 84 | float gridCellSize = 5.0; 85 | 86 | TArray outputPositions; 87 | 88 | TArray outputDirections; 89 | 90 | 91 | 92 | public: 93 | unsigned int dualBufferCount = 0; 94 | 95 | // GPU side 96 | FStructuredBufferRHIRef _positionBuffer[2]; 97 | FUnorderedAccessViewRHIRef _positionBufferUAV[2]; // we need a UAV for writing 98 | 99 | FStructuredBufferRHIRef _directionsBuffer[2]; 100 | FUnorderedAccessViewRHIRef _directionsBufferUAV[2]; 101 | 102 | FStructuredBufferRHIRef _newDirectionsBuffer; 103 | FUnorderedAccessViewRHIRef _newDirectionsBufferUAV; 104 | 105 | // Hashed grid data structures 106 | FStructuredBufferRHIRef _particleIndexBuffer; 107 | FUnorderedAccessViewRHIRef _particleIndexBufferUAV; 108 | 109 | FStructuredBufferRHIRef _cellIndexBuffer; 110 | FUnorderedAccessViewRHIRef _cellIndexBufferUAV; 111 | 112 | FStructuredBufferRHIRef _cellOffsetBuffer; 113 | FUnorderedAccessViewRHIRef _cellOffsetBufferUAV; 114 | }; 115 | -------------------------------------------------------------------------------- /Source/UnrealGPUSwarm/DrawPositionsComponent.cpp: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | 4 | #include "DrawPositionsComponent.h" 5 | 6 | #include "InstanceBufferMeshComponent.h" 7 | 8 | #include "ShaderParameterUtils.h" 9 | #include "RHIStaticStates.h" 10 | #include "Shader.h" 11 | #include "GlobalShader.h" 12 | #include "RenderGraphBuilder.h" 13 | #include "RenderGraphUtils.h" 14 | #include "ShaderParameterStruct.h" 15 | #include "UniformBuffer.h" 16 | #include "RHICommandList.h" 17 | #include "Private/InstanceBufferMesh.h" 18 | 19 | #include "ComputeShaderTestComponent.h" 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | class FBoids_copyPositions_CS : public FGlobalShader 30 | { 31 | public: 32 | DECLARE_GLOBAL_SHADER(FBoids_copyPositions_CS); 33 | SHADER_USE_PARAMETER_STRUCT(FBoids_copyPositions_CS, FGlobalShader); 34 | 35 | BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) 36 | SHADER_PARAMETER(uint32, numParticles) 37 | SHADER_PARAMETER(float, particleScale) 38 | 39 | SHADER_PARAMETER_UAV(RWStructuredBuffer, positions) 40 | SHADER_PARAMETER_UAV(RWStructuredBuffer, positions_other) 41 | 42 | SHADER_PARAMETER_UAV(RWStructuredBuffer, directions) 43 | SHADER_PARAMETER_UAV(RWStructuredBuffer, transforms_other) 44 | END_SHADER_PARAMETER_STRUCT() 45 | 46 | public: 47 | static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters) 48 | { 49 | return IsFeatureLevelSupported(Parameters.Platform, ERHIFeatureLevel::ES3_1); 50 | } 51 | }; 52 | 53 | IMPLEMENT_GLOBAL_SHADER(FBoids_copyPositions_CS, "/ComputeShaderPlugin/CopyPositions.usf", "copyPositions", SF_Compute); 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | // Sets default values for this component's properties 78 | UDrawPositionsComponent::UDrawPositionsComponent() 79 | { 80 | // Set this component to be initialized when the game starts, and to be ticked every frame. You can turn these features 81 | // off to improve performance if you don't need them. 82 | PrimaryComponentTick.bCanEverTick = true; 83 | 84 | // ... 85 | } 86 | 87 | 88 | // Called when the game starts 89 | void UDrawPositionsComponent::BeginPlay() 90 | { 91 | Super::BeginPlay(); 92 | 93 | _initISMC(); 94 | } 95 | 96 | 97 | // Called every frame 98 | void UDrawPositionsComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) 99 | { 100 | Super::TickComponent(DeltaTime, TickType, ThisTickFunction); 101 | 102 | 103 | _updateInstanceBuffers(); 104 | } 105 | 106 | void UDrawPositionsComponent::_initISMC() 107 | { 108 | UInstanceBufferMeshComponent * ismc = GetOwner()->FindComponentByClass(); 109 | 110 | if (!ismc) return; 111 | 112 | ismc->SetSimulatePhysics(false); 113 | 114 | ismc->SetMobility(EComponentMobility::Movable); 115 | ismc->SetCollisionEnabled(ECollisionEnabled::NoCollision); 116 | ismc->SetCanEverAffectNavigation(false); 117 | //ismc->UseDynamicInstanceBuffer = true; 118 | //ismc->KeepInstanceBufferCPUAccess = true; 119 | ismc->SetCollisionProfileName(TEXT("NoCollision")); 120 | } 121 | 122 | static FIntVector groupSize(int numElements) 123 | { 124 | const int threadCount = 256; 125 | 126 | int count = ((numElements - 1) / threadCount) + 1; 127 | 128 | return FIntVector(count, 1, 1); 129 | } 130 | 131 | 132 | void UDrawPositionsComponent::_updateInstanceBuffers() 133 | { 134 | UInstanceBufferMeshComponent * ismc = GetOwner()->FindComponentByClass(); 135 | 136 | if (!ismc) return; 137 | 138 | UComputeShaderTestComponent * boidsComponent = GetOwner()->FindComponentByClass(); 139 | 140 | if (!boidsComponent) return; 141 | 142 | // resize up/down the ismc 143 | int toAdd = FMath::Max(0, boidsComponent->numBoids - ismc->GetInstanceCount()); 144 | int toRemove = FMath::Max(0, ismc->GetInstanceCount() - boidsComponent->numBoids); 145 | 146 | FTransform transform = FTransform::Identity; 147 | 148 | transform.SetScale3D(FVector(size)); 149 | 150 | ismc->SetNumInstances(boidsComponent->numBoids); 151 | 152 | /*for (int i = 0; i < toAdd; ++i) 153 | { 154 | ismc->AddInstance(transform); 155 | } 156 | 157 | for (int i = 0; i < toRemove; ++i) 158 | ismc->RemoveInstance(ismc->GetInstanceCount() - 1);*/ 159 | 160 | // directly update the buffer 161 | if (ismc->GetNumInstancesCurrentlyAllocated() == boidsComponent->numBoids) 162 | { 163 | auto renderData = ismc->PerInstanceRenderData; 164 | 165 | auto originSRV = renderData->InstanceBuffer.InstanceOriginSRV.GetReference(); 166 | 167 | int numParticles = boidsComponent->numBoids; 168 | 169 | if (!_positionsUAV) 170 | { 171 | FRHIVertexBuffer * positionsVertexBuffer = renderData->InstanceBuffer.InstanceOriginBuffer.VertexBufferRHI.GetReference(); 172 | 173 | uint8 theFormat = PF_A32B32G32R32F;; 174 | 175 | _positionsUAV = RHICreateUnorderedAccessView(positionsVertexBuffer, theFormat); 176 | } 177 | 178 | if (!_transformsUAV) 179 | { 180 | FRHIVertexBuffer * positionsVertexBuffer = renderData->InstanceBuffer.InstanceTransformBuffer.VertexBufferRHI.GetReference(); 181 | 182 | uint8 theFormat = PF_A32B32G32R32F;; 183 | 184 | _transformsUAV = RHICreateUnorderedAccessView(positionsVertexBuffer, theFormat); 185 | } 186 | 187 | FBoids_copyPositions_CS::FParameters parameters; 188 | parameters.positions = boidsComponent->currentPositionsBuffer(); 189 | parameters.positions_other = _positionsUAV; 190 | parameters.directions = boidsComponent->currentDirectionsBuffer(); 191 | parameters.transforms_other = _transformsUAV; 192 | parameters.numParticles = numParticles; 193 | parameters.particleScale = size; 194 | 195 | ENQUEUE_RENDER_COMMAND(FComputeShaderRunner)( 196 | [&, parameters, numParticles](FRHICommandListImmediate& RHICommands) 197 | { 198 | TShaderMapRef computeShader(GetGlobalShaderMap(GMaxRHIFeatureLevel)); 199 | FComputeShaderUtils::Dispatch( 200 | RHICommands, 201 | *computeShader, 202 | parameters, 203 | groupSize(numParticles) 204 | ); 205 | }); 206 | } 207 | } 208 | 209 | 210 | -------------------------------------------------------------------------------- /Source/UnrealGPUSwarm/DrawPositionsComponent.h: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "Components/ActorComponent.h" 7 | #include "DrawPositionsComponent.generated.h" 8 | 9 | 10 | UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) ) 11 | class UNREALGPUSWARM_API UDrawPositionsComponent : public UActorComponent 12 | { 13 | GENERATED_BODY() 14 | 15 | public: 16 | // Sets default values for this component's properties 17 | UDrawPositionsComponent(); 18 | 19 | protected: 20 | // Called when the game starts 21 | virtual void BeginPlay() override; 22 | 23 | public: 24 | // Called every frame 25 | virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override; 26 | 27 | protected: 28 | void _initISMC(); 29 | void _updateInstanceBuffers(); 30 | 31 | public: 32 | UPROPERTY(EditAnywhere, BlueprintReadWrite) 33 | float size = 0.02f; 34 | 35 | protected: 36 | TArray _instanceTransforms; 37 | 38 | FUnorderedAccessViewRHIRef _positionsUAV; // float4 39 | FUnorderedAccessViewRHIRef _transformsUAV; // float4x4 40 | }; 41 | -------------------------------------------------------------------------------- /Source/UnrealGPUSwarm/GPUBitonicSort.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Timothy Davison, all rights reserved. 2 | 3 | #include "GPUBitonicSort.h" 4 | 5 | #include "ShaderParameterUtils.h" 6 | #include "RHIStaticStates.h" 7 | #include "Shader.h" 8 | #include "GlobalShader.h" 9 | #include "RenderGraphBuilder.h" 10 | #include "RenderGraphUtils.h" 11 | #include "ShaderParameterStruct.h" 12 | #include "UniformBuffer.h" 13 | #include "RHICommandList.h" 14 | 15 | class FBitonicSort_sort : public FGlobalShader 16 | { 17 | public: 18 | DECLARE_GLOBAL_SHADER(FBitonicSort_sort); 19 | SHADER_USE_PARAMETER_STRUCT(FBitonicSort_sort, FGlobalShader); 20 | 21 | BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) 22 | SHADER_PARAMETER(FIntVector, job_params) 23 | SHADER_PARAMETER(uint32, itemCount) // the number of particles 24 | 25 | 26 | SHADER_PARAMETER_UAV(RWStructuredBuffer, comparisonBuffer) 27 | SHADER_PARAMETER_UAV(RWStructuredBuffer, indexBuffer) 28 | END_SHADER_PARAMETER_STRUCT() 29 | 30 | public: 31 | static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters) 32 | { 33 | return IsFeatureLevelSupported(Parameters.Platform, ERHIFeatureLevel::ES3_1); 34 | } 35 | }; 36 | 37 | IMPLEMENT_GLOBAL_SHADER(FBitonicSort_sort, "/ComputeShaderPlugin/BitonicSort_sort.usf", "BitonicSort_sort", SF_Compute); 38 | 39 | 40 | 41 | 42 | 43 | class FBitonicSort_sortInner : public FGlobalShader 44 | { 45 | public: 46 | DECLARE_GLOBAL_SHADER(FBitonicSort_sortInner); 47 | SHADER_USE_PARAMETER_STRUCT(FBitonicSort_sortInner, FGlobalShader); 48 | 49 | BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) 50 | SHADER_PARAMETER(FIntVector, job_params) 51 | SHADER_PARAMETER(uint32, itemCount) // the number of particles 52 | 53 | SHADER_PARAMETER_UAV(RWStructuredBuffer, comparisonBuffer) 54 | SHADER_PARAMETER_UAV(RWStructuredBuffer, indexBuffer) 55 | END_SHADER_PARAMETER_STRUCT() 56 | 57 | public: 58 | static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters) 59 | { 60 | return IsFeatureLevelSupported(Parameters.Platform, ERHIFeatureLevel::ES3_1); 61 | } 62 | }; 63 | 64 | IMPLEMENT_GLOBAL_SHADER(FBitonicSort_sortInner, "/ComputeShaderPlugin/BitonicSort_sortInner.usf", "BitonicSort_sortInner", SF_Compute); 65 | 66 | 67 | 68 | 69 | 70 | 71 | class FBitonicSort_sortStep : public FGlobalShader 72 | { 73 | public: 74 | DECLARE_GLOBAL_SHADER(FBitonicSort_sortStep); 75 | SHADER_USE_PARAMETER_STRUCT(FBitonicSort_sortStep, FGlobalShader); 76 | 77 | BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) 78 | SHADER_PARAMETER(FIntVector, job_params) 79 | SHADER_PARAMETER(uint32, itemCount) // the number of particles 80 | 81 | SHADER_PARAMETER_UAV(RWStructuredBuffer, comparisonBuffer) 82 | SHADER_PARAMETER_UAV(RWStructuredBuffer, indexBuffer) 83 | END_SHADER_PARAMETER_STRUCT() 84 | 85 | public: 86 | static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters) 87 | { 88 | return IsFeatureLevelSupported(Parameters.Platform, ERHIFeatureLevel::ES3_1); 89 | } 90 | }; 91 | 92 | IMPLEMENT_GLOBAL_SHADER(FBitonicSort_sortStep, "/ComputeShaderPlugin/BitonicSort_sortStep.usf", "BitonicSort_sortStep", SF_Compute); 93 | 94 | 95 | 96 | 97 | 98 | 99 | void FGPUBitonicSort::sort( 100 | uint32_t maxCount, 101 | uint32_t numItems, 102 | FUnorderedAccessViewRHIRef comparisonBuffer_read, 103 | FUnorderedAccessViewRHIRef indexBuffer_write, 104 | FRHICommandListImmediate& commands) 105 | { 106 | int threadCount = ((numItems - 1) >> 9) + 1; 107 | 108 | bool done = true; 109 | 110 | { 111 | FBitonicSort_sort::FParameters parameters; 112 | 113 | parameters.itemCount = numItems; 114 | 115 | parameters.comparisonBuffer = comparisonBuffer_read; 116 | parameters.indexBuffer = indexBuffer_write; 117 | 118 | unsigned int numThreadGroups = ((maxCount - 1) >> 9) + 1; 119 | 120 | //assert(numThreadGroups <= 1024); 121 | 122 | if (numThreadGroups > 1) 123 | { 124 | done = false; 125 | } 126 | 127 | TShaderMapRef computeShader(GetGlobalShaderMap(GMaxRHIFeatureLevel)); 128 | FComputeShaderUtils::Dispatch( 129 | commands, 130 | *computeShader, 131 | parameters, 132 | FIntVector(threadCount, 1, 1) 133 | ); 134 | } 135 | 136 | int presorted = 512; 137 | while (!done) 138 | { 139 | // Incremental sorting: 140 | 141 | done = true; 142 | 143 | 144 | 145 | // prepare thread group description data 146 | uint32_t numThreadGroups = 0; 147 | 148 | if (maxCount > (uint32_t)presorted) 149 | { 150 | if (maxCount > (uint32_t)presorted * 2) 151 | done = false; 152 | 153 | uint32_t pow2 = presorted; 154 | while (pow2 < maxCount) 155 | pow2 *= 2; 156 | numThreadGroups = pow2 >> 9; 157 | } 158 | 159 | FIntVector job_params; 160 | 161 | // step-sort 162 | uint32_t nMergeSize = presorted * 2; 163 | for (uint32_t nMergeSubSize = nMergeSize >> 1; nMergeSubSize > 256; nMergeSubSize = nMergeSubSize >> 1) 164 | { 165 | 166 | 167 | job_params.X = nMergeSubSize; 168 | if (nMergeSubSize == nMergeSize >> 1) 169 | { 170 | job_params.Y = (2 * nMergeSubSize - 1); 171 | job_params.Z = -1; 172 | } 173 | else 174 | { 175 | job_params.Y = nMergeSubSize; 176 | job_params.Z = 1; 177 | } 178 | 179 | FBitonicSort_sortStep::FParameters parameters; 180 | 181 | parameters.itemCount = numItems; 182 | parameters.job_params = job_params; 183 | 184 | parameters.comparisonBuffer = comparisonBuffer_read; 185 | parameters.indexBuffer = indexBuffer_write; 186 | 187 | 188 | TShaderMapRef computeShader(GetGlobalShaderMap(GMaxRHIFeatureLevel)); 189 | FComputeShaderUtils::Dispatch( 190 | commands, 191 | *computeShader, 192 | parameters, 193 | FIntVector(numThreadGroups, 1, 1) 194 | ); 195 | } 196 | 197 | { 198 | FBitonicSort_sortInner::FParameters parameters; 199 | 200 | parameters.job_params = job_params; 201 | parameters.itemCount = numItems; 202 | 203 | parameters.comparisonBuffer = comparisonBuffer_read; 204 | parameters.indexBuffer = indexBuffer_write; 205 | 206 | TShaderMapRef computeShader(GetGlobalShaderMap(GMaxRHIFeatureLevel)); 207 | FComputeShaderUtils::Dispatch( 208 | commands, 209 | *computeShader, 210 | parameters, 211 | FIntVector(numThreadGroups, 1, 1) 212 | ); 213 | } 214 | 215 | presorted *= 2; 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /Source/UnrealGPUSwarm/GPUBitonicSort.h: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Timothy Davison, all rights reserved. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "Components/ActorComponent.h" 7 | 8 | #include "GPUBitonicSort.generated.h" 9 | 10 | USTRUCT(BlueprintType) 11 | struct UNREALGPUSWARM_API FGPUBitonicSort 12 | { 13 | GENERATED_BODY() 14 | 15 | public: 16 | // Sort data on the GPU with bitonic sorting. 17 | void sort( 18 | uint32_t maxSize, 19 | uint32_t numItems, 20 | FUnorderedAccessViewRHIRef comparisionBuffer_read, 21 | FUnorderedAccessViewRHIRef indexBuffer_write, 22 | FRHICommandListImmediate& commands 23 | ); 24 | }; 25 | -------------------------------------------------------------------------------- /Source/UnrealGPUSwarm/InstanceBufferMeshComponent.h: -------------------------------------------------------------------------------- 1 | // Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "Stats/Stats.h" 7 | #include "UObject/ObjectMacros.h" 8 | #include "EngineDefines.h" 9 | #include "HitProxies.h" 10 | #include "Misc/Guid.h" 11 | #include "Engine/TextureStreamingTypes.h" 12 | #include "Components/StaticMeshComponent.h" 13 | 14 | #include "InstanceBufferMeshComponent.generated.h" 15 | 16 | class FLightingBuildOptions; 17 | class FPrimitiveSceneProxy; 18 | class FStaticLightingTextureMapping_InstanceBufferMesh; 19 | class ULightComponent; 20 | struct FNavigableGeometryExport; 21 | struct FNavigationRelevantData; 22 | struct FIBMPerInstanceRenderData; 23 | struct FStaticLightingPrimitiveInfo; 24 | 25 | // Tim: disabled 26 | // DECLARE_STATS_GROUP(TEXT("Foliage"), STATGROUP_Foliage, STATCAT_Advanced); 27 | 28 | class FStaticLightingTextureMapping_InstanceBufferMesh; 29 | class FInstancedLightMap2D; 30 | class FInstancedShadowMap2D; 31 | class FIBMStaticMeshInstanceData; 32 | 33 | 34 | 35 | 36 | 37 | USTRUCT() 38 | struct FInstanceBufferMeshMappingInfo 39 | { 40 | GENERATED_USTRUCT_BODY() 41 | 42 | FStaticLightingTextureMapping_InstanceBufferMesh* Mapping; 43 | 44 | FInstanceBufferMeshMappingInfo() 45 | : Mapping(nullptr) 46 | { 47 | } 48 | }; 49 | 50 | /** A component that efficiently renders multiple instances of the same StaticMesh. */ 51 | UCLASS(ClassGroup = Rendering, meta = (BlueprintSpawnableComponent), Blueprintable) 52 | class UNREALGPUSWARM_API UInstanceBufferMeshComponent : public UStaticMeshComponent 53 | { 54 | GENERATED_UCLASS_BODY() 55 | 56 | /** Needs implementation in InstancedStaticMesh.cpp to compile UniquePtr for forward declared class */ 57 | UInstanceBufferMeshComponent(FVTableHelper& Helper); 58 | virtual ~UInstanceBufferMeshComponent(); 59 | 60 | /** Value used to seed the random number stream that generates random numbers for each of this mesh's instances. 61 | The random number is stored in a buffer accessible to materials through the PerInstanceRandom expression. If 62 | this is set to zero (default), it will be populated automatically by the editor. */ 63 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=InstancedStaticMeshComponent) 64 | int32 InstancingRandomSeed; 65 | 66 | /** Distance from camera at which each instance begins to fade out. */ 67 | UPROPERTY(EditAnywhere, BlueprintReadOnly, Category=Culling) 68 | int32 InstanceStartCullDistance; 69 | 70 | /** Distance from camera at which each instance completely fades out. */ 71 | UPROPERTY(EditAnywhere, BlueprintReadOnly, Category=Culling) 72 | int32 InstanceEndCullDistance; 73 | 74 | /** Tracks outstanding proxysize, as this is a bit hard to do with the fire-and-forget grass. */ 75 | SIZE_T ProxySize; 76 | 77 | 78 | virtual void OnUpdateTransform(EUpdateTransformFlags UpdateTransformFlags, ETeleportType Teleport) override; 79 | 80 | /** Get the scale coming form the component, when computing StreamingTexture data. Used to support instanced meshes. */ 81 | virtual float GetTextureStreamingTransformScale() const override; 82 | /** Get material, UV density and bounds for a given material index. */ 83 | virtual bool GetMaterialStreamingData(int32 MaterialIndex, FPrimitiveMaterialInfo& MaterialData) const override; 84 | /** Build the data to compute accurate StreaminTexture data. */ 85 | virtual bool BuildTextureStreamingData(ETextureStreamingBuildType BuildType, EMaterialQualityLevel::Type QualityLevel, ERHIFeatureLevel::Type FeatureLevel, TSet& DependentResources) override; 86 | /** Get the StreaminTexture data. */ 87 | virtual void GetStreamingRenderAssetInfo(FStreamingTextureLevelContext& LevelContext, TArray& OutStreamingRenderAssets) const override; 88 | 89 | 90 | /** Get the number of instances in this component. */ 91 | UFUNCTION(BlueprintCallable, Category = "Components|InstancedStaticMesh") 92 | int32 GetInstanceCount() const; 93 | 94 | /** Sets the fading start and culling end distances for this component. */ 95 | UFUNCTION(BlueprintCallable, Category = "Components|InstancedStaticMesh") 96 | void SetCullDistances(int32 StartCullDistance, int32 EndCullDistance); 97 | 98 | void SetNumInstances(int numInstances); 99 | int32 GetNumInstancesCurrentlyAllocated() const; 100 | 101 | virtual bool ShouldCreatePhysicsState() const override; 102 | 103 | virtual void PostLoad() override; 104 | virtual void OnComponentCreated() override; 105 | 106 | public: 107 | /** Render data will be initialized on PostLoad or on demand. Released on the rendering thread. */ 108 | TSharedPtr PerInstanceRenderData; 109 | 110 | 111 | #if WITH_EDITOR 112 | /** One bit per instance if the instance is selected. */ 113 | TBitArray<> SelectedInstances; 114 | #endif 115 | 116 | uint32_t _numInstances = 0; 117 | 118 | 119 | //~ Begin UActorComponent Interface 120 | virtual TStructOnScope GetComponentInstanceData() const override; 121 | //~ End UActorComponent Interface 122 | 123 | //~ Begin UPrimitiveComponent Interface 124 | virtual FPrimitiveSceneProxy* CreateSceneProxy() override; 125 | 126 | public: 127 | virtual bool CanEditSimulatePhysics() override; 128 | 129 | virtual FBoxSphereBounds CalcBounds(const FTransform& BoundTransform) const override; 130 | virtual bool SupportsStaticLighting() const override { return true; } 131 | #if WITH_EDITOR 132 | virtual void GetStaticLightingInfo(FStaticLightingPrimitiveInfo& OutPrimitiveInfo,const TArray& InRelevantLights,const FLightingBuildOptions& Options) override; 133 | #endif 134 | virtual void GetLightAndShadowMapMemoryUsage( int32& LightMapMemoryUsage, int32& ShadowMapMemoryUsage ) const override; 135 | 136 | virtual bool DoCustomNavigableGeometryExport(FNavigableGeometryExport& GeomExport) const override { return false; }; // we don't support navigation 137 | 138 | //~ End UPrimitiveComponent Interface 139 | 140 | 141 | //~ Begin UObject Interface 142 | virtual void Serialize(FArchive& Ar) override; 143 | virtual void GetResourceSizeEx(FResourceSizeEx& CumulativeResourceSize) override; 144 | void BeginDestroy() override; 145 | virtual void PostDuplicate(bool bDuplicateForPIE) override; 146 | #if WITH_EDITOR 147 | virtual void PostEditChangeChainProperty(FPropertyChangedChainEvent& PropertyChangedEvent) override; 148 | virtual void PostEditUndo() override; 149 | #endif 150 | //~ End UObject Interface 151 | 152 | /** Applies the cached component instance data to a newly blueprint constructed component. */ 153 | virtual void ApplyComponentInstanceData(struct FIBMComponentInstanceData* ComponentInstanceData); 154 | 155 | /** Check to see if an instance is selected. */ 156 | bool IsInstanceSelected(int32 InInstanceIndex) const; 157 | 158 | /** Select/deselect an instance or group of instances. */ 159 | void SelectInstance(bool bInSelected, int32 InInstanceIndex, int32 InInstanceCount = 1); 160 | 161 | /** Deselect all instances. */ 162 | void ClearInstanceSelection(); 163 | 164 | /** Initialize the Per Instance Render Data */ 165 | void InitPerInstanceRenderData(); 166 | 167 | /** Transfers ownership of instance render data to a render thread. Instance render data will be released in scene proxy destructor or on render thread task. */ 168 | void ReleasePerInstanceRenderData(); 169 | 170 | // Number of instances in the render-side instance buffer 171 | virtual int32 GetNumRenderInstances() const { return GetNumInstancesCurrentlyAllocated(); } 172 | 173 | virtual void PropagateLightingScenarioChange() override; 174 | 175 | void GetInstancesMinMaxScale(FVector& MinScale, FVector& MaxScale) const; 176 | private: 177 | 178 | 179 | 180 | protected: 181 | 182 | 183 | 184 | /** Number of pending lightmaps still to be calculated (Apply()'d). */ 185 | UPROPERTY(Transient, DuplicateTransient, TextExportTransient) 186 | int32 NumPendingLightmaps; 187 | 188 | /** The mappings for all the instances of this component. */ 189 | UPROPERTY(Transient, DuplicateTransient, TextExportTransient) 190 | TArray CachedMappings; 191 | 192 | void ApplyLightMapping(FStaticLightingTextureMapping_InstanceBufferMesh* InMapping, ULevel* LightingScenario); 193 | 194 | void CreateHitProxyData(TArray>& HitProxies); 195 | 196 | /** Build instance buffer for rendering from current component data. */ 197 | void BuildRenderData(TArray>& OutHitProxies); 198 | 199 | /** Serialize instance buffer that is used for rendering. Only for cooked content */ 200 | void SerializeRenderData(FArchive& Ar); 201 | 202 | /** Creates rendering buffer from serialized data, if any */ 203 | virtual void OnPostLoadPerInstanceData(); 204 | 205 | friend FStaticLightingTextureMapping_InstanceBufferMesh; 206 | friend FInstancedLightMap2D; 207 | friend FInstancedShadowMap2D; 208 | }; 209 | 210 | /** InstancedStaticMeshInstance hit proxy */ 211 | struct HInstanceBufferMeshInstance : public HHitProxy 212 | { 213 | UInstanceBufferMeshComponent* Component; 214 | int32 InstanceIndex; 215 | 216 | DECLARE_HIT_PROXY(UNREALGPUSWARM_API); 217 | HInstanceBufferMeshInstance(UInstanceBufferMeshComponent* InComponent, int32 InInstanceIndex) : HHitProxy(HPP_World), Component(InComponent), InstanceIndex(InInstanceIndex) {} 218 | 219 | virtual void AddReferencedObjects(FReferenceCollector& Collector) override; 220 | 221 | virtual EMouseCursor::Type GetMouseCursor() override 222 | { 223 | return EMouseCursor::CardinalCross; 224 | } 225 | }; 226 | 227 | /** Used to store lightmap data during RerunConstructionScripts */ 228 | USTRUCT() 229 | struct FInstanceBufferMeshLightMapInstanceData 230 | { 231 | GENERATED_BODY() 232 | 233 | /** Transform of component */ 234 | UPROPERTY() 235 | FTransform Transform; 236 | 237 | /** guid from LODData */ 238 | UPROPERTY() 239 | TArray MapBuildDataIds; 240 | }; 241 | 242 | /** Helper class used to preserve lighting/selection state across blueprint reinstancing */ 243 | USTRUCT() 244 | struct FIBMComponentInstanceData : public FSceneComponentInstanceData 245 | { 246 | GENERATED_BODY() 247 | public: 248 | FIBMComponentInstanceData() = default; 249 | FIBMComponentInstanceData(const UInstanceBufferMeshComponent* InComponent) 250 | : FSceneComponentInstanceData(InComponent) 251 | , StaticMesh(InComponent->GetStaticMesh()) 252 | {} 253 | virtual ~FIBMComponentInstanceData() = default; 254 | 255 | virtual bool ContainsData() const override 256 | { 257 | return true; 258 | } 259 | 260 | virtual void ApplyToComponent(UActorComponent* Component, const ECacheApplyPhase CacheApplyPhase) override 261 | { 262 | Super::ApplyToComponent(Component, CacheApplyPhase); 263 | CastChecked(Component)->ApplyComponentInstanceData(this); 264 | } 265 | 266 | virtual void AddReferencedObjects(FReferenceCollector& Collector) override 267 | { 268 | Super::AddReferencedObjects(Collector); 269 | Collector.AddReferencedObject(StaticMesh); 270 | } 271 | 272 | public: 273 | /** Mesh being used by component */ 274 | UPROPERTY() 275 | UStaticMesh* StaticMesh; 276 | 277 | // Static lighting info 278 | UPROPERTY() 279 | FInstanceBufferMeshLightMapInstanceData CachedStaticLighting; 280 | 281 | 282 | /** The cached selected instances */ 283 | TBitArray<> SelectedInstances; 284 | 285 | /* The cached random seed */ 286 | UPROPERTY() 287 | int32 InstancingRandomSeed; 288 | }; 289 | -------------------------------------------------------------------------------- /Source/UnrealGPUSwarm/Private/InstanceBufferMesh.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. 2 | 3 | /*============================================================================= 4 | InstaneBufferMesh.cpp: Static mesh rendering code. 5 | =============================================================================*/ 6 | 7 | #include "InstanceBufferMesh.h" 8 | #include "../InstanceBufferMeshComponent.h" 9 | 10 | #include "AI/NavigationSystemBase.h" 11 | #include "Engine/MapBuildDataRegistry.h" 12 | #include "Components/LightComponent.h" 13 | #include "Logging/TokenizedMessage.h" 14 | #include "Logging/MessageLog.h" 15 | #include "UnrealEngine.h" 16 | #include "AI/NavigationSystemHelpers.h" 17 | #include "AI/Navigation/NavCollisionBase.h" 18 | #include "ShaderParameterUtils.h" 19 | #include "Misc/UObjectToken.h" 20 | #include "PhysXPublic.h" 21 | //#include "PhysicsEngine/PhysXSupport.h" 22 | #include "PhysicsEngine/BodySetup.h" 23 | #include "GameFramework/WorldSettings.h" 24 | #include "ComponentRecreateRenderStateContext.h" 25 | #include "SceneManagement.h" 26 | #include "Algo/Transform.h" 27 | #include "UObject/MobileObjectVersion.h" 28 | #include "EngineStats.h" 29 | #include "Interfaces/ITargetPlatform.h" 30 | #if WITH_EDITOR 31 | #include "DeviceProfiles/DeviceProfile.h" 32 | #include "DeviceProfiles/DeviceProfileManager.h" 33 | #endif // WITH_EDITOR 34 | #include "MeshMaterialShader.h" 35 | 36 | #include "RayTracingInstance.h" 37 | 38 | 39 | //#if RHI_RAYTRACING 40 | //#include "RayTracingInstance.h" 41 | //#endif 42 | 43 | #include "Interfaces/ITargetPlatform.h" 44 | #if WITH_EDITOR 45 | #include "DeviceProfiles/DeviceProfile.h" 46 | #include "DeviceProfiles/DeviceProfileManager.h" 47 | #endif // WITH_EDITOR 48 | #include "UObject/FortniteMainBranchObjectVersion.h" 49 | #include "UObject/EditorObjectVersion.h" 50 | 51 | 52 | const int32 InstancedStaticMeshMaxTexCoord = 8; 53 | 54 | IMPLEMENT_HIT_PROXY(HInstanceBufferMeshInstance, HHitProxy); 55 | 56 | const float TimHackMinSize = 0.000001f; 57 | const float TimHackLODScale = 1.0f; 58 | const float TimHackLODRange = 0.0f; 59 | 60 | namespace InstanceBuffeStaticMeshNameSpace 61 | { 62 | TAutoConsoleVariable CVarMinLOD( 63 | TEXT("foliage.MinLOD"), 64 | -1, 65 | TEXT("Used to discard the top LODs for performance evaluation. -1: Disable all effects of this cvar.")); 66 | 67 | static TAutoConsoleVariable CVarRayTracingRenderInstances( 68 | TEXT("r.RayTracing.InstancedStaticMeshes"), 69 | 1, 70 | TEXT("Include static mesh instances in ray tracing effects (default = 1 (Instances enabled in ray tracing))")); 71 | 72 | static TAutoConsoleVariable CVarRayTracingRenderInstancesCulling( 73 | TEXT("r.RayTracing.InstancedStaticMeshes.Culling"), 74 | 1, 75 | TEXT("Enable culling for instances in ray tracing (default = 1 (Culling enabled))")); 76 | 77 | static TAutoConsoleVariable CVarRayTracingInstancesCullClusterMaxRadiusMultiplier( 78 | TEXT("r.RayTracing.InstancedStaticMeshes.CullClusterMaxRadiusMultiplier"), 79 | 20.0f, 80 | TEXT("Multiplier for the maximum instance size (default = 20cm)")); 81 | 82 | static TAutoConsoleVariable CVarRayTracingInstancesCullClusterRadius( 83 | TEXT("r.RayTracing.InstancedStaticMeshes.CullClusterRadius"), 84 | 10000.0f, // 100 m 85 | TEXT("Ignore instances outside of this radius in ray tracing effects (default = 10000 (100m))")); 86 | 87 | static TAutoConsoleVariable CVarRayTracingInstancesLowScaleThreshold( 88 | TEXT("r.RayTracing.InstancedStaticMeshes.LowScaleRadiusThreshold"), 89 | 50.0f, // Instances with a radius smaller than this threshold get culled after CVarRayTracingInstancesLowScaleCullRadius 90 | TEXT("Threshold that classifies instances as small (default = 50cm))")); 91 | 92 | static TAutoConsoleVariable CVarRayTracingInstancesLowScaleCullRadius( 93 | TEXT("r.RayTracing.InstancedStaticMeshes.LowScaleCullRadius"), 94 | 1000.0f, 95 | TEXT("Cull radius for small instances (default = 1000 (10m))")); 96 | } 97 | 98 | 99 | 100 | 101 | /** InstancedStaticMeshInstance hit proxy */ 102 | void HInstanceBufferMeshInstance::AddReferencedObjects(FReferenceCollector& Collector) 103 | { 104 | Collector.AddReferencedObject(Component); 105 | } 106 | 107 | 108 | FIBMInstanceBuffer::FIBMInstanceBuffer(ERHIFeatureLevel::Type InFeatureLevel) 109 | : FRenderResource(InFeatureLevel) 110 | { 111 | } 112 | 113 | FIBMInstanceBuffer::~FIBMInstanceBuffer() 114 | { 115 | CleanUp(); 116 | } 117 | 118 | /** Delete existing resources */ 119 | void FIBMInstanceBuffer::CleanUp() 120 | { 121 | } 122 | 123 | 124 | void FIBMInstanceBuffer::UpdateWithNumInstances_Concurrent(unsigned int numInstances) 125 | { 126 | QUICK_SCOPE_CYCLE_COUNTER(STAT_FIBMInstanceBuffer_UpdateWithNumInstances_Concurrent); 127 | 128 | FIBMInstanceBuffer* InstanceBuffer = this; 129 | 130 | ENQUEUE_RENDER_COMMAND(InstanceBuffer_UpdateFromPreallocatedData)( 131 | [InstanceBuffer, numInstances](FRHICommandListImmediate& RHICmdList) 132 | { 133 | InstanceBuffer->UpdateWithNumInstances_RenderThread(numInstances); 134 | }); 135 | } 136 | 137 | void FIBMInstanceBuffer::UpdateWithNumInstances_RenderThread(unsigned int numInstances) 138 | { 139 | _numInstances = numInstances; 140 | 141 | UpdateRHI(); 142 | } 143 | 144 | 145 | /** 146 | * Specialized assignment operator, only used when importing LOD's. 147 | */ 148 | void FIBMInstanceBuffer::operator=(const FIBMInstanceBuffer &Other) 149 | { 150 | checkf(0, TEXT("Unexpected assignment call")); 151 | } 152 | 153 | void FIBMInstanceBuffer::InitRHI() 154 | { 155 | if (_numInstances > 0) 156 | { 157 | QUICK_SCOPE_CYCLE_COUNTER(STAT_FStaticMeshInstanceBuffer_InitRHI); 158 | LLM_SCOPE(ELLMTag::InstancedMesh); 159 | 160 | // Tim: We want to write to this buffer GPU side. So it should not be static. 161 | auto AccessFlags = BUF_UnorderedAccess | BUF_ShaderResource; 162 | 163 | uint32_t originsSize = _numInstances * uint32_t(sizeof(FVector4)); 164 | uint32_t transformsSize = _numInstances * uint32_t(sizeof(FVector4) * 3); 165 | uint32_t lightmapSize = _numInstances * uint32_t(sizeof(int16) * 4); // see FStaticMeshInstanceData::FInstanceLightMapVector (which is private, hence the manual calculations here) 166 | 167 | CreateVertexBuffer(originsSize, AccessFlags, 16, PF_A32B32G32R32F, InstanceOriginBuffer.VertexBufferRHI, InstanceOriginSRV); 168 | CreateVertexBuffer(transformsSize, AccessFlags, 16, PF_A32B32G32R32F, InstanceTransformBuffer.VertexBufferRHI, InstanceTransformSRV); 169 | CreateVertexBuffer(lightmapSize, AccessFlags, 8, PF_R16G16B16A16_SNORM, InstanceLightmapBuffer.VertexBufferRHI, InstanceLightmapSRV); 170 | } 171 | } 172 | 173 | void FIBMInstanceBuffer::ReleaseRHI() 174 | { 175 | InstanceOriginSRV.SafeRelease(); 176 | InstanceTransformSRV.SafeRelease(); 177 | InstanceLightmapSRV.SafeRelease(); 178 | 179 | InstanceOriginBuffer.ReleaseRHI(); 180 | InstanceTransformBuffer.ReleaseRHI(); 181 | InstanceLightmapBuffer.ReleaseRHI(); 182 | } 183 | 184 | void FIBMInstanceBuffer::InitResource() 185 | { 186 | FRenderResource::InitResource(); 187 | InstanceOriginBuffer.InitResource(); 188 | InstanceTransformBuffer.InitResource(); 189 | InstanceLightmapBuffer.InitResource(); 190 | } 191 | 192 | void FIBMInstanceBuffer::ReleaseResource() 193 | { 194 | FRenderResource::ReleaseResource(); 195 | InstanceOriginBuffer.ReleaseResource(); 196 | InstanceTransformBuffer.ReleaseResource(); 197 | InstanceLightmapBuffer.ReleaseResource(); 198 | } 199 | 200 | SIZE_T FIBMInstanceBuffer::GetResourceSize() const 201 | { 202 | return 0; 203 | } 204 | 205 | void FIBMInstanceBuffer::CreateVertexBuffer(uint32_t sizeInBytes, uint32 InUsage, uint32 InStride, uint8 InFormat, FVertexBufferRHIRef& OutVertexBufferRHI, FShaderResourceViewRHIRef& OutInstanceSRV) 206 | { 207 | FRHIResourceCreateInfo CreateInfo; 208 | 209 | OutVertexBufferRHI = RHICreateVertexBuffer(sizeInBytes, InUsage, CreateInfo); 210 | 211 | if (RHISupportsManualVertexFetch(GMaxRHIShaderPlatform)) 212 | { 213 | OutInstanceSRV = RHICreateShaderResourceView(OutVertexBufferRHI, InStride, InFormat); 214 | } 215 | } 216 | 217 | void FIBMInstanceBuffer::BindInstanceVertexBuffer(const class FVertexFactory* VertexFactory, FInstanceBufferMeshDataType& InstancedStaticMeshData) const 218 | { 219 | if (GetNumInstances() > 0 && RHISupportsManualVertexFetch(GMaxRHIShaderPlatform)) 220 | { 221 | check(InstanceOriginSRV); 222 | check(InstanceTransformSRV); 223 | check(InstanceLightmapSRV); 224 | } 225 | 226 | { 227 | InstancedStaticMeshData.InstanceOriginSRV = InstanceOriginSRV; 228 | InstancedStaticMeshData.InstanceTransformSRV = InstanceTransformSRV; 229 | InstancedStaticMeshData.InstanceLightmapSRV = InstanceLightmapSRV; 230 | InstancedStaticMeshData.NumInstances = GetNumInstances(); 231 | InstancedStaticMeshData.bInitialized = true; 232 | } 233 | 234 | { 235 | InstancedStaticMeshData.InstanceOriginComponent = FVertexStreamComponent( 236 | &InstanceOriginBuffer, 237 | 0, 238 | 16, 239 | VET_Float4, 240 | EVertexStreamUsage::ManualFetch | EVertexStreamUsage::Instancing 241 | ); 242 | 243 | EVertexElementType TransformType = VET_Float4; 244 | uint32 TransformStride = 16; 245 | 246 | InstancedStaticMeshData.InstanceTransformComponent[0] = FVertexStreamComponent( 247 | &InstanceTransformBuffer, 248 | 0 * TransformStride, 249 | 3 * TransformStride, 250 | TransformType, 251 | EVertexStreamUsage::ManualFetch | EVertexStreamUsage::Instancing 252 | ); 253 | InstancedStaticMeshData.InstanceTransformComponent[1] = FVertexStreamComponent( 254 | &InstanceTransformBuffer, 255 | 1 * TransformStride, 256 | 3 * TransformStride, 257 | TransformType, 258 | EVertexStreamUsage::ManualFetch | EVertexStreamUsage::Instancing 259 | ); 260 | InstancedStaticMeshData.InstanceTransformComponent[2] = FVertexStreamComponent( 261 | &InstanceTransformBuffer, 262 | 2 * TransformStride, 263 | 3 * TransformStride, 264 | TransformType, 265 | EVertexStreamUsage::ManualFetch | EVertexStreamUsage::Instancing 266 | ); 267 | 268 | InstancedStaticMeshData.InstanceLightmapAndShadowMapUVBiasComponent = FVertexStreamComponent( 269 | &InstanceLightmapBuffer, 270 | 0, 271 | 8, 272 | VET_Short4N, 273 | EVertexStreamUsage::ManualFetch | EVertexStreamUsage::Instancing 274 | ); 275 | } 276 | } 277 | 278 | 279 | 280 | 281 | /** 282 | * Should we cache the material's shadertype on this platform with this vertex factory? 283 | */ 284 | bool FInstanceBufferMeshVertexFactory::ShouldCompilePermutation(EShaderPlatform Platform, const class FMaterial* Material, const class FShaderType* ShaderType) 285 | { 286 | return (Material->IsUsedWithInstancedStaticMeshes() || Material->IsSpecialEngineMaterial()) 287 | && FLocalVertexFactory::ShouldCompilePermutation(Platform, Material, ShaderType); 288 | } 289 | 290 | 291 | /** 292 | * Copy the data from another vertex factory 293 | * @param Other - factory to copy from 294 | */ 295 | void FInstanceBufferMeshVertexFactory::Copy(const FInstanceBufferMeshVertexFactory& Other) 296 | { 297 | FInstanceBufferMeshVertexFactory* VertexFactory = this; 298 | const FDataType* DataCopy = &Other.Data; 299 | ENQUEUE_RENDER_COMMAND(FInstancedStaticMeshVertexFactoryCopyData)( 300 | [VertexFactory, DataCopy](FRHICommandListImmediate& RHICmdList) 301 | { 302 | VertexFactory->Data = *DataCopy; 303 | }); 304 | BeginUpdateResourceRHI(this); 305 | } 306 | 307 | void FInstanceBufferMeshVertexFactory::InitRHI() 308 | { 309 | check(HasValidFeatureLevel()); 310 | const bool bInstanced = GRHISupportsInstancing; 311 | 312 | #if !ALLOW_DITHERED_LOD_FOR_INSTANCED_STATIC_MESHES // position(and normal) only shaders cannot work with dithered LOD 313 | // If the vertex buffer containing position is not the same vertex buffer containing the rest of the data, 314 | // then initialize PositionStream and PositionDeclaration. 315 | if(Data.PositionComponent.VertexBuffer != Data.TangentBasisComponents[0].VertexBuffer) 316 | { 317 | auto AddDeclaration = [&Data](EVertexInputStreamType InputStreamType, bool bInstanced, bool bAddNormal) 318 | { 319 | FVertexDeclarationElementList StreamElements; 320 | StreamElements.Add(AccessPositionStreamComponent(Data.PositionComponent, 0)); 321 | 322 | if (bAddNormal) 323 | StreamElements.Add(AccessPositionStreamComponent(Data.TangentBasisComponents[2], 2)); 324 | 325 | if (bInstanced) 326 | { 327 | // toss in the instanced location stream 328 | StreamElements.Add(AccessPositionStreamComponent(Data.InstanceOriginComponent, 8)); 329 | StreamElements.Add(AccessPositionStreamComponent(Data.InstanceTransformComponent[0], 9)); 330 | StreamElements.Add(AccessPositionStreamComponent(Data.InstanceTransformComponent[1], 10)); 331 | StreamElements.Add(AccessPositionStreamComponent(Data.InstanceTransformComponent[2], 11)); 332 | } 333 | 334 | InitDeclaration(StreamElements, InputStreamType); 335 | }; 336 | AddDeclaration(EVertexInputStreamType::PositionOnly, bInstanced, false); 337 | AddDeclaration(EVertexInputStreamType::PositionAndNormalOnly, bInstanced, true); 338 | } 339 | #endif 340 | 341 | FVertexDeclarationElementList Elements; 342 | if(Data.PositionComponent.VertexBuffer != NULL) 343 | { 344 | Elements.Add(AccessStreamComponent(Data.PositionComponent,0)); 345 | } 346 | 347 | // only tangent,normal are used by the stream. the binormal is derived in the shader 348 | uint8 TangentBasisAttributes[2] = { 1, 2 }; 349 | for(int32 AxisIndex = 0;AxisIndex < 2;AxisIndex++) 350 | { 351 | if(Data.TangentBasisComponents[AxisIndex].VertexBuffer != NULL) 352 | { 353 | Elements.Add(AccessStreamComponent(Data.TangentBasisComponents[AxisIndex],TangentBasisAttributes[AxisIndex])); 354 | } 355 | } 356 | 357 | if (Data.ColorComponentsSRV == nullptr) 358 | { 359 | Data.ColorComponentsSRV = GNullColorVertexBuffer.VertexBufferSRV; 360 | Data.ColorIndexMask = 0; 361 | } 362 | 363 | if(Data.ColorComponent.VertexBuffer) 364 | { 365 | Elements.Add(AccessStreamComponent(Data.ColorComponent,3)); 366 | } 367 | else 368 | { 369 | //If the mesh has no color component, set the null color buffer on a new stream with a stride of 0. 370 | //This wastes 4 bytes of bandwidth per vertex, but prevents having to compile out twice the number of vertex factories. 371 | FVertexStreamComponent NullColorComponent(&GNullColorVertexBuffer, 0, 0, VET_Color, EVertexStreamUsage::ManualFetch); 372 | Elements.Add(AccessStreamComponent(NullColorComponent, 3)); 373 | } 374 | 375 | if(Data.TextureCoordinates.Num()) 376 | { 377 | const int32 BaseTexCoordAttribute = 4; 378 | for(int32 CoordinateIndex = 0;CoordinateIndex < Data.TextureCoordinates.Num();CoordinateIndex++) 379 | { 380 | Elements.Add(AccessStreamComponent( 381 | Data.TextureCoordinates[CoordinateIndex], 382 | BaseTexCoordAttribute + CoordinateIndex 383 | )); 384 | } 385 | 386 | for(int32 CoordinateIndex = Data.TextureCoordinates.Num(); CoordinateIndex < (InstancedStaticMeshMaxTexCoord + 1) / 2; CoordinateIndex++) 387 | { 388 | Elements.Add(AccessStreamComponent( 389 | Data.TextureCoordinates[Data.TextureCoordinates.Num() - 1], 390 | BaseTexCoordAttribute + CoordinateIndex 391 | )); 392 | } 393 | } 394 | 395 | if(Data.LightMapCoordinateComponent.VertexBuffer) 396 | { 397 | Elements.Add(AccessStreamComponent(Data.LightMapCoordinateComponent,15)); 398 | } 399 | else if(Data.TextureCoordinates.Num()) 400 | { 401 | Elements.Add(AccessStreamComponent(Data.TextureCoordinates[0],15)); 402 | } 403 | 404 | // toss in the instanced location stream 405 | check(Data.InstanceOriginComponent.VertexBuffer || !bInstanced); 406 | if (bInstanced && Data.InstanceOriginComponent.VertexBuffer) 407 | { 408 | Elements.Add(AccessStreamComponent(Data.InstanceOriginComponent, 8)); 409 | } 410 | 411 | check(Data.InstanceTransformComponent[0].VertexBuffer || !bInstanced); 412 | if (bInstanced && Data.InstanceTransformComponent[0].VertexBuffer) 413 | { 414 | Elements.Add(AccessStreamComponent(Data.InstanceTransformComponent[0], 9)); 415 | Elements.Add(AccessStreamComponent(Data.InstanceTransformComponent[1], 10)); 416 | Elements.Add(AccessStreamComponent(Data.InstanceTransformComponent[2], 11)); 417 | } 418 | 419 | if (bInstanced && Data.InstanceLightmapAndShadowMapUVBiasComponent.VertexBuffer) 420 | { 421 | Elements.Add(AccessStreamComponent(Data.InstanceLightmapAndShadowMapUVBiasComponent,12)); 422 | } 423 | 424 | // we don't need per-vertex shadow or lightmap rendering 425 | InitDeclaration(Elements); 426 | } 427 | 428 | 429 | FVertexFactoryShaderParameters* FInstanceBufferMeshVertexFactory::ConstructShaderParameters(EShaderFrequency ShaderFrequency) 430 | { 431 | return ShaderFrequency == SF_Vertex ? new FInstanceBufferMeshVertexFactoryShaderParameters() : NULL; 432 | } 433 | 434 | IMPLEMENT_VERTEX_FACTORY_TYPE_EX(FInstanceBufferMeshVertexFactory,"/Engine/Private/LocalVertexFactory.ush",true,true,true,true,true,true,false); 435 | 436 | void FInstanceBufferMeshRenderData::InitVertexFactories() 437 | { 438 | const bool bInstanced = GRHISupportsInstancing; 439 | 440 | check(bInstanced); 441 | 442 | // Allocate the vertex factories for each LOD 443 | for (int32 LODIndex = 0; LODIndex < LODModels.Num(); LODIndex++) 444 | { 445 | VertexFactories.Add(new FInstanceBufferMeshVertexFactory(FeatureLevel)); 446 | } 447 | 448 | const int32 LightMapCoordinateIndex = Component->GetStaticMesh()->LightMapCoordinateIndex; 449 | ENQUEUE_RENDER_COMMAND(InstancedStaticMeshRenderData_InitVertexFactories)( 450 | [this, LightMapCoordinateIndex, bInstanced](FRHICommandListImmediate& RHICmdList) 451 | { 452 | for (int32 LODIndex = 0; LODIndex < VertexFactories.Num(); LODIndex++) 453 | { 454 | const FStaticMeshLODResources* RenderData = &LODModels[LODIndex]; 455 | 456 | FInstanceBufferMeshVertexFactory::FDataType Data; 457 | // Assign to the vertex factory for this LOD. 458 | FInstanceBufferMeshVertexFactory& VertexFactory = VertexFactories[LODIndex]; 459 | 460 | RenderData->VertexBuffers.PositionVertexBuffer.BindPositionVertexBuffer(&VertexFactory, Data); 461 | RenderData->VertexBuffers.StaticMeshVertexBuffer.BindTangentVertexBuffer(&VertexFactory, Data); 462 | RenderData->VertexBuffers.StaticMeshVertexBuffer.BindPackedTexCoordVertexBuffer(&VertexFactory, Data); 463 | if (LightMapCoordinateIndex < (int32)RenderData->VertexBuffers.StaticMeshVertexBuffer.GetNumTexCoords() && LightMapCoordinateIndex >= 0) 464 | { 465 | RenderData->VertexBuffers.StaticMeshVertexBuffer.BindLightMapVertexBuffer(&VertexFactory, Data, LightMapCoordinateIndex); 466 | } 467 | RenderData->VertexBuffers.ColorVertexBuffer.BindColorVertexBuffer(&VertexFactory, Data); 468 | 469 | if (bInstanced) 470 | { 471 | check(PerInstanceRenderData); 472 | PerInstanceRenderData->InstanceBuffer.BindInstanceVertexBuffer(&VertexFactory, Data); 473 | } 474 | 475 | VertexFactory.SetData(Data); 476 | VertexFactory.InitResource(); 477 | } 478 | }); 479 | } 480 | 481 | FIBMPerInstanceRenderData::FIBMPerInstanceRenderData(ERHIFeatureLevel::Type InFeaureLevel) 482 | : ResourceSize(0) 483 | , InstanceBuffer(InFeaureLevel) 484 | { 485 | BeginInitResource(&InstanceBuffer); 486 | } 487 | 488 | FIBMPerInstanceRenderData::~FIBMPerInstanceRenderData() 489 | { 490 | // Should be always destructed on rendering thread 491 | InstanceBuffer.ReleaseResource(); 492 | } 493 | 494 | 495 | UNREALGPUSWARM_API void FIBMPerInstanceRenderData::UpdateWithNumInstance(int numInstances) 496 | { 497 | InstanceBuffer.UpdateWithNumInstances_Concurrent(numInstances); 498 | } 499 | 500 | 501 | 502 | SIZE_T FInstanceBufferMeshSceneProxy::GetTypeHash() const 503 | { 504 | static size_t UniquePointer; 505 | return reinterpret_cast(&UniquePointer); 506 | } 507 | 508 | void FInstanceBufferMeshSceneProxy::GetDynamicMeshElements(const TArray& Views, const FSceneViewFamily& ViewFamily, uint32 VisibilityMap, FMeshElementCollector& Collector) const 509 | { 510 | QUICK_SCOPE_CYCLE_COUNTER(STAT_InstancedStaticMeshSceneProxy_GetMeshElements); 511 | 512 | const bool bSelectionRenderEnabled = GIsEditor && ViewFamily.EngineShowFlags.Selection; 513 | 514 | // If the first pass rendered selected instances only, we need to render the deselected instances in a second pass 515 | const int32 NumSelectionGroups = (bSelectionRenderEnabled && bHasSelectedInstances) ? 2 : 1; 516 | 517 | const FInstancingUserData* PassUserData[2] = 518 | { 519 | bHasSelectedInstances && bSelectionRenderEnabled ? &UserData_SelectedInstances : &UserData_AllInstances, 520 | &UserData_DeselectedInstances 521 | }; 522 | 523 | bool BatchRenderSelection[2] = 524 | { 525 | bSelectionRenderEnabled && IsSelected(), 526 | false 527 | }; 528 | 529 | const bool bIsWireframe = ViewFamily.EngineShowFlags.Wireframe; 530 | 531 | for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++) 532 | { 533 | if (VisibilityMap & (1 << ViewIndex)) 534 | { 535 | const FSceneView* View = Views[ViewIndex]; 536 | 537 | for (int32 SelectionGroupIndex = 0; SelectionGroupIndex < NumSelectionGroups; SelectionGroupIndex++) 538 | { 539 | const int32 LODIndex = GetLOD(View); 540 | const FStaticMeshLODResources& LODModel = StaticMesh->RenderData->LODResources[LODIndex]; 541 | 542 | for (int32 SectionIndex = 0; SectionIndex < LODModel.Sections.Num(); SectionIndex++) 543 | { 544 | const int32 NumBatches = GetNumMeshBatches(); 545 | 546 | for (int32 BatchIndex = 0; BatchIndex < NumBatches; BatchIndex++) 547 | { 548 | FMeshBatch& MeshElement = Collector.AllocateMesh(); 549 | 550 | if (GetMeshElement(LODIndex, BatchIndex, SectionIndex, GetDepthPriorityGroup(View), BatchRenderSelection[SelectionGroupIndex], true, MeshElement)) 551 | { 552 | //@todo-rco this is only supporting selection on the first element 553 | MeshElement.Elements[0].UserData = PassUserData[SelectionGroupIndex]; 554 | MeshElement.Elements[0].bUserDataIsColorVertexBuffer = false; 555 | MeshElement.bCanApplyViewModeOverrides = true; 556 | MeshElement.bUseSelectionOutline = BatchRenderSelection[SelectionGroupIndex]; 557 | MeshElement.bUseWireframeSelectionColoring = BatchRenderSelection[SelectionGroupIndex]; 558 | 559 | if (View->bRenderFirstInstanceOnly) 560 | { 561 | for (int32 ElementIndex = 0; ElementIndex < MeshElement.Elements.Num(); ElementIndex++) 562 | { 563 | MeshElement.Elements[ElementIndex].NumInstances = FMath::Min(MeshElement.Elements[ElementIndex].NumInstances, 1); 564 | } 565 | } 566 | 567 | Collector.AddMesh(ViewIndex, MeshElement); 568 | INC_DWORD_STAT_BY(STAT_StaticMeshTriangles, MeshElement.GetNumPrimitives()); 569 | } 570 | } 571 | } 572 | } 573 | } 574 | } 575 | } 576 | 577 | int32 FInstanceBufferMeshSceneProxy::GetNumMeshBatches() const 578 | { 579 | const bool bInstanced = GRHISupportsInstancing; 580 | 581 | if (bInstanced) 582 | { 583 | return 1; 584 | } 585 | else 586 | { 587 | const uint32 NumInstances = InstancedRenderData.PerInstanceRenderData->InstanceBuffer.GetNumInstances(); 588 | const uint32 MaxInstancesPerBatch = FInstanceBufferMeshVertexFactory::NumBitsForVisibilityMask(); 589 | const uint32 NumBatches = FMath::DivideAndRoundUp(NumInstances, MaxInstancesPerBatch); 590 | return NumBatches; 591 | } 592 | } 593 | 594 | int32 FInstanceBufferMeshSceneProxy::CollectOccluderElements(FOccluderElementsCollector& Collector) const 595 | { 596 | if (OccluderData) 597 | { 598 | FIBMInstanceBuffer& InstanceBuffer = InstancedRenderData.PerInstanceRenderData->InstanceBuffer; 599 | const int32 NumInstances = InstanceBuffer.GetNumInstances(); 600 | 601 | for (int32 InstanceIndex = 0; InstanceIndex < NumInstances; ++InstanceIndex) 602 | { 603 | FMatrix InstanceToLocal; 604 | ; 605 | InstanceToLocal.M[3][3] = 1.0f; 606 | 607 | Collector.AddElements(OccluderData->VerticesSP, OccluderData->IndicesSP, InstanceToLocal * GetLocalToWorld()); 608 | } 609 | 610 | return NumInstances; 611 | } 612 | 613 | return 0; 614 | } 615 | 616 | void FInstanceBufferMeshSceneProxy::SetupProxy(UInstanceBufferMeshComponent* InComponent) 617 | { 618 | // Tim: disabled 619 | //#if WITH_EDITOR 620 | // if (bHasSelectedInstances) 621 | // { 622 | // // if we have selected indices, mark scene proxy as selected. 623 | // SetSelection_GameThread(true); 624 | // } 625 | //#endif 626 | // Make sure all the materials are okay to be rendered as an instanced mesh. 627 | for (int32 LODIndex = 0; LODIndex < LODs.Num(); LODIndex++) 628 | { 629 | FStaticMeshSceneProxy::FLODInfo& LODInfo = LODs[LODIndex]; 630 | for (int32 SectionIndex = 0; SectionIndex < LODInfo.Sections.Num(); SectionIndex++) 631 | { 632 | FStaticMeshSceneProxy::FLODInfo::FSectionInfo& Section = LODInfo.Sections[SectionIndex]; 633 | if (!Section.Material->CheckMaterialUsage_Concurrent(MATUSAGE_InstancedStaticMeshes)) 634 | { 635 | Section.Material = UMaterial::GetDefaultMaterial(MD_Surface); 636 | } 637 | } 638 | } 639 | 640 | const bool bInstanced = GRHISupportsInstancing; 641 | 642 | // Copy the parameters for LOD - all instances 643 | UserData_AllInstances.MeshRenderData = InComponent->GetStaticMesh()->RenderData.Get(); 644 | UserData_AllInstances.StartCullDistance = InComponent->InstanceStartCullDistance; 645 | UserData_AllInstances.EndCullDistance = InComponent->InstanceEndCullDistance; 646 | UserData_AllInstances.MinLOD = ClampedMinLOD; 647 | UserData_AllInstances.bRenderSelected = true; 648 | UserData_AllInstances.bRenderUnselected = true; 649 | UserData_AllInstances.RenderData = bInstanced ? nullptr : &InstancedRenderData; 650 | 651 | FVector MinScale(0); 652 | FVector MaxScale(0); 653 | InComponent->GetInstancesMinMaxScale(MinScale, MaxScale); 654 | 655 | UserData_AllInstances.AverageInstancesScale = MinScale + (MaxScale - MinScale) / 2.0f; 656 | 657 | // selected only 658 | UserData_SelectedInstances = UserData_AllInstances; 659 | UserData_SelectedInstances.bRenderUnselected = false; 660 | 661 | // unselected only 662 | UserData_DeselectedInstances = UserData_AllInstances; 663 | UserData_DeselectedInstances.bRenderSelected = false; 664 | } 665 | 666 | void FInstanceBufferMeshSceneProxy::SetupInstancedMeshBatch(int32 LODIndex, int32 BatchIndex, FMeshBatch& OutMeshBatch) const 667 | { 668 | const bool bInstanced = GRHISupportsInstancing; 669 | 670 | // we only support hardware with instancing 671 | check(bInstanced); 672 | 673 | OutMeshBatch.VertexFactory = &InstancedRenderData.VertexFactories[LODIndex]; 674 | const uint32 NumInstances = InstancedRenderData.PerInstanceRenderData->InstanceBuffer.GetNumInstances(); 675 | FMeshBatchElement& BatchElement0 = OutMeshBatch.Elements[0]; 676 | BatchElement0.UserData = (void*)&UserData_AllInstances; 677 | BatchElement0.bUserDataIsColorVertexBuffer = false; 678 | BatchElement0.InstancedLODIndex = LODIndex; 679 | BatchElement0.UserIndex = 0; 680 | BatchElement0.bIsInstancedMesh = bInstanced; 681 | BatchElement0.PrimitiveUniformBuffer = GetUniformBuffer(); 682 | 683 | BatchElement0.NumInstances = NumInstances; 684 | } 685 | 686 | void FInstanceBufferMeshSceneProxy::GetLightRelevance(const FLightSceneProxy* LightSceneProxy, bool& bDynamic, bool& bRelevant, bool& bLightMapped, bool& bShadowMapped) const 687 | { 688 | FStaticMeshSceneProxy::GetLightRelevance(LightSceneProxy, bDynamic, bRelevant, bLightMapped, bShadowMapped); 689 | 690 | if (InstancedRenderData.PerInstanceRenderData->InstanceBuffer.GetNumInstances() == 0) 691 | { 692 | bRelevant = false; 693 | } 694 | } 695 | 696 | bool FInstanceBufferMeshSceneProxy::GetShadowMeshElement(int32 LODIndex, int32 BatchIndex, uint8 InDepthPriorityGroup, FMeshBatch& OutMeshBatch, bool bDitheredLODTransition) const 697 | { 698 | if (LODIndex < InstancedRenderData.VertexFactories.Num() && FStaticMeshSceneProxy::GetShadowMeshElement(LODIndex, BatchIndex, InDepthPriorityGroup, OutMeshBatch, bDitheredLODTransition)) 699 | { 700 | SetupInstancedMeshBatch(LODIndex, BatchIndex, OutMeshBatch); 701 | return true; 702 | } 703 | return false; 704 | } 705 | 706 | /** Sets up a FMeshBatch for a specific LOD and element. */ 707 | bool FInstanceBufferMeshSceneProxy::GetMeshElement(int32 LODIndex, int32 BatchIndex, int32 ElementIndex, uint8 InDepthPriorityGroup, bool bUseSelectionOutline, bool bAllowPreCulledIndices, FMeshBatch& OutMeshBatch) const 708 | { 709 | if (LODIndex < InstancedRenderData.VertexFactories.Num() && FStaticMeshSceneProxy::GetMeshElement(LODIndex, BatchIndex, ElementIndex, InDepthPriorityGroup, bUseSelectionOutline, bAllowPreCulledIndices, OutMeshBatch)) 710 | { 711 | SetupInstancedMeshBatch(LODIndex, BatchIndex, OutMeshBatch); 712 | return true; 713 | } 714 | return false; 715 | }; 716 | 717 | /** Sets up a wireframe FMeshBatch for a specific LOD. */ 718 | bool FInstanceBufferMeshSceneProxy::GetWireframeMeshElement(int32 LODIndex, int32 BatchIndex, const FMaterialRenderProxy* WireframeRenderProxy, uint8 InDepthPriorityGroup, bool bAllowPreCulledIndices, FMeshBatch& OutMeshBatch) const 719 | { 720 | if (LODIndex < InstancedRenderData.VertexFactories.Num() && FStaticMeshSceneProxy::GetWireframeMeshElement(LODIndex, BatchIndex, WireframeRenderProxy, InDepthPriorityGroup, bAllowPreCulledIndices, OutMeshBatch)) 721 | { 722 | SetupInstancedMeshBatch(LODIndex, BatchIndex, OutMeshBatch); 723 | return true; 724 | } 725 | return false; 726 | } 727 | 728 | void FInstanceBufferMeshSceneProxy::GetDistancefieldAtlasData(FBox& LocalVolumeBounds, FVector2D& OutDistanceMinMax, FIntVector& OutBlockMin, FIntVector& OutBlockSize, bool& bOutBuiltAsIfTwoSided, bool& bMeshWasPlane, float& SelfShadowBias, TArray& ObjectLocalToWorldTransforms, bool& bOutThrottled) const 729 | { 730 | FStaticMeshSceneProxy::GetDistancefieldAtlasData(LocalVolumeBounds, OutDistanceMinMax, OutBlockMin, OutBlockSize, bOutBuiltAsIfTwoSided, bMeshWasPlane, SelfShadowBias, ObjectLocalToWorldTransforms, bOutThrottled); 731 | 732 | ObjectLocalToWorldTransforms.Reset(); 733 | 734 | const uint32 NumInstances = InstancedRenderData.PerInstanceRenderData->InstanceBuffer.GetNumInstances(); 735 | for (uint32 InstanceIndex = 0; InstanceIndex < NumInstances; InstanceIndex++) 736 | { 737 | FMatrix InstanceToLocal; 738 | 739 | // Tim: Hack, we need the position 740 | InstanceToLocal.M[3][3] = 1.0f; 741 | 742 | ObjectLocalToWorldTransforms.Add(InstanceToLocal * GetLocalToWorld()); 743 | } 744 | } 745 | 746 | void FInstanceBufferMeshSceneProxy::GetDistanceFieldInstanceInfo(int32& NumInstances, float& BoundsSurfaceArea) const 747 | { 748 | NumInstances = DistanceFieldData ? InstancedRenderData.PerInstanceRenderData->InstanceBuffer.GetNumInstances() : 0; 749 | 750 | if (NumInstances > 0) 751 | { 752 | FMatrix InstanceToLocal; 753 | const int32 InstanceIndex = 0; 754 | 755 | InstanceToLocal.M[3][3] = 1.0f; 756 | 757 | const FMatrix InstanceTransform = InstanceToLocal * GetLocalToWorld(); 758 | const FVector AxisScales = InstanceTransform.GetScaleVector(); 759 | const FVector BoxDimensions = RenderData->Bounds.BoxExtent * AxisScales * 2; 760 | 761 | BoundsSurfaceArea = 2 * BoxDimensions.X * BoxDimensions.Y 762 | + 2 * BoxDimensions.Z * BoxDimensions.Y 763 | + 2 * BoxDimensions.X * BoxDimensions.Z; 764 | } 765 | } 766 | 767 | HHitProxy* FInstanceBufferMeshSceneProxy::CreateHitProxies(UPrimitiveComponent* Component,TArray >& OutHitProxies) 768 | { 769 | if(InstancedRenderData.PerInstanceRenderData.IsValid() && InstancedRenderData.PerInstanceRenderData->HitProxies.Num() ) 770 | { 771 | // Add any per-instance hit proxies. 772 | OutHitProxies += InstancedRenderData.PerInstanceRenderData->HitProxies; 773 | 774 | // No default hit proxy. 775 | return nullptr; 776 | } 777 | 778 | return FStaticMeshSceneProxy::CreateHitProxies(Component, OutHitProxies); 779 | } 780 | 781 | #if RHI_RAYTRACING 782 | void FInstanceBufferMeshSceneProxy::GetDynamicRayTracingInstances(struct FRayTracingMaterialGatheringContext& Context, TArray& OutRayTracingInstances) 783 | { 784 | //if (!CVarRayTracingRenderInstances.GetValueOnRenderThread()) 785 | //{ 786 | // return; 787 | //} 788 | 789 | //// Tim: Disabling this stuff for now 790 | //uint32 LOD = GetCurrentFirstLODIdx_RenderThread(); 791 | //const int32 InstanceCount = 0; 792 | 793 | //if (InstanceCount == 0) 794 | //{ 795 | // return; 796 | //} 797 | ////setup a 'template' for the instance first, so we aren't duplicating work 798 | ////#dxr_todo: when multiple LODs are used, template needs to be an array of templates, probably best initialized on-demand via a lamda 799 | //FRayTracingInstance RayTracingInstanceTemplate; 800 | //RayTracingInstanceTemplate.Geometry = &RenderData->LODResources[LOD].RayTracingGeometry; 801 | 802 | ////preallocate the worst-case to prevent an explosion of reallocs 803 | ////#dxr_todo: possibly track used instances and reserve based on previous behavior 804 | //RayTracingInstanceTemplate.InstanceTransforms.Reserve(InstanceCount); 805 | 806 | 807 | //int32 SectionCount = InstancedRenderData.LODModels[LOD].Sections.Num(); 808 | 809 | //for (int32 SectionIdx = 0; SectionIdx < SectionCount; ++SectionIdx) 810 | //{ 811 | // //#dxr_todo: so far we use the parent static mesh path to get material data 812 | // FMeshBatch MeshBatch; 813 | // FStaticMeshSceneProxy::GetMeshElement(LOD, 0, SectionIdx, 0, false, false, MeshBatch); 814 | 815 | // RayTracingInstanceTemplate.Materials.Add(MeshBatch); 816 | //} 817 | //RayTracingInstanceTemplate.BuildInstanceMaskAndFlags(); 818 | 819 | //if (CVarRayTracingRenderInstancesCulling.GetValueOnRenderThread() > 0 && RayTracingCullClusterInstances.Num() > 0) 820 | //{ 821 | // const float BVHCullRadius = CVarRayTracingInstancesCullClusterRadius.GetValueOnRenderThread(); 822 | // const float BVHLowScaleThreshold = CVarRayTracingInstancesLowScaleThreshold.GetValueOnRenderThread(); 823 | // const float BVHLowScaleRadius = CVarRayTracingInstancesLowScaleCullRadius.GetValueOnRenderThread(); 824 | // const bool ApplyGeneralCulling = BVHCullRadius > 0.0f; 825 | // const bool ApplyLowScaleCulling = BVHLowScaleThreshold > 0.0f && BVHLowScaleRadius > 0.0f; 826 | // FMatrix ToWorld = InstancedRenderData.Component->GetComponentTransform().ToMatrixWithScale(); 827 | 828 | // // Iterate over all culling clusters 829 | // for (int32 ClusterIdx = 0; ClusterIdx < RayTracingCullClusterBoundsMin.Num(); ++ClusterIdx) 830 | // { 831 | // FVector VClusterBBoxSize = RayTracingCullClusterBoundsMax[ClusterIdx] - RayTracingCullClusterBoundsMin[ClusterIdx]; 832 | // FVector VClusterCenter = 0.5f * (RayTracingCullClusterBoundsMax[ClusterIdx] + RayTracingCullClusterBoundsMin[ClusterIdx]); 833 | // FVector VToClusterCenter = VClusterCenter - Context.ReferenceView->ViewLocation; 834 | // float ClusterRadius = 0.5f * VClusterBBoxSize.Size(); 835 | // float DistToClusterCenter = VToClusterCenter.Size(); 836 | 837 | // // Cull whole cluster if the bounding sphere is too far away 838 | // if ((DistToClusterCenter - ClusterRadius) > BVHCullRadius && ApplyGeneralCulling) 839 | // { 840 | // continue; 841 | // } 842 | 843 | // TDoubleLinkedList< uint32 >* InstanceList = RayTracingCullClusterInstances[ClusterIdx]; 844 | 845 | // // Unroll instances in the current cluster into the array 846 | // for (TDoubleLinkedList::TDoubleLinkedListNode* InstancePtr = InstanceList->GetHead(); InstancePtr != nullptr; InstancePtr = InstancePtr->GetNextNode()) 847 | // { 848 | // const uint32 Instance = InstancePtr->GetValue(); 849 | 850 | // if (InstancedRenderData.Component->PerInstanceSMData.IsValidIndex(Instance)) 851 | // { 852 | // const FIBMInstanceData& InstanceData = InstancedRenderData.Component->PerInstanceSMData[Instance]; 853 | // FMatrix InstanceTransform = InstanceData.Transform * ToWorld; 854 | // FVector InstanceLocation = InstanceTransform.TransformPosition({ 0.0f,0.0f,0.0f }); 855 | // FVector VToInstanceCenter = Context.ReferenceView->ViewLocation - InstanceLocation; 856 | // float DistanceToInstanceCenter = VToInstanceCenter.Size(); 857 | 858 | // FVector VMin, VMax, VDiag; 859 | // InstancedRenderData.Component->GetLocalBounds(VMin, VMax); 860 | // VMin = InstanceTransform.TransformPosition(VMin); 861 | // VMax = InstanceTransform.TransformPosition(VMax); 862 | // VDiag = VMax - VMin; 863 | 864 | // float InstanceRadius = 0.5f * VDiag.Size(); 865 | // float DistanceToInstanceStart = DistanceToInstanceCenter - InstanceRadius; 866 | 867 | // // Cull instance based on distance 868 | // if (DistanceToInstanceStart > BVHCullRadius && ApplyGeneralCulling) 869 | // continue; 870 | 871 | // // Special culling for small scale objects 872 | // if (InstanceRadius < BVHLowScaleThreshold && ApplyLowScaleCulling) 873 | // { 874 | // if (DistanceToInstanceStart > BVHLowScaleRadius) 875 | // continue; 876 | // } 877 | 878 | // RayTracingInstanceTemplate.InstanceTransforms.Add(InstanceTransform); 879 | // } 880 | // } 881 | // } 882 | //} 883 | //else 884 | //{ 885 | // // No culling 886 | // for (int32 InstanceIdx = 0; InstanceIdx < InstanceCount; ++InstanceIdx) 887 | // { 888 | // if (InstancedRenderData.Component->PerInstanceSMData.IsValidIndex(InstanceIdx)) 889 | // { 890 | // const FIBMInstanceData& InstanceData = InstancedRenderData.Component->PerInstanceSMData[InstanceIdx]; 891 | // FMatrix ComponentLocalToWorld = InstancedRenderData.Component->GetComponentTransform().ToMatrixWithScale(); 892 | // FMatrix InstanceTransform = InstanceData.Transform * ComponentLocalToWorld; 893 | 894 | // RayTracingInstanceTemplate.InstanceTransforms.Add(InstanceTransform); 895 | // } 896 | // } 897 | //} 898 | 899 | //OutRayTracingInstances.Add(RayTracingInstanceTemplate); 900 | } 901 | 902 | void FInstanceBufferMeshSceneProxy::SetupRayTracingCullClusters() 903 | { 904 | ////#dxr_todo: select the appropriate LOD depending on Context.View 905 | //int32 LOD = 0; 906 | //if (RenderData->LODResources.Num() > LOD && RenderData->LODResources[LOD].RayTracingGeometry.IsInitialized()) 907 | //{ 908 | // const float MaxClusterRadiusMultiplier = CVarRayTracingInstancesCullClusterMaxRadiusMultiplier.GetValueOnAnyThread(); 909 | // const int32 Batches = GetNumMeshBatches(); 910 | // const int32 InstanceCount = InstancedRenderData.Component->PerInstanceSMData.Num(); 911 | // int32 ClusterIndex = 0; 912 | // FMatrix ComponentLocalToWorld = InstancedRenderData.Component->GetComponentTransform().ToMatrixWithScale(); 913 | // float MaxInstanceRadius = 0.0f; 914 | 915 | // // Init first cluster 916 | // RayTracingCullClusterInstances.Add(new TDoubleLinkedList< uint32>()); 917 | // RayTracingCullClusterBoundsMin.Add(FVector(MAX_FLT, MAX_FLT, MAX_FLT)); 918 | // RayTracingCullClusterBoundsMax.Add(FVector(-MAX_FLT, -MAX_FLT, -MAX_FLT)); 919 | 920 | // // Traverse instances to find maximum rarius 921 | // for (int32 Instance = 0; Instance < InstanceCount; ++Instance) 922 | // { 923 | // if (InstancedRenderData.Component->PerInstanceSMData.IsValidIndex(Instance)) 924 | // { 925 | // const FIBMInstanceData& InstanceData = InstancedRenderData.Component->PerInstanceSMData[Instance]; 926 | // FMatrix InstanceTransform = InstanceData.Transform * ComponentLocalToWorld; 927 | // FVector VMin, VMax; 928 | 929 | // InstancedRenderData.Component->GetLocalBounds(VMin, VMax); 930 | // VMin = InstanceTransform.TransformPosition(VMin); 931 | // VMax = InstanceTransform.TransformPosition(VMax); 932 | 933 | // FVector VBBoxSize = VMax - VMin; 934 | 935 | // MaxInstanceRadius = FMath::Max(0.5f * VBBoxSize.Size(), MaxInstanceRadius); 936 | // } 937 | // } 938 | 939 | // float MaxClusterRadius = MaxInstanceRadius * MaxClusterRadiusMultiplier; 940 | 941 | // // Build clusters 942 | // for (int32 Instance = 0; Instance < InstanceCount; ++Instance) 943 | // { 944 | // if (InstancedRenderData.Component->PerInstanceSMData.IsValidIndex(Instance)) 945 | // { 946 | // const FIBMInstanceData& InstanceData = InstancedRenderData.Component->PerInstanceSMData[Instance]; 947 | // FMatrix InstanceTransform = InstanceData.Transform * ComponentLocalToWorld; 948 | // FVector InstanceLocation = InstanceTransform.TransformPosition({ 0.0f,0.0f,0.0f }); 949 | // FVector VMin = InstanceLocation - FVector(MaxInstanceRadius, MaxInstanceRadius, MaxInstanceRadius); 950 | // FVector VMax = InstanceLocation + FVector(MaxInstanceRadius, MaxInstanceRadius, MaxInstanceRadius); 951 | // bool bClusterFound = false; 952 | 953 | // // Try to find suitable cluster 954 | // for (int32 CandidateCluster = 0; CandidateCluster <= ClusterIndex; ++CandidateCluster) 955 | // { 956 | // // Build new candidate cluster bounds 957 | // FVector VCandidateMin = VMin.ComponentMin(RayTracingCullClusterBoundsMin[CandidateCluster]); 958 | // FVector VCandidateMax = VMax.ComponentMax(RayTracingCullClusterBoundsMax[CandidateCluster]); 959 | 960 | // FVector VCandidateBBoxSize = VCandidateMax - VCandidateMin; 961 | // float MaxCandidateRadius = 0.5f * VCandidateBBoxSize.Size(); 962 | 963 | // // If new candidate is still small enough, update current cluster 964 | // if (MaxCandidateRadius <= MaxClusterRadius) 965 | // { 966 | // RayTracingCullClusterInstances[CandidateCluster]->AddTail(Instance); 967 | // RayTracingCullClusterBoundsMin[CandidateCluster] = VCandidateMin; 968 | // RayTracingCullClusterBoundsMax[CandidateCluster] = VCandidateMax; 969 | // bClusterFound = true; 970 | // break; 971 | // } 972 | // } 973 | 974 | // // if we couldn't add the instance to an existing cluster create a new one 975 | // if (!bClusterFound) 976 | // { 977 | // ++ClusterIndex; 978 | // RayTracingCullClusterInstances.Add(new TDoubleLinkedList< uint32>()); 979 | // RayTracingCullClusterInstances[ClusterIndex]->AddTail(Instance); 980 | // RayTracingCullClusterBoundsMin.Add(VMin); 981 | // RayTracingCullClusterBoundsMax.Add(VMax); 982 | // } 983 | // } 984 | // } 985 | //} 986 | } 987 | 988 | #endif 989 | 990 | 991 | /*----------------------------------------------------------------------------- 992 | UInstanceBufferMeshComponent 993 | -----------------------------------------------------------------------------*/ 994 | 995 | UInstanceBufferMeshComponent::UInstanceBufferMeshComponent(const FObjectInitializer& ObjectInitializer) 996 | : Super(ObjectInitializer) 997 | { 998 | Mobility = EComponentMobility::Movable; 999 | BodyInstance.bSimulatePhysics = false; 1000 | 1001 | bDisallowMeshPaintPerInstance = true; 1002 | } 1003 | 1004 | UInstanceBufferMeshComponent::UInstanceBufferMeshComponent(FVTableHelper& Helper) 1005 | : Super(Helper) 1006 | { 1007 | } 1008 | 1009 | UInstanceBufferMeshComponent::~UInstanceBufferMeshComponent() 1010 | { 1011 | ReleasePerInstanceRenderData(); 1012 | } 1013 | 1014 | TStructOnScope UInstanceBufferMeshComponent::GetComponentInstanceData() const 1015 | { 1016 | TStructOnScope InstanceData; 1017 | 1018 | return InstanceData; 1019 | } 1020 | 1021 | void UInstanceBufferMeshComponent::ApplyComponentInstanceData(FIBMComponentInstanceData* InstancedMeshData) 1022 | { 1023 | 1024 | } 1025 | 1026 | void UInstanceBufferMeshComponent::SetNumInstances(int numInstances) 1027 | { 1028 | check(numInstances > 0); 1029 | 1030 | if (numInstances == _numInstances) 1031 | return; 1032 | 1033 | // this will trigger a rebuild of the scene proxy 1034 | 1035 | _numInstances = numInstances; 1036 | 1037 | // Force recreation of the render data 1038 | MarkRenderStateDirty(); 1039 | } 1040 | 1041 | int32 UInstanceBufferMeshComponent::GetNumInstancesCurrentlyAllocated() const 1042 | { 1043 | return PerInstanceRenderData->InstanceBuffer.GetNumInstances(); 1044 | } 1045 | 1046 | 1047 | 1048 | FPrimitiveSceneProxy* UInstanceBufferMeshComponent::CreateSceneProxy() 1049 | { 1050 | LLM_SCOPE(ELLMTag::InstancedMesh); 1051 | ProxySize = 0; 1052 | 1053 | // Verify that the mesh is valid before using it. 1054 | const bool bMeshIsValid = 1055 | // make sure we have instances 1056 | _numInstances > 0 && 1057 | // make sure we have an actual staticmesh 1058 | GetStaticMesh() && 1059 | GetStaticMesh()->HasValidRenderData() && 1060 | // You really can't use hardware instancing on the consoles with multiple elements because they share the same index buffer. 1061 | // @todo: Level error or something to let LDs know this 1062 | 1;//GetStaticMesh()->LODModels(0).Elements.Num() == 1; 1063 | 1064 | if(bMeshIsValid) 1065 | { 1066 | check(InstancingRandomSeed != 0); 1067 | 1068 | CreateHitProxyData(PerInstanceRenderData->HitProxies); 1069 | 1070 | PerInstanceRenderData->UpdateWithNumInstance(_numInstances); 1071 | 1072 | ProxySize = PerInstanceRenderData->ResourceSize; 1073 | return ::new FInstanceBufferMeshSceneProxy(this, GetWorld()->FeatureLevel); 1074 | } 1075 | else 1076 | { 1077 | return NULL; 1078 | } 1079 | } 1080 | 1081 | void UInstanceBufferMeshComponent::CreateHitProxyData(TArray>& HitProxies) 1082 | { 1083 | if (GIsEditor && bHasPerInstanceHitProxies) 1084 | { 1085 | //QUICK_SCOPE_CYCLE_COUNTER(STAT_UInstancedStaticMeshComponent_CreateHitProxyData); 1086 | // 1087 | //int32 NumProxies = PerInstanceSMData.Num(); 1088 | //HitProxies.Empty(NumProxies); 1089 | 1090 | //for (int32 InstanceIdx = 0; InstanceIdx < NumProxies; ++InstanceIdx) 1091 | //{ 1092 | // HitProxies.Add(new HInstanceBufferMeshInstance(this, InstanceIdx)); 1093 | //} 1094 | } 1095 | else 1096 | { 1097 | HitProxies.Empty(); 1098 | } 1099 | } 1100 | 1101 | void UInstanceBufferMeshComponent::BuildRenderData(TArray>& OutHitProxies) 1102 | { 1103 | LLM_SCOPE(ELLMTag::InstancedMesh); 1104 | QUICK_SCOPE_CYCLE_COUNTER(STAT_UInstancedStaticMeshComponent_BuildRenderData); 1105 | 1106 | CreateHitProxyData(OutHitProxies); 1107 | } 1108 | 1109 | bool UInstanceBufferMeshComponent::CanEditSimulatePhysics() 1110 | { 1111 | return false; 1112 | } 1113 | 1114 | FBoxSphereBounds UInstanceBufferMeshComponent::CalcBounds(const FTransform& BoundTransform) const 1115 | { 1116 | return FBoxSphereBounds(BoundTransform.GetLocation(), FVector(500000.f), 1000000.f); 1117 | 1118 | //if(GetStaticMesh() && PerInstanceSMData.Num() > 0) 1119 | //{ 1120 | // FMatrix BoundTransformMatrix = BoundTransform.ToMatrixWithScale(); 1121 | 1122 | // FBoxSphereBounds RenderBounds = GetStaticMesh()->GetBounds(); 1123 | // FBoxSphereBounds NewBounds = RenderBounds.TransformBy(PerInstanceSMData[0].Transform * BoundTransformMatrix); 1124 | 1125 | // for (int32 InstanceIndex = 1; InstanceIndex < PerInstanceSMData.Num(); InstanceIndex++) 1126 | // { 1127 | // NewBounds = NewBounds + RenderBounds.TransformBy(PerInstanceSMData[InstanceIndex].Transform * BoundTransformMatrix); 1128 | // } 1129 | 1130 | // return NewBounds; 1131 | //} 1132 | //else 1133 | //{ 1134 | // return FBoxSphereBounds(BoundTransform.GetLocation(), FVector::ZeroVector, 0.f); 1135 | //} 1136 | } 1137 | 1138 | #if WITH_EDITOR 1139 | void UInstanceBufferMeshComponent::GetStaticLightingInfo(FStaticLightingPrimitiveInfo& OutPrimitiveInfo, const TArray& InRelevantLights, const FLightingBuildOptions& Options) 1140 | { 1141 | // Tim: There are more classes we have to port to support this. We don't bake our lighting anyways. 1142 | 1143 | //if (HasValidSettingsForStaticLighting(false)) 1144 | //{ 1145 | // // create static lighting for LOD 0 1146 | // int32 LightMapWidth = 0; 1147 | // int32 LightMapHeight = 0; 1148 | // GetLightMapResolution(LightMapWidth, LightMapHeight); 1149 | 1150 | // bool bFit = false; 1151 | // bool bReduced = false; 1152 | // while (1) 1153 | // { 1154 | // const int32 OneLessThanMaximumSupportedResolution = 1 << (GMaxTextureMipCount - 2); 1155 | 1156 | // const int32 MaxInstancesInMaxSizeLightmap = (OneLessThanMaximumSupportedResolution / LightMapWidth) * ((OneLessThanMaximumSupportedResolution / 2) / LightMapHeight); 1157 | // if (PerInstanceSMData.Num() > MaxInstancesInMaxSizeLightmap) 1158 | // { 1159 | // if (LightMapWidth < 4 || LightMapHeight < 4) 1160 | // { 1161 | // break; 1162 | // } 1163 | // LightMapWidth /= 2; 1164 | // LightMapHeight /= 2; 1165 | // bReduced = true; 1166 | // } 1167 | // else 1168 | // { 1169 | // bFit = true; 1170 | // break; 1171 | // } 1172 | // } 1173 | 1174 | // if (!bFit) 1175 | // { 1176 | // FMessageLog("LightingResults").Message(EMessageSeverity::Error) 1177 | // ->AddToken(FUObjectToken::Create(this)) 1178 | // ->AddToken(FTextToken::Create(NSLOCTEXT("InstancedStaticMesh", "FailedStaticLightingWarning", "The total lightmap size for this InstancedStaticMeshComponent is too big no matter how much we reduce the per-instance size, the number of mesh instances in this component must be reduced"))); 1179 | // return; 1180 | // } 1181 | // if (bReduced) 1182 | // { 1183 | // FMessageLog("LightingResults").Message(EMessageSeverity::Warning) 1184 | // ->AddToken(FUObjectToken::Create(this)) 1185 | // ->AddToken(FTextToken::Create(NSLOCTEXT("InstancedStaticMesh", "ReducedStaticLightingWarning", "The total lightmap size for this InstancedStaticMeshComponent was too big and it was automatically reduced. Consider reducing the component's lightmap resolution or number of mesh instances in this component"))); 1186 | // } 1187 | 1188 | // const int32 LightMapSize = GetWorld()->GetWorldSettings()->PackedLightAndShadowMapTextureSize; 1189 | // const int32 MaxInstancesInDefaultSizeLightmap = (LightMapSize / LightMapWidth) * ((LightMapSize / 2) / LightMapHeight); 1190 | // if (PerInstanceSMData.Num() > MaxInstancesInDefaultSizeLightmap) 1191 | // { 1192 | // FMessageLog("LightingResults").Message(EMessageSeverity::Warning) 1193 | // ->AddToken(FUObjectToken::Create(this)) 1194 | // ->AddToken(FTextToken::Create(NSLOCTEXT("InstancedStaticMesh", "LargeStaticLightingWarning", "The total lightmap size for this InstancedStaticMeshComponent is large, consider reducing the component's lightmap resolution or number of mesh instances in this component"))); 1195 | // } 1196 | 1197 | // // TODO: Support separate static lighting in LODs for instanced meshes. 1198 | 1199 | // if (!GetStaticMesh()->CanLODsShareStaticLighting()) 1200 | // { 1201 | // //TODO: Detect if the UVs for all sub-LODs overlap the base LOD UVs and omit this warning if they do. 1202 | // FMessageLog("LightingResults").Message(EMessageSeverity::Warning) 1203 | // ->AddToken(FUObjectToken::Create(this)) 1204 | // ->AddToken(FTextToken::Create(NSLOCTEXT("InstancedStaticMesh", "UniqueStaticLightingForLODWarning", "Instanced meshes don't yet support unique static lighting for each LOD. Lighting on LOD 1+ may be incorrect unless lightmap UVs are the same for all LODs."))); 1205 | // } 1206 | 1207 | // // Force sharing LOD 0 lightmaps for now. 1208 | // int32 NumLODs = 1; 1209 | 1210 | // CachedMappings.Reset(PerInstanceSMData.Num() * NumLODs); 1211 | // CachedMappings.AddZeroed(PerInstanceSMData.Num() * NumLODs); 1212 | 1213 | // NumPendingLightmaps = 0; 1214 | 1215 | // for (int32 LODIndex = 0; LODIndex < NumLODs; LODIndex++) 1216 | // { 1217 | // const FStaticMeshLODResources& LODRenderData = GetStaticMesh()->RenderData->LODResources[LODIndex]; 1218 | 1219 | // for (int32 InstanceIndex = 0; InstanceIndex < PerInstanceSMData.Num(); InstanceIndex++) 1220 | // { 1221 | // auto* StaticLightingMesh = new FStaticLightingMesh_InstancedStaticMesh(this, LODIndex, InstanceIndex, InRelevantLights); 1222 | // OutPrimitiveInfo.Meshes.Add(StaticLightingMesh); 1223 | 1224 | // auto* InstancedMapping = new FStaticLightingTextureMapping_InstanceBufferMesh(this, LODIndex, InstanceIndex, StaticLightingMesh, LightMapWidth, LightMapHeight, GetStaticMesh()->LightMapCoordinateIndex, true); 1225 | // OutPrimitiveInfo.Mappings.Add(InstancedMapping); 1226 | 1227 | // CachedMappings[LODIndex * PerInstanceSMData.Num() + InstanceIndex].Mapping = InstancedMapping; 1228 | // NumPendingLightmaps++; 1229 | // } 1230 | 1231 | // // Shrink LOD texture lightmaps by half for each LOD level (minimum 4x4 px) 1232 | // LightMapWidth = FMath::Max(LightMapWidth / 2, 4); 1233 | // LightMapHeight = FMath::Max(LightMapHeight / 2, 4); 1234 | // } 1235 | //} 1236 | } 1237 | 1238 | void UInstanceBufferMeshComponent::ApplyLightMapping(FStaticLightingTextureMapping_InstanceBufferMesh* InMapping, ULevel* LightingScenario) 1239 | { 1240 | // Tim: There are more classes we have to port to support this. We don't bake our lighting anyways. 1241 | 1242 | //static const auto CVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.VirtualTexturedLightmaps")); 1243 | //const bool bUseVirtualTextures = (CVar->GetValueOnAnyThread() != 0) && UseVirtualTexturing(GMaxRHIFeatureLevel); 1244 | 1245 | //NumPendingLightmaps--; 1246 | 1247 | //if (NumPendingLightmaps == 0) 1248 | //{ 1249 | // // Calculate the range of each coefficient in this light-map and repack the data to have the same scale factor and bias across all instances 1250 | // // TODO: Per instance scale? 1251 | 1252 | // // generate the final lightmaps for all the mappings for this component 1253 | // TArray> AllQuantizedData; 1254 | // for (auto& MappingInfo : CachedMappings) 1255 | // { 1256 | // FStaticLightingTextureMapping_InstanceBufferMesh* Mapping = MappingInfo.Mapping; 1257 | // AllQuantizedData.Add(MoveTemp(Mapping->QuantizedData)); 1258 | // } 1259 | 1260 | // bool bNeedsShadowMap = false; 1261 | // TArray>> AllShadowMapData; 1262 | // for (auto& MappingInfo : CachedMappings) 1263 | // { 1264 | // FStaticLightingTextureMapping_InstanceBufferMesh* Mapping = MappingInfo.Mapping; 1265 | // bNeedsShadowMap = bNeedsShadowMap || (Mapping->ShadowMapData.Num() > 0); 1266 | // AllShadowMapData.Add(MoveTemp(Mapping->ShadowMapData)); 1267 | // } 1268 | 1269 | // UStaticMesh* ResolvedMesh = GetStaticMesh(); 1270 | // if (LODData.Num() != ResolvedMesh->GetNumLODs()) 1271 | // { 1272 | // MarkPackageDirty(); 1273 | // } 1274 | 1275 | // // Ensure LODData has enough entries in it, free not required. 1276 | // SetLODDataCount(ResolvedMesh->GetNumLODs(), ResolvedMesh->GetNumLODs()); 1277 | 1278 | // ULevel* StorageLevel = LightingScenario ? LightingScenario : GetOwner()->GetLevel(); 1279 | // UMapBuildDataRegistry* Registry = StorageLevel->GetOrCreateMapBuildData(); 1280 | // FMeshMapBuildData& MeshBuildData = Registry->AllocateMeshBuildData(LODData[0].MapBuildDataId, true); 1281 | 1282 | // MeshBuildData.PerInstanceLightmapData.Empty(AllQuantizedData.Num()); 1283 | // MeshBuildData.PerInstanceLightmapData.AddZeroed(AllQuantizedData.Num()); 1284 | 1285 | // // Create a light-map for the primitive. 1286 | // // When using VT, shadow map data is included with lightmap allocation 1287 | // const ELightMapPaddingType PaddingType = GAllowLightmapPadding ? LMPT_NormalPadding : LMPT_NoPadding; 1288 | // TArray>> EmptyShadowMapData; 1289 | // TRefCountPtr NewLightMap = FLightMap2D::AllocateInstancedLightMap(Registry, this, 1290 | // MoveTemp(AllQuantizedData), 1291 | // bUseVirtualTextures ? MoveTemp(AllShadowMapData) : MoveTemp(EmptyShadowMapData), 1292 | // Registry, LODData[0].MapBuildDataId, Bounds, PaddingType, LMF_Streamed); 1293 | 1294 | // // Create a shadow-map for the primitive, only needed when not using VT 1295 | // TRefCountPtr NewShadowMap = (bNeedsShadowMap && !bUseVirtualTextures) 1296 | // ? FShadowMap2D::AllocateInstancedShadowMap(Registry, this, MoveTemp(AllShadowMapData), Registry, LODData[0].MapBuildDataId, Bounds, PaddingType, SMF_Streamed) 1297 | // : nullptr; 1298 | 1299 | // MeshBuildData.LightMap = NewLightMap; 1300 | // MeshBuildData.ShadowMap = NewShadowMap; 1301 | 1302 | // // Build the list of statically irrelevant lights. 1303 | // // TODO: This should be stored per LOD. 1304 | // TSet RelevantLights; 1305 | // TSet PossiblyIrrelevantLights; 1306 | // for (auto& MappingInfo : CachedMappings) 1307 | // { 1308 | // for (const ULightComponent* Light : MappingInfo.Mapping->Mesh->RelevantLights) 1309 | // { 1310 | // // Check if the light is stored in the light-map. 1311 | // const bool bIsInLightMap = MeshBuildData.LightMap && MeshBuildData.LightMap->LightGuids.Contains(Light->LightGuid); 1312 | 1313 | // // Check if the light is stored in the shadow-map. 1314 | // const bool bIsInShadowMap = MeshBuildData.ShadowMap && MeshBuildData.ShadowMap->LightGuids.Contains(Light->LightGuid); 1315 | 1316 | // // If the light isn't already relevant to another mapping, add it to the potentially irrelevant list 1317 | // if (!bIsInLightMap && !bIsInShadowMap && !RelevantLights.Contains(Light->LightGuid)) 1318 | // { 1319 | // PossiblyIrrelevantLights.Add(Light->LightGuid); 1320 | // } 1321 | 1322 | // // Light is relevant 1323 | // if (bIsInLightMap || bIsInShadowMap) 1324 | // { 1325 | // RelevantLights.Add(Light->LightGuid); 1326 | // PossiblyIrrelevantLights.Remove(Light->LightGuid); 1327 | // } 1328 | // } 1329 | // } 1330 | 1331 | // MeshBuildData.IrrelevantLights = PossiblyIrrelevantLights.Array(); 1332 | 1333 | // // Force recreation of the render data 1334 | // InstanceUpdateCmdBuffer.Edit(); 1335 | // MarkRenderStateDirty(); 1336 | //} 1337 | } 1338 | #endif 1339 | 1340 | void UInstanceBufferMeshComponent::ReleasePerInstanceRenderData() 1341 | { 1342 | if (PerInstanceRenderData.IsValid()) 1343 | { 1344 | typedef TSharedPtr FPerInstanceRenderDataPtr; 1345 | 1346 | PerInstanceRenderData->HitProxies.Empty(); 1347 | 1348 | // Make shared pointer object on the heap 1349 | FPerInstanceRenderDataPtr* CleanupRenderDataPtr = new FPerInstanceRenderDataPtr(PerInstanceRenderData); 1350 | PerInstanceRenderData.Reset(); 1351 | 1352 | FPerInstanceRenderDataPtr* InCleanupRenderDataPtr = CleanupRenderDataPtr; 1353 | ENQUEUE_RENDER_COMMAND(FReleasePerInstanceRenderData)( 1354 | [InCleanupRenderDataPtr](FRHICommandList& RHICmdList) 1355 | { 1356 | // Destroy the shared pointer object we allocated on the heap. 1357 | // Resource will either be released here or by scene proxy on the render thread, whoever gets executed last 1358 | delete InCleanupRenderDataPtr; 1359 | }); 1360 | } //-V773 1361 | } 1362 | 1363 | void UInstanceBufferMeshComponent::PropagateLightingScenarioChange() 1364 | { 1365 | FComponentRecreateRenderStateContext Context(this); 1366 | 1367 | // Force recreation of the render data 1368 | MarkRenderStateDirty(); 1369 | } 1370 | 1371 | void UInstanceBufferMeshComponent::GetLightAndShadowMapMemoryUsage( int32& LightMapMemoryUsage, int32& ShadowMapMemoryUsage ) const 1372 | { 1373 | Super::GetLightAndShadowMapMemoryUsage(LightMapMemoryUsage, ShadowMapMemoryUsage); 1374 | 1375 | int32 NumInstances = GetNumInstancesCurrentlyAllocated(); 1376 | 1377 | // Scale lighting demo by number of instances 1378 | LightMapMemoryUsage *= NumInstances; 1379 | ShadowMapMemoryUsage *= NumInstances; 1380 | } 1381 | 1382 | static bool NeedRenderDataForTargetPlatform(const ITargetPlatform* TargetPlatform) 1383 | { 1384 | #if WITH_EDITOR 1385 | const UDeviceProfile* DeviceProfile = UDeviceProfileManager::Get().FindProfile(TargetPlatform->IniPlatformName()); 1386 | if (DeviceProfile) 1387 | { 1388 | int32 CVarFoliageSaveRenderData = 1; 1389 | if (DeviceProfile->GetConsolidatedCVarValue(TEXT("foliage.SaveRenderData"), CVarFoliageSaveRenderData)) 1390 | { 1391 | return CVarFoliageSaveRenderData != 0; 1392 | } 1393 | } 1394 | #endif // WITH_EDITOR 1395 | return true; 1396 | } 1397 | 1398 | void UInstanceBufferMeshComponent::SerializeRenderData(FArchive& Ar) 1399 | { 1400 | 1401 | } 1402 | 1403 | void UInstanceBufferMeshComponent::Serialize(FArchive& Ar) 1404 | { 1405 | LLM_SCOPE(ELLMTag::InstancedMesh); 1406 | Super::Serialize(Ar); 1407 | 1408 | Ar.UsingCustomVersion(FMobileObjectVersion::GUID); 1409 | Ar.UsingCustomVersion(FFortniteMainBranchObjectVersion::GUID); 1410 | Ar.UsingCustomVersion(FEditorObjectVersion::GUID); 1411 | 1412 | bool bCooked = Ar.IsCooking(); 1413 | if (Ar.CustomVer(FFortniteMainBranchObjectVersion::GUID) >= FFortniteMainBranchObjectVersion::SerializeInstancedStaticMeshRenderData || Ar.CustomVer(FEditorObjectVersion::GUID) >= FEditorObjectVersion::SerializeInstancedStaticMeshRenderData) 1414 | { 1415 | Ar << bCooked; 1416 | } 1417 | 1418 | if (bCooked && (Ar.CustomVer(FFortniteMainBranchObjectVersion::GUID) >= FFortniteMainBranchObjectVersion::SerializeInstancedStaticMeshRenderData || Ar.CustomVer(FEditorObjectVersion::GUID) >= FEditorObjectVersion::SerializeInstancedStaticMeshRenderData)) 1419 | { 1420 | SerializeRenderData(Ar); 1421 | } 1422 | 1423 | } 1424 | 1425 | void UInstanceBufferMeshComponent::OnUpdateTransform(EUpdateTransformFlags UpdateTransformFlags, ETeleportType Teleport) 1426 | { 1427 | // We are handling the physics move below, so don't handle it at higher levels 1428 | Super::OnUpdateTransform(UpdateTransformFlags | EUpdateTransformFlags::SkipPhysicsUpdate, Teleport); 1429 | } 1430 | 1431 | bool UInstanceBufferMeshComponent::ShouldCreatePhysicsState() const 1432 | { 1433 | return false; 1434 | } 1435 | 1436 | float UInstanceBufferMeshComponent::GetTextureStreamingTransformScale() const 1437 | { 1438 | // By default if there are no per instance data, use a scale of 1. 1439 | // This is required because some derived class use the instancing system without filling the per instance data. (like landscape grass) 1440 | // In those cases, we assume the instance are spreaded across the bounds with a scale of 1. 1441 | float TransformScale = 1.f; 1442 | 1443 | //if (PerInstanceSMData.Num() > 0) 1444 | //{ 1445 | // TransformScale = Super::GetTextureStreamingTransformScale(); 1446 | 1447 | // float WeightedAxisScaleSum = 0; 1448 | // float WeightSum = 0; 1449 | 1450 | // for (int32 InstanceIndex = 0; InstanceIndex < PerInstanceSMData.Num(); InstanceIndex++) 1451 | // { 1452 | // const float AxisScale = PerInstanceSMData[InstanceIndex].Transform.GetMaximumAxisScale(); 1453 | // const float Weight = AxisScale; // The weight is the axis scale since we want to weight by surface coverage. 1454 | // WeightedAxisScaleSum += AxisScale * Weight; 1455 | // WeightSum += Weight; 1456 | // } 1457 | 1458 | // if (WeightSum > SMALL_NUMBER) 1459 | // { 1460 | // TransformScale *= WeightedAxisScaleSum / WeightSum; 1461 | // } 1462 | //} 1463 | return TransformScale; 1464 | } 1465 | 1466 | bool UInstanceBufferMeshComponent::GetMaterialStreamingData(int32 MaterialIndex, FPrimitiveMaterialInfo& MaterialData) const 1467 | { 1468 | // Same thing as StaticMesh but we take the full bounds to cover the instances. 1469 | if (GetStaticMesh()) 1470 | { 1471 | MaterialData.Material = GetMaterial(MaterialIndex); 1472 | MaterialData.UVChannelData = GetStaticMesh()->GetUVChannelData(MaterialIndex); 1473 | MaterialData.PackedRelativeBox = PackedRelativeBox_Identity; 1474 | } 1475 | return MaterialData.IsValid(); 1476 | } 1477 | 1478 | bool UInstanceBufferMeshComponent::BuildTextureStreamingData(ETextureStreamingBuildType BuildType, EMaterialQualityLevel::Type QualityLevel, ERHIFeatureLevel::Type FeatureLevel, TSet& DependentResources) 1479 | { 1480 | #if WITH_EDITORONLY_DATA // Only rebuild the data in editor 1481 | if (GetInstanceCount() > 0) 1482 | { 1483 | return Super::BuildTextureStreamingData(BuildType, QualityLevel, FeatureLevel, DependentResources); 1484 | } 1485 | #endif 1486 | return true; 1487 | } 1488 | 1489 | void UInstanceBufferMeshComponent::GetStreamingRenderAssetInfo(FStreamingTextureLevelContext& LevelContext, TArray& OutStreamingRenderAssets) const 1490 | { 1491 | // Don't only look the instance count but also if the bound is valid, as derived classes might not set PerInstanceSMData. 1492 | if (GetInstanceCount() > 0 || Bounds.SphereRadius > 0) 1493 | { 1494 | return Super::GetStreamingRenderAssetInfo(LevelContext, OutStreamingRenderAssets); 1495 | } 1496 | } 1497 | 1498 | 1499 | 1500 | int32 UInstanceBufferMeshComponent::GetInstanceCount() const 1501 | { 1502 | return GetNumInstancesCurrentlyAllocated(); 1503 | } 1504 | 1505 | void UInstanceBufferMeshComponent::SetCullDistances(int32 StartCullDistance, int32 EndCullDistance) 1506 | { 1507 | InstanceStartCullDistance = StartCullDistance; 1508 | InstanceEndCullDistance = EndCullDistance; 1509 | MarkRenderStateDirty(); 1510 | } 1511 | 1512 | 1513 | static bool ComponentRequestsCPUAccess(UInstanceBufferMeshComponent* InComponent, ERHIFeatureLevel::Type FeatureLevel) 1514 | { 1515 | if (FeatureLevel > ERHIFeatureLevel::ES3_1) 1516 | { 1517 | static const auto CVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.GenerateMeshDistanceFields")); 1518 | 1519 | const bool bNeedsCPUAccess = (InComponent->CastShadow && InComponent->bAffectDistanceFieldLighting 1520 | // Distance field algorithms need access to instance data on the CPU 1521 | && (CVar->GetValueOnAnyThread(true) != 0 || (InComponent->GetStaticMesh() && InComponent->GetStaticMesh()->bGenerateMeshDistanceField))); 1522 | 1523 | return bNeedsCPUAccess; 1524 | } 1525 | return false; 1526 | } 1527 | 1528 | void UInstanceBufferMeshComponent::GetInstancesMinMaxScale(FVector& MinScale, FVector& MaxScale) const 1529 | { 1530 | MinScale = FVector(1.0f); 1531 | MaxScale = FVector(1.0f); 1532 | 1533 | // Tim: We should read this from the GPU 1534 | } 1535 | 1536 | void UInstanceBufferMeshComponent::InitPerInstanceRenderData() 1537 | { 1538 | if (PerInstanceRenderData.IsValid()) 1539 | { 1540 | return; 1541 | } 1542 | 1543 | LLM_SCOPE(ELLMTag::InstancedMesh); 1544 | 1545 | // If we don't have a random seed for this instanced static mesh component yet, then go ahead and 1546 | // generate one now. This will be saved with the static mesh component and used for future generation 1547 | // of random numbers for this component's instances. (Used by the PerInstanceRandom material expression) 1548 | while (InstancingRandomSeed == 0) 1549 | { 1550 | InstancingRandomSeed = FMath::Rand(); 1551 | } 1552 | 1553 | UWorld* World = GetWorld(); 1554 | ERHIFeatureLevel::Type FeatureLevel = World != nullptr ? World->FeatureLevel.GetValue() : GMaxRHIFeatureLevel; 1555 | 1556 | { 1557 | TArray> HitProxies; 1558 | 1559 | CreateHitProxyData(HitProxies); 1560 | 1561 | PerInstanceRenderData = MakeShareable(new FIBMPerInstanceRenderData(FeatureLevel)); 1562 | PerInstanceRenderData->HitProxies = MoveTemp(HitProxies); 1563 | } 1564 | } 1565 | 1566 | void UInstanceBufferMeshComponent::OnComponentCreated() 1567 | { 1568 | Super::OnComponentCreated(); 1569 | 1570 | if (FApp::CanEverRender() && !HasAnyFlags(RF_ClassDefaultObject | RF_ArchetypeObject)) 1571 | { 1572 | // if we are pasting/duplicating this component, it may be created with some instances already in place 1573 | // in this case, need to ensure that the instance render data is properly created 1574 | // We only need to only init from current data if the reorder table == per instance data, but only for the HISM Component, in the case of ISM, the reorder table is never used. 1575 | InitPerInstanceRenderData(); 1576 | } 1577 | } 1578 | 1579 | void UInstanceBufferMeshComponent::PostLoad() 1580 | { 1581 | Super::PostLoad(); 1582 | 1583 | // Has different implementation in HISMC 1584 | OnPostLoadPerInstanceData(); 1585 | } 1586 | 1587 | void UInstanceBufferMeshComponent::OnPostLoadPerInstanceData() 1588 | { 1589 | if (!HasAnyFlags(RF_ClassDefaultObject|RF_ArchetypeObject)) 1590 | { 1591 | // create PerInstanceRenderData and pass InstanceDataBuffers ownership to it 1592 | InitPerInstanceRenderData(); 1593 | } 1594 | } 1595 | 1596 | void UInstanceBufferMeshComponent::GetResourceSizeEx(FResourceSizeEx& CumulativeResourceSize) 1597 | { 1598 | Super::GetResourceSizeEx(CumulativeResourceSize); 1599 | 1600 | if (PerInstanceRenderData.IsValid()) 1601 | { 1602 | CumulativeResourceSize.AddDedicatedSystemMemoryBytes(PerInstanceRenderData->ResourceSize); 1603 | } 1604 | } 1605 | 1606 | void UInstanceBufferMeshComponent::BeginDestroy() 1607 | { 1608 | ReleasePerInstanceRenderData(); 1609 | Super::BeginDestroy(); 1610 | } 1611 | 1612 | void UInstanceBufferMeshComponent::PostDuplicate(bool bDuplicateForPIE) 1613 | { 1614 | Super::PostDuplicate(bDuplicateForPIE); 1615 | 1616 | if (!HasAnyFlags(RF_ClassDefaultObject|RF_ArchetypeObject) && bDuplicateForPIE) 1617 | { 1618 | InitPerInstanceRenderData(); 1619 | } 1620 | } 1621 | 1622 | #if WITH_EDITOR 1623 | void UInstanceBufferMeshComponent::PostEditChangeChainProperty(FPropertyChangedChainEvent& PropertyChangedEvent) 1624 | { 1625 | if(PropertyChangedEvent.Property != NULL) 1626 | { 1627 | if (PropertyChangedEvent.Property->GetFName() == "Transform") 1628 | { 1629 | // Force recreation of the render data 1630 | MarkRenderStateDirty(); 1631 | } 1632 | } 1633 | Super::PostEditChangeChainProperty(PropertyChangedEvent); 1634 | } 1635 | 1636 | void UInstanceBufferMeshComponent::PostEditUndo() 1637 | { 1638 | Super::PostEditUndo(); 1639 | 1640 | FNavigationSystem::UpdateComponentData(*this); 1641 | } 1642 | #endif 1643 | 1644 | bool UInstanceBufferMeshComponent::IsInstanceSelected(int32 InInstanceIndex) const 1645 | { 1646 | #if WITH_EDITOR 1647 | if(SelectedInstances.IsValidIndex(InInstanceIndex)) 1648 | { 1649 | return SelectedInstances[InInstanceIndex]; 1650 | } 1651 | #endif 1652 | 1653 | return false; 1654 | } 1655 | 1656 | void UInstanceBufferMeshComponent::SelectInstance(bool bInSelected, int32 InInstanceIndex, int32 InInstanceCount) 1657 | { 1658 | #if WITH_EDITOR 1659 | 1660 | #endif 1661 | } 1662 | 1663 | void UInstanceBufferMeshComponent::ClearInstanceSelection() 1664 | { 1665 | #if WITH_EDITOR 1666 | 1667 | #endif 1668 | } 1669 | 1670 | static TAutoConsoleVariable CVarCullAllInVertexShader( 1671 | TEXT("foliage.CullAllInVertexShader"), 1672 | 0, 1673 | TEXT("Debugging, if this is greater than 0, cull all instances in the vertex shader.")); 1674 | 1675 | // Copied from from FLocalVertexFactoryShaderParametersBase because it wasn't declared part of the ENGINE_API >: 1676 | void FInstanceBufferMeshVertexFactoryShaderParameters::GetElementShaderBindingsBase(const FSceneInterface* Scene, const FSceneView* View, const FMeshMaterialShader* Shader, const EVertexInputStreamType InputStreamType, ERHIFeatureLevel::Type FeatureLevel, const FVertexFactory* VertexFactory, const FMeshBatchElement& BatchElement, FRHIUniformBuffer* VertexFactoryUniformBuffer, FMeshDrawSingleShaderBindings& ShaderBindings, FVertexInputStreamArray& VertexStreams) const 1677 | { 1678 | const auto* LocalVertexFactory = static_cast(VertexFactory); 1679 | 1680 | if (LocalVertexFactory->SupportsManualVertexFetch(FeatureLevel) || UseGPUScene(GMaxRHIShaderPlatform, FeatureLevel)) 1681 | { 1682 | if (!VertexFactoryUniformBuffer) 1683 | { 1684 | // No batch element override 1685 | VertexFactoryUniformBuffer = LocalVertexFactory->GetUniformBuffer(); 1686 | } 1687 | 1688 | ShaderBindings.Add(Shader->GetUniformBufferParameter(), VertexFactoryUniformBuffer); 1689 | } 1690 | 1691 | //@todo - allow FMeshBatch to supply vertex streams (instead of requiring that they come from the vertex factory), and this userdata hack will no longer be needed for override vertex color 1692 | if (BatchElement.bUserDataIsColorVertexBuffer) 1693 | { 1694 | FColorVertexBuffer* OverrideColorVertexBuffer = (FColorVertexBuffer*)BatchElement.UserData; 1695 | check(OverrideColorVertexBuffer); 1696 | 1697 | if (!LocalVertexFactory->SupportsManualVertexFetch(FeatureLevel)) 1698 | { 1699 | LocalVertexFactory->GetColorOverrideStream(OverrideColorVertexBuffer, VertexStreams); 1700 | } 1701 | } 1702 | 1703 | 1704 | } 1705 | 1706 | void FInstanceBufferMeshVertexFactoryShaderParameters::GetElementShaderBindings( 1707 | const class FSceneInterface* Scene, 1708 | const FSceneView* View, 1709 | const FMeshMaterialShader* Shader, 1710 | const EVertexInputStreamType InputStreamType, 1711 | ERHIFeatureLevel::Type FeatureLevel, 1712 | const FVertexFactory* VertexFactory, 1713 | const FMeshBatchElement& BatchElement, 1714 | FMeshDrawSingleShaderBindings& ShaderBindings, 1715 | FVertexInputStreamArray& VertexStreams 1716 | ) const 1717 | { 1718 | const bool bInstanced = GRHISupportsInstancing; 1719 | 1720 | // Decode VertexFactoryUserData as VertexFactoryUniformBuffer 1721 | FRHIUniformBuffer* VertexFactoryUniformBuffer = static_cast(BatchElement.VertexFactoryUserData); 1722 | FInstanceBufferMeshVertexFactoryShaderParameters::GetElementShaderBindingsBase(Scene, View, Shader, InputStreamType, FeatureLevel, VertexFactory, BatchElement, VertexFactoryUniformBuffer, ShaderBindings, VertexStreams); 1723 | 1724 | const FInstancingUserData* InstancingUserData = (const FInstancingUserData*)BatchElement.UserData; 1725 | const auto* InstancedVertexFactory = static_cast(VertexFactory); 1726 | const int32 InstanceOffsetValue = BatchElement.UserIndex; 1727 | 1728 | if (bInstanced) 1729 | { 1730 | if (InstancedVertexFactory->SupportsManualVertexFetch(FeatureLevel)) 1731 | { 1732 | if (InstancedVertexFactory->GetNumInstances() > 0) 1733 | { 1734 | ShaderBindings.Add(VertexFetch_InstanceOriginBufferParameter, InstancedVertexFactory->GetInstanceOriginSRV()); 1735 | ShaderBindings.Add(VertexFetch_InstanceTransformBufferParameter, InstancedVertexFactory->GetInstanceTransformSRV()); 1736 | ShaderBindings.Add(VertexFetch_InstanceLightmapBufferParameter, InstancedVertexFactory->GetInstanceLightmapSRV()); 1737 | ShaderBindings.Add(InstanceOffset, InstanceOffsetValue); 1738 | } 1739 | else 1740 | { 1741 | ensureMsgf(false, TEXT("Instanced static mesh rendered with no instances. Data initialized: %d"), InstancedVertexFactory->IsDataInitialized()); 1742 | } 1743 | } 1744 | 1745 | if (InstanceOffsetValue > 0 && VertexStreams.Num() > 0) 1746 | { 1747 | VertexFactory->OffsetInstanceStreams(InstanceOffsetValue, InputStreamType, VertexStreams); 1748 | } 1749 | } 1750 | 1751 | 1752 | if( InstancingWorldViewOriginOneParameter.IsBound() ) 1753 | { 1754 | FVector4 InstancingViewZCompareZero(MIN_flt, MIN_flt, MAX_flt, 1.0f); 1755 | FVector4 InstancingViewZCompareOne(MIN_flt, MIN_flt, MAX_flt, 0.0f); 1756 | FVector4 InstancingViewZConstant(ForceInit); 1757 | FVector4 InstancingWorldViewOriginZero(ForceInit); 1758 | FVector4 InstancingWorldViewOriginOne(ForceInit); 1759 | InstancingWorldViewOriginOne.W = 1.0f; 1760 | if (InstancingUserData && BatchElement.InstancedLODRange) 1761 | { 1762 | int32 FirstLOD = InstancingUserData->MinLOD; 1763 | 1764 | int32 DebugMin = FMath::Min(InstanceBuffeStaticMeshNameSpace::CVarMinLOD.GetValueOnRenderThread(), InstancingUserData->MeshRenderData->LODResources.Num() - 1); 1765 | if (DebugMin >= 0) 1766 | { 1767 | FirstLOD = FMath::Max(FirstLOD, DebugMin); 1768 | } 1769 | 1770 | FBoxSphereBounds ScaledBounds = InstancingUserData->MeshRenderData->Bounds.TransformBy(FTransform(FRotator::ZeroRotator, FVector::ZeroVector, InstancingUserData->AverageInstancesScale)); 1771 | float SphereRadius = ScaledBounds.SphereRadius; 1772 | float MinSize = View->ViewMatrices.IsPerspectiveProjection() ? TimHackMinSize : 0.0f; 1773 | float LODScale = TimHackLODScale; 1774 | float LODRandom = TimHackLODRange; 1775 | float MaxDrawDistanceScale = GetCachedScalabilityCVars().ViewDistanceScale; 1776 | 1777 | if (BatchElement.InstancedLODIndex) 1778 | { 1779 | InstancingViewZConstant.X = -1.0f; 1780 | } 1781 | else 1782 | { 1783 | // this is the first LOD, so we don't have a fade-in region 1784 | InstancingViewZConstant.X = 0.0f; 1785 | } 1786 | InstancingViewZConstant.Y = 0.0f; 1787 | InstancingViewZConstant.Z = 1.0f; 1788 | 1789 | // now we subtract off the lower segments, since they will be incorporated 1790 | InstancingViewZConstant.Y -= InstancingViewZConstant.X; 1791 | InstancingViewZConstant.Z -= InstancingViewZConstant.X + InstancingViewZConstant.Y; 1792 | //not using W InstancingViewZConstant.W -= InstancingViewZConstant.X + InstancingViewZConstant.Y + InstancingViewZConstant.Z; 1793 | 1794 | for (int32 SampleIndex = 0; SampleIndex < 2; SampleIndex++) 1795 | { 1796 | FVector4& InstancingViewZCompare(SampleIndex ? InstancingViewZCompareOne : InstancingViewZCompareZero); 1797 | 1798 | float FinalCull = MAX_flt; 1799 | if (MinSize > 0.0) 1800 | { 1801 | FinalCull = ComputeBoundsDrawDistance(MinSize, SphereRadius, View->ViewMatrices.GetProjectionMatrix()) * LODScale; 1802 | } 1803 | if (InstancingUserData->EndCullDistance > 0.0f) 1804 | { 1805 | FinalCull = FMath::Min(FinalCull, InstancingUserData->EndCullDistance * MaxDrawDistanceScale); 1806 | } 1807 | FinalCull *= MaxDrawDistanceScale; 1808 | 1809 | InstancingViewZCompare.Z = FinalCull; 1810 | if (BatchElement.InstancedLODIndex < InstancingUserData->MeshRenderData->LODResources.Num() - 1) 1811 | { 1812 | float NextCut = ComputeBoundsDrawDistance(InstancingUserData->MeshRenderData->ScreenSize[BatchElement.InstancedLODIndex + 1].GetValueForFeatureLevel(FeatureLevel), SphereRadius, View->ViewMatrices.GetProjectionMatrix()) * LODScale; 1813 | InstancingViewZCompare.Z = FMath::Min(NextCut, FinalCull); 1814 | } 1815 | 1816 | InstancingViewZCompare.X = MIN_flt; 1817 | if (BatchElement.InstancedLODIndex > FirstLOD) 1818 | { 1819 | float CurCut = ComputeBoundsDrawDistance(InstancingUserData->MeshRenderData->ScreenSize[BatchElement.InstancedLODIndex].GetValueForFeatureLevel(FeatureLevel), SphereRadius, View->ViewMatrices.GetProjectionMatrix()) * LODScale; 1820 | if (CurCut < FinalCull) 1821 | { 1822 | InstancingViewZCompare.Y = CurCut; 1823 | } 1824 | else 1825 | { 1826 | // this LOD is completely removed by one of the other two factors 1827 | InstancingViewZCompare.Y = MIN_flt; 1828 | InstancingViewZCompare.Z = MIN_flt; 1829 | } 1830 | } 1831 | else 1832 | { 1833 | // this is the first LOD, so we don't have a fade-in region 1834 | InstancingViewZCompare.Y = MIN_flt; 1835 | } 1836 | } 1837 | 1838 | 1839 | InstancingWorldViewOriginZero = View->GetTemporalLODOrigin(0); 1840 | InstancingWorldViewOriginOne = View->GetTemporalLODOrigin(1); 1841 | 1842 | float Alpha = View->GetTemporalLODTransition(); 1843 | InstancingWorldViewOriginZero.W = 1.0f - Alpha; 1844 | InstancingWorldViewOriginOne.W = Alpha; 1845 | 1846 | InstancingViewZCompareZero.W = LODRandom; 1847 | } 1848 | 1849 | ShaderBindings.Add(InstancingViewZCompareZeroParameter, InstancingViewZCompareZero); 1850 | ShaderBindings.Add(InstancingViewZCompareOneParameter, InstancingViewZCompareOne); 1851 | ShaderBindings.Add(InstancingViewZConstantParameter, InstancingViewZConstant); 1852 | ShaderBindings.Add(InstancingWorldViewOriginZeroParameter, InstancingWorldViewOriginZero); 1853 | ShaderBindings.Add(InstancingWorldViewOriginOneParameter, InstancingWorldViewOriginOne); 1854 | } 1855 | 1856 | if( InstancingFadeOutParamsParameter.IsBound() ) 1857 | { 1858 | FVector4 InstancingFadeOutParams(MAX_flt,0.f,1.f,1.f); 1859 | if (InstancingUserData) 1860 | { 1861 | const float MaxDrawDistanceScale = GetCachedScalabilityCVars().ViewDistanceScale; 1862 | const float StartDistance = InstancingUserData->StartCullDistance * MaxDrawDistanceScale; 1863 | const float EndDistance = InstancingUserData->EndCullDistance * MaxDrawDistanceScale; 1864 | 1865 | InstancingFadeOutParams.X = StartDistance; 1866 | if( EndDistance > 0 ) 1867 | { 1868 | if( EndDistance > StartDistance ) 1869 | { 1870 | InstancingFadeOutParams.Y = 1.f / (float)(EndDistance - StartDistance); 1871 | } 1872 | else 1873 | { 1874 | InstancingFadeOutParams.Y = 1.f; 1875 | } 1876 | } 1877 | else 1878 | { 1879 | InstancingFadeOutParams.Y = 0.f; 1880 | } 1881 | if (CVarCullAllInVertexShader.GetValueOnRenderThread() > 0) 1882 | { 1883 | InstancingFadeOutParams.Z = 0.0f; 1884 | InstancingFadeOutParams.W = 0.0f; 1885 | } 1886 | else 1887 | { 1888 | InstancingFadeOutParams.Z = InstancingUserData->bRenderSelected ? 1.f : 0.f; 1889 | InstancingFadeOutParams.W = InstancingUserData->bRenderUnselected ? 1.f : 0.f; 1890 | } 1891 | } 1892 | 1893 | ShaderBindings.Add(InstancingFadeOutParamsParameter, InstancingFadeOutParams); 1894 | 1895 | } 1896 | } 1897 | 1898 | -------------------------------------------------------------------------------- /Source/UnrealGPUSwarm/Private/InstanceBufferMesh.h: -------------------------------------------------------------------------------- 1 | // Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. 2 | 3 | /*============================================================================= 4 | InstancedStaticMesh.h: Instanced static mesh header 5 | =============================================================================*/ 6 | 7 | #pragma once 8 | 9 | #include "CoreMinimal.h" 10 | #include "Containers/IndirectArray.h" 11 | #include "Stats/Stats.h" 12 | #include "HAL/IConsoleManager.h" 13 | #include "RenderingThread.h" 14 | #include "RenderResource.h" 15 | #include "PrimitiveViewRelevance.h" 16 | #include "ShaderParameters.h" 17 | #include "SceneView.h" 18 | #include "VertexFactory.h" 19 | #include "LocalVertexFactory.h" 20 | #include "MaterialShared.h" 21 | #include "Materials/Material.h" 22 | #include "StaticMeshResources.h" 23 | #include "../InstanceBufferMeshComponent.h" 24 | #include "Engine/StaticMesh.h" 25 | 26 | #include "StaticMeshLight.h" 27 | 28 | #if WITH_EDITOR 29 | #include "LightMap.h" 30 | #include "ShadowMap.h" 31 | #endif 32 | 33 | class ULightComponent; 34 | 35 | extern TAutoConsoleVariable CVarFoliageMinimumScreenSize; 36 | extern TAutoConsoleVariable CVarFoliageLODDistanceScale; 37 | extern TAutoConsoleVariable CVarRandomLODRange; 38 | extern TAutoConsoleVariable CVarMinLOD; 39 | 40 | 41 | // This must match the maximum a user could specify in the material (see 42 | // FHLSLMaterialTranslator::TextureCoordinate), otherwise the material will attempt 43 | // to look up a texture coordinate we didn't provide an element for. 44 | extern const int32 InstancedStaticMeshMaxTexCoord; 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | /*----------------------------------------------------------------------------- 73 | FIBMInstanceBuffer 74 | -----------------------------------------------------------------------------*/ 75 | 76 | 77 | /** A vertex buffer of positions. */ 78 | class FIBMInstanceBuffer : public FRenderResource 79 | { 80 | public: 81 | 82 | /** Default constructor. */ 83 | FIBMInstanceBuffer(ERHIFeatureLevel::Type InFeatureLevel); 84 | 85 | /** Destructor. */ 86 | ~FIBMInstanceBuffer(); 87 | 88 | 89 | 90 | UNREALGPUSWARM_API void UpdateWithNumInstances_Concurrent(unsigned int numInstances); 91 | 92 | /** 93 | * Specialized assignment operator, only used when importing LOD's. 94 | */ 95 | void operator=(const FIBMInstanceBuffer &Other); 96 | 97 | // Other accessors. 98 | FORCEINLINE uint32 GetNumInstances() const 99 | { 100 | return _numInstances; 101 | } 102 | 103 | // FRenderResource interface. 104 | virtual void InitRHI() override; 105 | virtual void ReleaseRHI() override; 106 | virtual void InitResource() override; 107 | virtual void ReleaseResource() override; 108 | virtual FString GetFriendlyName() const override { return TEXT("Static-mesh instances"); } 109 | SIZE_T GetResourceSize() const; 110 | 111 | void BindInstanceVertexBuffer(const class FVertexFactory* VertexFactory, struct FInstanceBufferMeshDataType& InstancedStaticMeshData) const; 112 | 113 | protected: 114 | int _numInstances = 0; 115 | 116 | 117 | public: 118 | class FInstanceOriginBuffer : public FVertexBuffer 119 | { 120 | virtual FString GetFriendlyName() const override { return TEXT("FInstanceOriginBuffer"); } 121 | } InstanceOriginBuffer; 122 | FShaderResourceViewRHIRef InstanceOriginSRV; 123 | 124 | class FInstanceTransformBuffer : public FVertexBuffer 125 | { 126 | virtual FString GetFriendlyName() const override { return TEXT("FInstanceTransformBuffer"); } 127 | } InstanceTransformBuffer; 128 | FShaderResourceViewRHIRef InstanceTransformSRV; 129 | 130 | class FInstanceLightmapBuffer : public FVertexBuffer 131 | { 132 | virtual FString GetFriendlyName() const override { return TEXT("FInstanceLightmapBuffer"); } 133 | } InstanceLightmapBuffer; 134 | FShaderResourceViewRHIRef InstanceLightmapSRV; 135 | 136 | /** Delete existing resources */ 137 | void CleanUp(); 138 | 139 | void CreateVertexBuffer(uint32_t sizeInBytes, uint32 InUsage, uint32 InStride, uint8 InFormat, FVertexBufferRHIRef& OutVertexBufferRHI, FShaderResourceViewRHIRef& OutInstanceSRV); 140 | 141 | /** */ 142 | void UpdateWithNumInstances_RenderThread(unsigned int numInstances); 143 | 144 | }; 145 | 146 | /*----------------------------------------------------------------------------- 147 | FInstanceBufferMeshVertexFactory 148 | -----------------------------------------------------------------------------*/ 149 | 150 | struct FInstancingUserData 151 | { 152 | class FInstanceBufferMeshRenderData* RenderData; 153 | class FStaticMeshRenderData* MeshRenderData; 154 | 155 | int32 StartCullDistance; 156 | int32 EndCullDistance; 157 | 158 | int32 MinLOD; 159 | 160 | bool bRenderSelected; 161 | bool bRenderUnselected; 162 | FVector AverageInstancesScale; 163 | }; 164 | 165 | struct FInstanceBufferMeshDataType 166 | { 167 | /** The stream to read the mesh transform from. */ 168 | FVertexStreamComponent InstanceOriginComponent; 169 | 170 | /** The stream to read the mesh transform from. */ 171 | FVertexStreamComponent InstanceTransformComponent[3]; 172 | 173 | /** The stream to read the Lightmap Bias and Random instance ID from. */ 174 | FVertexStreamComponent InstanceLightmapAndShadowMapUVBiasComponent; 175 | 176 | FRHIShaderResourceView* InstanceOriginSRV = nullptr; 177 | FRHIShaderResourceView* InstanceTransformSRV = nullptr; 178 | FRHIShaderResourceView* InstanceLightmapSRV = nullptr; 179 | 180 | /** Used to track state for debugging. */ 181 | uint32 NumInstances = 0; 182 | bool bInitialized = false; 183 | }; 184 | 185 | /** 186 | * A vertex factory for instanced static meshes 187 | */ 188 | struct FInstanceBufferMeshVertexFactory : public FLocalVertexFactory 189 | { 190 | DECLARE_VERTEX_FACTORY_TYPE(FInstanceBufferMeshVertexFactory); 191 | public: 192 | FInstanceBufferMeshVertexFactory(ERHIFeatureLevel::Type InFeatureLevel) 193 | : FLocalVertexFactory(InFeatureLevel, "FInstanceBufferMeshVertexFactory") 194 | { 195 | } 196 | 197 | struct FDataType : public FInstanceBufferMeshDataType, public FLocalVertexFactory::FDataType 198 | { 199 | }; 200 | 201 | /** 202 | * Should we cache the material's shadertype on this platform with this vertex factory? 203 | */ 204 | static bool ShouldCompilePermutation(EShaderPlatform Platform, const class FMaterial* Material, const class FShaderType* ShaderType); 205 | 206 | /** 207 | * Modify compile environment to enable instancing 208 | * @param OutEnvironment - shader compile environment to modify 209 | */ 210 | static void ModifyCompilationEnvironment(const FVertexFactoryType* Type, EShaderPlatform Platform, const FMaterial* Material, FShaderCompilerEnvironment& OutEnvironment) 211 | { 212 | const bool ContainsManualVertexFetch = OutEnvironment.GetDefinitions().Contains("MANUAL_VERTEX_FETCH"); 213 | if (!ContainsManualVertexFetch && RHISupportsManualVertexFetch(Platform)) 214 | { 215 | OutEnvironment.SetDefine(TEXT("MANUAL_VERTEX_FETCH"), TEXT("1")); 216 | } 217 | 218 | OutEnvironment.SetDefine(TEXT("USE_INSTANCING"),TEXT("1")); 219 | if (IsFeatureLevelSupported(Platform, ERHIFeatureLevel::SM5)) 220 | { 221 | OutEnvironment.SetDefine(TEXT("USE_DITHERED_LOD_TRANSITION_FOR_INSTANCED"), ALLOW_DITHERED_LOD_FOR_INSTANCED_STATIC_MESHES); 222 | } 223 | else 224 | { 225 | // On mobile dithered LOD transition has to be explicitly enabled in material and project settings 226 | OutEnvironment.SetDefine(TEXT("USE_DITHERED_LOD_TRANSITION_FOR_INSTANCED"), Material->IsDitheredLODTransition() && ALLOW_DITHERED_LOD_FOR_INSTANCED_STATIC_MESHES); 227 | } 228 | 229 | FLocalVertexFactory::ModifyCompilationEnvironment(Type, Platform, Material, OutEnvironment); 230 | } 231 | 232 | /** 233 | * An implementation of the interface used by TSynchronizedResource to update the resource with new data from the game thread. 234 | */ 235 | void SetData(const FDataType& InData) 236 | { 237 | FLocalVertexFactory::Data = InData; 238 | Data = InData; 239 | UpdateRHI(); 240 | } 241 | 242 | /** 243 | * Copy the data from another vertex factory 244 | * @param Other - factory to copy from 245 | */ 246 | void Copy(const FInstanceBufferMeshVertexFactory& Other); 247 | 248 | // FRenderResource interface. 249 | virtual void InitRHI() override; 250 | 251 | static FVertexFactoryShaderParameters* ConstructShaderParameters(EShaderFrequency ShaderFrequency); 252 | 253 | /** Make sure we account for changes in the signature of GetStaticBatchElementVisibility() */ 254 | static CONSTEXPR uint32 NumBitsForVisibilityMask() 255 | { 256 | return 8 * sizeof(decltype(((FInstanceBufferMeshVertexFactory*)nullptr)->GetStaticBatchElementVisibility(FSceneView(FSceneViewInitOptions()), nullptr))); 257 | } 258 | 259 | /** 260 | * Get a bitmask representing the visibility of each FMeshBatch element. 261 | */ 262 | virtual uint64 GetStaticBatchElementVisibility(const class FSceneView& View, const struct FMeshBatch* Batch, const void* ViewCustomData = nullptr) const override 263 | { 264 | const uint32 NumBits = NumBitsForVisibilityMask(); 265 | const uint32 NumElements = FMath::Min((uint32)Batch->Elements.Num(), NumBits); 266 | return NumElements == NumBits ? ~0ULL : (1ULL << (uint64)NumElements) - 1ULL; 267 | } 268 | #if ALLOW_DITHERED_LOD_FOR_INSTANCED_STATIC_MESHES 269 | virtual bool SupportsNullPixelShader() const override { return false; } 270 | #endif 271 | 272 | inline bool IsDataInitialized() const 273 | { 274 | return Data.bInitialized; 275 | } 276 | 277 | inline uint32 GetNumInstances() const 278 | { 279 | return Data.NumInstances; 280 | } 281 | 282 | inline FRHIShaderResourceView* GetInstanceOriginSRV() const 283 | { 284 | return Data.InstanceOriginSRV; 285 | } 286 | 287 | inline FRHIShaderResourceView* GetInstanceTransformSRV() const 288 | { 289 | return Data.InstanceTransformSRV; 290 | } 291 | 292 | inline FRHIShaderResourceView* GetInstanceLightmapSRV() const 293 | { 294 | return Data.InstanceLightmapSRV; 295 | } 296 | 297 | private: 298 | FDataType Data; 299 | }; 300 | 301 | class FInstanceBufferMeshVertexFactoryShaderParameters : public FVertexFactoryShaderParameters 302 | { 303 | virtual void Bind(const FShaderParameterMap& ParameterMap) override 304 | { 305 | InstancingFadeOutParamsParameter.Bind(ParameterMap, TEXT("InstancingFadeOutParams")); 306 | InstancingViewZCompareZeroParameter.Bind(ParameterMap, TEXT("InstancingViewZCompareZero")); 307 | InstancingViewZCompareOneParameter.Bind(ParameterMap, TEXT("InstancingViewZCompareOne")); 308 | InstancingViewZConstantParameter.Bind(ParameterMap, TEXT("InstancingViewZConstant")); 309 | InstancingWorldViewOriginZeroParameter.Bind(ParameterMap, TEXT("InstancingWorldViewOriginZero")); 310 | InstancingWorldViewOriginOneParameter.Bind(ParameterMap, TEXT("InstancingWorldViewOriginOne")); 311 | CPUInstanceOrigin.Bind(ParameterMap, TEXT("CPUInstanceOrigin")); 312 | CPUInstanceTransform.Bind(ParameterMap, TEXT("CPUInstanceTransform")); 313 | CPUInstanceLightmapAndShadowMapBias.Bind(ParameterMap, TEXT("CPUInstanceLightmapAndShadowMapBias")); 314 | VertexFetch_InstanceOriginBufferParameter.Bind(ParameterMap, TEXT("VertexFetch_InstanceOriginBuffer")); 315 | VertexFetch_InstanceTransformBufferParameter.Bind(ParameterMap, TEXT("VertexFetch_InstanceTransformBuffer")); 316 | VertexFetch_InstanceLightmapBufferParameter.Bind(ParameterMap, TEXT("VertexFetch_InstanceLightmapBuffer")); 317 | InstanceOffset.Bind(ParameterMap, TEXT("InstanceOffset")); 318 | } 319 | 320 | void GetElementShaderBindingsBase( 321 | const FSceneInterface* Scene, 322 | const FSceneView* View, 323 | const FMeshMaterialShader* Shader, 324 | const EVertexInputStreamType InputStreamType, 325 | ERHIFeatureLevel::Type FeatureLevel, 326 | const FVertexFactory* VertexFactory, 327 | const FMeshBatchElement& BatchElement, 328 | FRHIUniformBuffer* VertexFactoryUniformBuffer, 329 | FMeshDrawSingleShaderBindings& ShaderBindings, 330 | FVertexInputStreamArray& VertexStreams 331 | ) const; 332 | 333 | virtual void GetElementShaderBindings( 334 | const class FSceneInterface* Scene, 335 | const FSceneView* View, 336 | const FMeshMaterialShader* Shader, 337 | const EVertexInputStreamType InputStreamType, 338 | ERHIFeatureLevel::Type FeatureLevel, 339 | const FVertexFactory* VertexFactory, 340 | const FMeshBatchElement& BatchElement, 341 | FMeshDrawSingleShaderBindings& ShaderBindings, 342 | FVertexInputStreamArray& VertexStreams 343 | ) const override; 344 | 345 | void Serialize(FArchive& Ar) override 346 | { 347 | Ar << InstancingFadeOutParamsParameter; 348 | Ar << InstancingViewZCompareZeroParameter; 349 | Ar << InstancingViewZCompareOneParameter; 350 | Ar << InstancingViewZConstantParameter; 351 | Ar << InstancingWorldViewOriginZeroParameter; 352 | Ar << InstancingWorldViewOriginOneParameter; 353 | Ar << CPUInstanceOrigin; 354 | Ar << CPUInstanceTransform; 355 | Ar << CPUInstanceLightmapAndShadowMapBias; 356 | Ar << VertexFetch_InstanceOriginBufferParameter; 357 | Ar << VertexFetch_InstanceTransformBufferParameter; 358 | Ar << VertexFetch_InstanceLightmapBufferParameter; 359 | Ar << InstanceOffset; 360 | } 361 | 362 | virtual uint32 GetSize() const override { return sizeof(*this); } 363 | 364 | private: 365 | FShaderParameter InstancingFadeOutParamsParameter; 366 | FShaderParameter InstancingViewZCompareZeroParameter; 367 | FShaderParameter InstancingViewZCompareOneParameter; 368 | FShaderParameter InstancingViewZConstantParameter; 369 | FShaderParameter InstancingWorldViewOriginZeroParameter; 370 | FShaderParameter InstancingWorldViewOriginOneParameter; 371 | 372 | FShaderParameter CPUInstanceOrigin; 373 | FShaderParameter CPUInstanceTransform; 374 | FShaderParameter CPUInstanceLightmapAndShadowMapBias; 375 | 376 | FShaderResourceParameter VertexFetch_InstanceOriginBufferParameter; 377 | FShaderResourceParameter VertexFetch_InstanceTransformBufferParameter; 378 | FShaderResourceParameter VertexFetch_InstanceLightmapBufferParameter; 379 | FShaderParameter InstanceOffset; 380 | }; 381 | 382 | /*----------------------------------------------------------------------------- 383 | FIBMPerInstanceRenderData 384 | Holds render data that can persist between scene proxy reconstruction 385 | -----------------------------------------------------------------------------*/ 386 | struct FIBMPerInstanceRenderData 387 | { 388 | // Should be always constructed on main thread 389 | FIBMPerInstanceRenderData(ERHIFeatureLevel::Type InFeaureLevel); 390 | ~FIBMPerInstanceRenderData(); 391 | 392 | /** 393 | * Call to reallocate the instance buffer to contain numInstances. 394 | * @param numInstances The number of instances stored in the instance buffer. 395 | */ 396 | UNREALGPUSWARM_API void UpdateWithNumInstance(int numInstances); 397 | 398 | 399 | 400 | /** Hit proxies for the instances */ 401 | TArray> HitProxies; 402 | 403 | /** cached per-instance resource size*/ 404 | SIZE_T ResourceSize; 405 | 406 | /** Instance buffer */ 407 | FIBMInstanceBuffer InstanceBuffer; 408 | }; 409 | 410 | 411 | /*----------------------------------------------------------------------------- 412 | FInstanceBufferMeshRenderData 413 | -----------------------------------------------------------------------------*/ 414 | 415 | class FInstanceBufferMeshRenderData 416 | { 417 | public: 418 | 419 | FInstanceBufferMeshRenderData(UInstanceBufferMeshComponent* InComponent, ERHIFeatureLevel::Type InFeatureLevel) 420 | : Component(InComponent) 421 | , PerInstanceRenderData(InComponent->PerInstanceRenderData) 422 | , LODModels(Component->GetStaticMesh()->RenderData->LODResources) 423 | , FeatureLevel(InFeatureLevel) 424 | { 425 | check(PerInstanceRenderData.IsValid()); 426 | // Allocate the vertex factories for each LOD 427 | InitVertexFactories(); 428 | RegisterSpeedTreeWind(); 429 | } 430 | 431 | void ReleaseResources(FSceneInterface* Scene, const UStaticMesh* StaticMesh) 432 | { 433 | // unregister SpeedTree wind with the scene 434 | if (Scene && StaticMesh && StaticMesh->SpeedTreeWind.IsValid()) 435 | { 436 | for (int32 LODIndex = 0; LODIndex < VertexFactories.Num(); LODIndex++) 437 | { 438 | Scene->RemoveSpeedTreeWind_RenderThread(&VertexFactories[LODIndex], StaticMesh); 439 | } 440 | } 441 | 442 | for (int32 LODIndex = 0; LODIndex < VertexFactories.Num(); LODIndex++) 443 | { 444 | VertexFactories[LODIndex].ReleaseResource(); 445 | } 446 | } 447 | 448 | /** Source component */ 449 | UInstanceBufferMeshComponent* Component; 450 | 451 | /** Per instance render data, could be shared with component */ 452 | TSharedPtr PerInstanceRenderData; 453 | 454 | /** Vertex factory */ 455 | TIndirectArray VertexFactories; 456 | 457 | /** LOD render data from the static mesh. */ 458 | TIndirectArray& LODModels; 459 | 460 | /** Feature level used when creating instance data */ 461 | ERHIFeatureLevel::Type FeatureLevel; 462 | 463 | private: 464 | void InitVertexFactories(); 465 | 466 | void RegisterSpeedTreeWind() 467 | { 468 | // register SpeedTree wind with the scene 469 | if (Component->GetStaticMesh()->SpeedTreeWind.IsValid()) 470 | { 471 | for (int32 LODIndex = 0; LODIndex < LODModels.Num(); LODIndex++) 472 | { 473 | Component->GetScene()->AddSpeedTreeWind(&VertexFactories[LODIndex], Component->GetStaticMesh()); 474 | } 475 | } 476 | } 477 | }; 478 | 479 | 480 | /*----------------------------------------------------------------------------- 481 | FInstanceBufferMeshSceneProxy 482 | -----------------------------------------------------------------------------*/ 483 | 484 | class FInstanceBufferMeshSceneProxy : public FStaticMeshSceneProxy 485 | { 486 | public: 487 | SIZE_T GetTypeHash() const override; 488 | 489 | FInstanceBufferMeshSceneProxy(UInstanceBufferMeshComponent* InComponent, ERHIFeatureLevel::Type InFeatureLevel) 490 | : FStaticMeshSceneProxy(InComponent, true) 491 | , StaticMesh(InComponent->GetStaticMesh()) 492 | , InstancedRenderData(InComponent, InFeatureLevel) 493 | #if WITH_EDITOR 494 | , bHasSelectedInstances(InComponent->SelectedInstances.Num() > 0) 495 | #endif 496 | { 497 | bVFRequiresPrimitiveUniformBuffer = true; 498 | SetupProxy(InComponent); 499 | 500 | #if RHI_RAYTRACING 501 | SetupRayTracingCullClusters(); 502 | #endif 503 | } 504 | 505 | ~FInstanceBufferMeshSceneProxy() 506 | { 507 | InstancedRenderData.ReleaseResources(&GetScene( ), StaticMesh); 508 | 509 | #if RHI_RAYTRACING 510 | for (int32 i = 0; i < RayTracingCullClusterInstances.Num(); ++i) 511 | { 512 | delete RayTracingCullClusterInstances[i]; 513 | } 514 | #endif 515 | } 516 | 517 | // FPrimitiveSceneProxy interface. 518 | 519 | virtual FPrimitiveViewRelevance GetViewRelevance(const FSceneView* View) const override 520 | { 521 | FPrimitiveViewRelevance Result; 522 | if(View->Family->EngineShowFlags.InstancedStaticMeshes) 523 | { 524 | Result = FStaticMeshSceneProxy::GetViewRelevance(View); 525 | #if WITH_EDITOR 526 | // use dynamic path to render selected indices 527 | if( bHasSelectedInstances ) 528 | { 529 | Result.bDynamicRelevance = true; 530 | } 531 | #endif 532 | } 533 | return Result; 534 | } 535 | 536 | #if RHI_RAYTRACING 537 | virtual bool IsRayTracingStaticRelevant() const override 538 | { 539 | return false; 540 | } 541 | 542 | virtual void GetDynamicRayTracingInstances(struct FRayTracingMaterialGatheringContext& Context, TArray& OutRayTracingInstances) final override; 543 | void SetupRayTracingCullClusters(); 544 | 545 | #endif 546 | 547 | virtual void GetLightRelevance(const FLightSceneProxy* LightSceneProxy, bool& bDynamic, bool& bRelevant, bool& bLightMapped, bool& bShadowMapped) const override; 548 | virtual void GetDynamicMeshElements(const TArray& Views, const FSceneViewFamily& ViewFamily, uint32 VisibilityMap, FMeshElementCollector& Collector) const override; 549 | 550 | virtual int32 GetNumMeshBatches() const override; 551 | 552 | /** Sets up a shadow FMeshBatch for a specific LOD. */ 553 | virtual bool GetShadowMeshElement(int32 LODIndex, int32 BatchIndex, uint8 InDepthPriorityGroup, FMeshBatch& OutMeshBatch, bool bDitheredLODTransition) const override; 554 | 555 | /** Sets up a FMeshBatch for a specific LOD and element. */ 556 | virtual bool GetMeshElement(int32 LODIndex, int32 BatchIndex, int32 ElementIndex, uint8 InDepthPriorityGroup, bool bUseSelectionOutline, bool bAllowPreCulledIndices, FMeshBatch& OutMeshBatch) const override; 557 | 558 | /** Sets up a wireframe FMeshBatch for a specific LOD. */ 559 | virtual bool GetWireframeMeshElement(int32 LODIndex, int32 BatchIndex, const FMaterialRenderProxy* WireframeRenderProxy, uint8 InDepthPriorityGroup, bool bAllowPreCulledIndices, FMeshBatch& OutMeshBatch) const override; 560 | 561 | virtual void GetDistancefieldAtlasData(FBox& LocalVolumeBounds, FVector2D& OutDistanceMinMax, FIntVector& OutBlockMin, FIntVector& OutBlockSize, bool& bOutBuiltAsIfTwoSided, bool& bMeshWasPlane, float& SelfShadowBias, TArray& ObjectLocalToWorldTransforms, bool& bOutThrottled) const override; 562 | 563 | virtual void GetDistanceFieldInstanceInfo(int32& NumInstances, float& BoundsSurfaceArea) const override; 564 | 565 | virtual int32 CollectOccluderElements(FOccluderElementsCollector& Collector) const override; 566 | 567 | /** 568 | * Creates the hit proxies are used when DrawDynamicElements is called. 569 | * Called in the game thread. 570 | * @param OutHitProxies - Hit proxes which are created should be added to this array. 571 | * @return The hit proxy to use by default for elements drawn by DrawDynamicElements. 572 | */ 573 | virtual HHitProxy* CreateHitProxies(UPrimitiveComponent* Component,TArray >& OutHitProxies) override; 574 | 575 | virtual bool IsDetailMesh() const override 576 | { 577 | return true; 578 | } 579 | 580 | protected: 581 | /** Cache of the StaticMesh asset, needed to release SpeedTree resources*/ 582 | UStaticMesh* StaticMesh; 583 | 584 | /** Per component render data */ 585 | FInstanceBufferMeshRenderData InstancedRenderData; 586 | 587 | #if WITH_EDITOR 588 | /* If we we have any selected instances */ 589 | bool bHasSelectedInstances; 590 | #else 591 | static const bool bHasSelectedInstances = false; 592 | #endif 593 | 594 | /** LOD transition info. */ 595 | FInstancingUserData UserData_AllInstances; 596 | FInstancingUserData UserData_SelectedInstances; 597 | FInstancingUserData UserData_DeselectedInstances; 598 | 599 | #if RHI_RAYTRACING 600 | TArray< FVector > RayTracingCullClusterBoundsMin; 601 | TArray< FVector > RayTracingCullClusterBoundsMax; 602 | TArray< TDoubleLinkedList< uint32 >* > RayTracingCullClusterInstances; 603 | #endif 604 | 605 | /** Common path for the Get*MeshElement functions */ 606 | void SetupInstancedMeshBatch(int32 LODIndex, int32 BatchIndex, FMeshBatch& OutMeshBatch) const; 607 | 608 | private: 609 | 610 | void SetupProxy(UInstanceBufferMeshComponent* InComponent); 611 | }; 612 | 613 | #if WITH_EDITOR 614 | 615 | 616 | /*----------------------------------------------------------------------------- 617 | FInstancedStaticMeshStaticLightingTextureMapping 618 | -----------------------------------------------------------------------------*/ 619 | 620 | 621 | /** Represents a static mesh primitive with texture mapped static lighting. */ 622 | class FStaticLightingTextureMapping_InstanceBufferMesh : public FStaticMeshStaticLightingTextureMapping 623 | { 624 | public: 625 | /** Initialization constructor. */ 626 | FStaticLightingTextureMapping_InstanceBufferMesh(UInstanceBufferMeshComponent* InPrimitive, int32 LODIndex, int32 InInstanceIndex, FStaticLightingMesh* InMesh, int32 InSizeX, int32 InSizeY, int32 InTextureCoordinateIndex, bool bPerformFullQualityRebuild) 627 | : FStaticMeshStaticLightingTextureMapping(InPrimitive, LODIndex, InMesh, InSizeX, InSizeY, InTextureCoordinateIndex, bPerformFullQualityRebuild) 628 | , InstanceIndex(InInstanceIndex) 629 | , QuantizedData(nullptr) 630 | , ShadowMapData() 631 | , bComplete(false) 632 | { 633 | } 634 | 635 | // FStaticLightingTextureMapping interface 636 | virtual void Apply(FQuantizedLightmapData* InQuantizedData, const TMap& InShadowMapData, ULevel* LightingScenario) override 637 | { 638 | check(bComplete == false); 639 | 640 | UInstanceBufferMeshComponent* InstancedComponent = Cast(Primitive.Get()); 641 | 642 | if (InstancedComponent) 643 | { 644 | // Save the static lighting until all of the component's static lighting has been built. 645 | QuantizedData = TUniquePtr(InQuantizedData); 646 | ShadowMapData.Empty(InShadowMapData.Num()); 647 | for (auto& ShadowDataPair : InShadowMapData) 648 | { 649 | ShadowMapData.Add(ShadowDataPair.Key, TUniquePtr(ShadowDataPair.Value)); 650 | } 651 | 652 | InstancedComponent->ApplyLightMapping(this, LightingScenario); 653 | } 654 | 655 | bComplete = true; 656 | } 657 | 658 | virtual bool DebugThisMapping() const override 659 | { 660 | return false; 661 | } 662 | 663 | virtual FString GetDescription() const override 664 | { 665 | return FString(TEXT("InstancedSMLightingMapping")); 666 | } 667 | 668 | private: 669 | friend class UInstanceBufferMeshComponent; 670 | 671 | /** The instance of the primitive this mapping represents. */ 672 | const int32 InstanceIndex; 673 | 674 | // Light/shadow map data stored until all instances for this component are processed 675 | // so we can apply them all into one light/shadowmap 676 | TUniquePtr QuantizedData; 677 | TMap> ShadowMapData; 678 | 679 | /** Has this mapping already been completed? */ 680 | bool bComplete; 681 | }; 682 | 683 | #endif 684 | 685 | 686 | 687 | 688 | 689 | 690 | 691 | 692 | 693 | 694 | 695 | 696 | 697 | 698 | 699 | 700 | -------------------------------------------------------------------------------- /Source/UnrealGPUSwarm/Private/InstanceBufferMesh_StaticMeshInstanceData.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timdecode/UnrealGPUSwarm/8d3a20d2a91a4b6f943c28563b05fc6776435ebc/Source/UnrealGPUSwarm/Private/InstanceBufferMesh_StaticMeshInstanceData.h -------------------------------------------------------------------------------- /Source/UnrealGPUSwarm/UnrealGPUSwarm.Build.cs: -------------------------------------------------------------------------------- 1 | // Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. 2 | 3 | using UnrealBuildTool; 4 | 5 | public class UnrealGPUSwarm : ModuleRules 6 | { 7 | public UnrealGPUSwarm(ReadOnlyTargetRules Target) : base(Target) 8 | { 9 | PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; 10 | 11 | PublicDependencyModuleNames.AddRange(new string[] { 12 | "Core", 13 | "CoreUObject", 14 | "Engine", 15 | "InputCore", 16 | "RenderCore", 17 | "RHI" 18 | }); 19 | 20 | PrivateDependencyModuleNames.AddRange(new string[] { }); 21 | 22 | PrivateIncludePaths.Add(EngineDirectory + "/Shaders/Shared"); 23 | 24 | // Uncomment if you are using Slate UI 25 | // PrivateDependencyModuleNames.AddRange(new string[] { "Slate", "SlateCore" }); 26 | 27 | // Uncomment if you are using online features 28 | // PrivateDependencyModuleNames.Add("OnlineSubsystem"); 29 | 30 | // To include OnlineSubsystemSteam, add it to the plugins section in your uproject file with the Enabled attribute set to true 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Source/UnrealGPUSwarm/UnrealGPUSwarm.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. 2 | 3 | #include "UnrealGPUSwarm.h" 4 | #include "Modules/ModuleManager.h" 5 | 6 | class FUnrealGPUSwarmModule : public IModuleInterface 7 | { 8 | virtual bool IsGameModule() const override 9 | { 10 | return true; 11 | } 12 | 13 | void StartupModule() override { 14 | FString ShaderDirectory = FPaths::Combine(FPaths::ProjectDir(), TEXT("Shaders")); 15 | AddShaderSourceDirectoryMapping("/ComputeShaderPlugin", ShaderDirectory); 16 | } 17 | }; 18 | 19 | IMPLEMENT_PRIMARY_GAME_MODULE(FUnrealGPUSwarmModule, UnrealGPUSwarm, "UnrealGPUSwarm"); 20 | -------------------------------------------------------------------------------- /Source/UnrealGPUSwarm/UnrealGPUSwarm.h: -------------------------------------------------------------------------------- 1 | // Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | 7 | -------------------------------------------------------------------------------- /Source/UnrealGPUSwarm/UnrealGPUSwarmGameModeBase.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. 2 | 3 | 4 | #include "UnrealGPUSwarmGameModeBase.h" 5 | 6 | -------------------------------------------------------------------------------- /Source/UnrealGPUSwarm/UnrealGPUSwarmGameModeBase.h: -------------------------------------------------------------------------------- 1 | // Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "GameFramework/GameModeBase.h" 7 | #include "UnrealGPUSwarmGameModeBase.generated.h" 8 | 9 | /** 10 | * 11 | */ 12 | UCLASS() 13 | class UNREALGPUSWARM_API AUnrealGPUSwarmGameModeBase : public AGameModeBase 14 | { 15 | GENERATED_BODY() 16 | 17 | }; 18 | -------------------------------------------------------------------------------- /Source/UnrealGPUSwarmEditor.Target.cs: -------------------------------------------------------------------------------- 1 | // Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. 2 | 3 | using UnrealBuildTool; 4 | using System.Collections.Generic; 5 | 6 | public class UnrealGPUSwarmEditorTarget : TargetRules 7 | { 8 | public UnrealGPUSwarmEditorTarget( TargetInfo Target) : base(Target) 9 | { 10 | Type = TargetType.Editor; 11 | DefaultBuildSettings = BuildSettingsVersion.V2; 12 | ExtraModuleNames.AddRange( new string[] { "UnrealGPUSwarm" } ); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /UnrealGPUSwarm.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": "." 5 | } 6 | ], 7 | "settings": {} 8 | } -------------------------------------------------------------------------------- /UnrealGPUSwarm.uproject: -------------------------------------------------------------------------------- 1 | { 2 | "FileVersion": 3, 3 | "EngineAssociation": "4.24", 4 | "Category": "", 5 | "Description": "", 6 | "Modules": [ 7 | { 8 | "Name": "UnrealGPUSwarm", 9 | "Type": "Runtime", 10 | "LoadingPhase": "PostConfigInit", 11 | "AdditionalDependencies": [ 12 | "Engine" 13 | ] 14 | } 15 | ], 16 | "Plugins": [ 17 | { 18 | "Name": "OculusVR", 19 | "Enabled": false, 20 | "SupportedTargetPlatforms": [ 21 | "Win32", 22 | "Win64", 23 | "Android" 24 | ] 25 | }, 26 | { 27 | "Name": "SteamVR", 28 | "Enabled": false, 29 | "SupportedTargetPlatforms": [ 30 | "Win32", 31 | "Win64", 32 | "Linux", 33 | "Mac" 34 | ] 35 | }, 36 | { 37 | "Name": "MagicLeapMedia", 38 | "Enabled": false, 39 | "SupportedTargetPlatforms": [ 40 | "Lumin" 41 | ] 42 | }, 43 | { 44 | "Name": "MagicLeap", 45 | "Enabled": false, 46 | "SupportedTargetPlatforms": [ 47 | "Lumin", 48 | "Mac", 49 | "Win64" 50 | ] 51 | }, 52 | { 53 | "Name": "MLSDK", 54 | "Enabled": false 55 | } 56 | ], 57 | "TargetPlatforms": [ 58 | "IOS", 59 | "MacNoEditor", 60 | "WindowsNoEditor" 61 | ] 62 | } --------------------------------------------------------------------------------