Files
UnrealEngineUWP/Engine/Source/Developer/TextureCompressor/Private/TextureCompressorModule.cpp
Lauren Ridge b46b1ce9ed Copying //UE4/Dev-Editor to //UE4/Dev-Main (Source: //UE4/Dev-Editor @ 4048875)
#lockdown Nick.Penwarden
#rb none

============================
  MAJOR FEATURES & CHANGES
============================

Change 3808185 by Cody.Albert

	Added missing calls to FEditorViewportClient::AddReferencedObjects in overrides

Change 3809824 by Michael.Trepka

	Improved the way we generate groups in Xcode project's source code navigator. They are now sorted alphabetically and have correct paths so Xcode no longer displays them in red. Also, added __INTELLISENSE__ to preprocessor definitions for indexing to improve indexing without game header files generated.

Change 3810089 by Jamie.Dale

	Fixed PO files failing to import translations containing only whitespace

Change 3811281 by Matt.Kuhlenschmidt

	PR #4331: Toggle SIE shortcut only in PIE (Contributed by projectgheist)


Change 3813031 by Matt.Kuhlenschmidt

	Fix undocked tabs not dropping at users mouse location

	#jira UE-53427

Change 3813361 by Brandon.Schaefer

	Print what SDL video driver we are using

Change 3818430 by Matt.Kuhlenschmidt

	PR #4365: Incorrect font name and forgotten undef (Contributed by projectgheist)


Change 3818432 by Matt.Kuhlenschmidt

	PR #4366: Asset Color Strip updates correct on drag and drop (Contributed by projectgheist)


Change 3818436 by Matt.Kuhlenschmidt

	PR #4367: Improved logging (Contributed by projectgheist)


Change 3819886 by Matt.Kuhlenschmidt

	Add a way to optionally disable the warning about referenced actors being moved to other levels.  Useful for bulk actor moves via script

Change 3819888 by Matt.Kuhlenschmidt

	Avoid crashing when a window size becomes too large to render.  Instead just ensure and clamp to the maximum allowed size.  Avoids crashes where the screen dimensions are saved with super large numbers for unknown reasons

Change 3821773 by Brandon.Schaefer

	Fix crash when importing to level

	#jira UE-31573

Change 3821892 by Jamie.Dale

	Improved the localized asset cooking so that it only cooks L10N variants if their source asset is cooked

	#jira UE-53010

Change 3823714 by Christina.TempelaarL

	#jira UE-52179 added support for grayscale PSD files

Change 3826805 by Christina.TempelaarL

	#jira UE-49636 SceneCaptureComponent2D hidden actor and show only actors disabled in blueprints
	#jira UE-53445 SceneCaptureComponent2D hidden actors always disabled in details layout

Change 3828444 by Anthony.Bills

	Add LXC container script for building third party libraries.

	The intention is that this should become the only way to rebuild the third party libraries that require system dependencies not included in the cross-compile toolchain and also to rebuild the toolchains. Other third party libraries without any system dependencies could be rebuilt via the cross-compile toolchains/UBT.

	This script has been tested running on CentOS 7 and Ubuntu 17.10.

	Buy default the x86 and x86_64 builds will be built against a CentOS 6 container (and targeting glibc 1.12) and the aarch64 and armhf builds will use an Ubuntu Ubuntu Trusty (14.04) but this is not yet complete.

Change 3828754 by Brandon.Schaefer

	Linux: Fix gamepad thumbstick clicks not registering (github #4209 thanks J??rn M??ller)

	#jira UE-45722

	#review-3828733 Arciel.Rekman

Change 3830414 by Brandon.Schaefer

	Remove circular referencing to a parent window. Move to use AddSP vs AddRaw as well to be safe manually remove ourselves from the selection event delegate list due to Linux pending deletion of windows.
	Looks like this should fix  UE-28322 as well which I've removed the work around placed in for that.

	#jira UE-53918

	#review @michael.trepka, @matt.kuhlenschmidt, @arciel.rekman

Change 3830916 by Brandon.Schaefer

	More verbose message about missing VK extensions (from Marcin Undak)

	#review-3830710 marcin.undak, arciel.rekman

Change 3831339 by Brandon.Schaefer

	Default to as-needed for debug mode

	#jira none

	#review-3830658 Arciel.Rekman

Change 3833102 by Jamie.Dale

	Re-added warning for duplicate package localization IDs when gathering asset localization

Change 3834600 by Jamie.Dale

	Optimized asset registry filter intersection

Change 3838024 by Brandon.Schaefer

	Remove tracking of CLion/CMake build files (from github #4346 thanks reapazor!)

	#jira UE-53551

	#review-3835803 arciel.rekman

Change 3839969 by Michael.Dupuis

	#jira UE-52289: When OnRegister is called on the component make sure our PerInstanceRenderData is up to date
	Prevent a possible crash if ClearInstanceSelection was called on a component with no PerInstanceRenderData existing

Change 3840049 by Michael.Dupuis

	#jira UE-52975: Was always performing the equivalent of an Add, so now we use the Transform during the duplicate

Change 3840071 by Matt.Kuhlenschmidt

	- Combine some shader params for slate in order to reduce overhead setting uniform buffers
	- Added better stats for slate draw call rendering
	- cleaned up huge lambda in Slate rendering main function so we can read the main slate rendering function again

Change 3840291 by Michael.Dupuis

	#jira UE-53053: Was having a mismatch between the remove reorder and the actual remove

Change 3840840 by Michael.Dupuis

	#jira UE-53944: Make sure the LOD generated is in the valid range to prevent the crash

Change 3842072 by Michael.Dupuis

	#jira UE-50299: Include NumSubsection in calculation of component quad factor

Change 3842487 by Christina.TempelaarL

	#jira UE-50573 HighResShot has wrong res in immersive mode

Change 3845702 by Matt.Kuhlenschmidt

	PR #4381: DefaultASTCQualityBySpeed too high max value. (Contributed by kallehamalainen)


Change 3845706 by Matt.Kuhlenschmidt

	PR #4388: Only restore window if minimized (Contributed by projectgheist)


Change 3845993 by Christina.TempelaarL

	#jira UE-41558 crash when selecting PostProcessingVolumes in separate levels

Change 3856395 by Brandon.Schaefer

	No longer using ALAudio on Linux

	#jira UE-53717

Change 3858324 by Michael.Trepka

	Preserve command line arguments in Xcode project when regenerating it

Change 3858365 by Michael.Dupuis

	#jira UE-52049: There was a case where adding and removing multiple time would lead to reordering the instances and this would cause the regeneration of the random stream for all the reorded instances.

Change 3858492 by Michael.Trepka

	Updated dependencies for Mac dSYM files so that only cross-referenced modules have their dSYMs recreated on subsequent builds instead of all modules.

Change 3859470 by Michael.Trepka

	CIS fix. Make sure a scheme file exists before trying to read it when generating Xcode project.

Change 3859900 by Joe.Conley

	Fix for "Check Out Assets" window not properly receiving focus.

Change 3865218 by Michael.Dupuis

	#jira UE-45784: Exposed the possibility to edit LDMaxDrawDistance

Change 3866957 by Michael.Dupuis

	#jira UE-42509: Added BodyInstance to ULandscapeSplineSegment and ULandscapeSplineControlPoint
	Deprecated bEnabledCollision and migrate data as it's replaced by BodyInstance

Change 3867220 by Cody.Albert

	Fixed Project Launcher scrollbar to properly stay anchored at the bottom of the scroll area.

Change 3869117 by Michael.Dupuis

	#jira UE-42509:Fixed compile error when not having editor data

Change 3872478 by Arciel.Rekman

	Linux: disable PIE if compiler enables it by default.

Change 3874786 by Michael.Dupuis

	#jira UE-46925: Remove the guessing functionality when importing a heightmap, and instead propose to the user valid size that can be used for the import through a combo button.
	Improved usability of the UI by disabling size field when no file was specified

Change 3875859 by Jamie.Dale

	Implemented our own canonization for culture codes

Change 3877604 by Cody.Albert

	We now validate actor names passed to SetActorLabel to ensure None isn't passed in, which can corrupt levels

Change 3877777 by Nick.Shin

	PhysX build fix - this came from CL: 3809757

	#jira UE-54924  Cannot rebuild Apex/PhysX/NvCloth .emscripten missing

Change 3881693 by Alexis.Matte

	Fix local path search to not search in memory only

	#jira UE-55018

Change 3882512 by Michael.Dupuis

	#jira none : Fixed screen size calculation to take aspect ratio into account correctly

Change 3886926 by Arciel.Rekman

	Linux: fixed checking clang settings during the cross-build (UE-55132).

	#jira UE-55132

Change 3887080 by Anthony.Bills

	Updated SDL2 build script.

	- Now allows compiling inside a CentOS 6 or Ubuntu 12.04 container with wayland support when using the ContainerBuildThirdParty.sh.
	- Added multiple build arch support to the BuildThirdParty script and pass this down to the SDL2 build script.

Change 3887260 by Arciel.Rekman

	Linux: fix leaking process handles in the cross-toolchain.

Change 3889072 by Brandon.Schaefer

	Fix RPath workaround, to better handle both cases

	#jira UE-55150

	#review-3888119 @Arciel.Rekman, @Ben.Marsh

Change 3892546 by Alexis.Matte

	Remove fbx exporter welded vertices options

	#jira UE-51575

Change 3893516 by Michael.Dupuis

	Remove static mesh instancing async buffer filling, as with all the changes made, it's no longer necessary, the cost of loading very large buffer is negligable

	Rebuild the occlusion tree when using foliage.DensityScale with something other than 1.0

Change 3894365 by Brandon.Schaefer

	Pass FileReference over a raw string to the LinkEnvironment

	#jira none

	#review-3894241 @Ben.Marsh, @Arciel.Rekman

Change 3895251 by Brandon.Schaefer

	Use X11 pointer barriers to bound the cursor to a region over warping the pointers. Patch from Cengiz

	#jira UE-25615
	#jira UE-30714

	#review-3894886 @Arciel.Rekman

Change 3897541 by Michael.Dupuis

	#jira UE-53787: Added guard if for some reason the material is null we should not try to draw using this material

Change 3904143 by Rex.Hill

	#jira UE-55366: Fix crash when overwriting existing level during level save as
	#jira UE-42426: Map '_BuiltData' can now be deleted when selected at same time as map

	- Map '_BuiltData' package is now garbage collected when switching maps in the editor

Change 3906373 by Brandon.Schaefer

	Fix splash image. Use alias format for big/little endian machines.

	#jira none

Change 3906711 by Rex.Hill

	#jira UE-42426: BuiltData now deleted with maps

Change 3907221 by Cody.Albert

	Add support for relative asset source paths in content plugins

Change 3911670 by Alexis.Matte

	Fix assetimportdata creation owner

	#jira UE-55567

Change 3912382 by Anthony.Bills

	Linux: Add binaries for GoogleTest and add to BuildThirdParty script.

Change 3914634 by Cody.Albert

	Added missing include that could cause compile errors if IWYU was disabled.

Change 3916227 by Cody.Albert

	Fixing some cases where we check #ifdef WITH_EDITOR instead of #if WITH_EDITOR

Change 3917245 by Michael.Dupuis

	#jira UE-35097: Fixed crash when creating a new landscape with 2x2 subsection and material containing grass spawning

Change 3918331 by Anthony.Bills

	Linux: Bundled Mono - Explicilty pick libc.so.6 as libc.so is a linker script and store the config file directly.

Change 3920191 by Rex.Hill

	#jira UE-44197 Fix saving sub-level level causing MapBuildData to be deleted

	Improved MapBuildData rename, move, duplicate, copy

Change 3920333 by Matt.Kuhlenschmidt

	Render target clear color property now settable in editor

	#jira UE-55347

Change 3926094 by Michael.Dupuis

	#jira UE-51502: Added some min/max values to foliage and grass settings to prevent overflow/crash

	#coderevew jack.porter

Change 3926243 by Michael.Dupuis

	#jira UE-54669: cleaned up invalid/duplicate shader and moved some shaders to appropriate list

Change 3926760 by Jamie.Dale

	Added support for TTC/OTC fonts

	These can be used via a sub-face index on FFontData, which can be set via a new combo in the font editor. You can also see the cached list of sub-faces within a font file from the UFontFace asset.

Change 3927793 by Anthony.Bills

	Mono: Remove SharpZipLib and references from bundled Mono.

	#review-3887212 @ben.marsh, @michael.trepka

Change 3928029 by Anthony.Bills

	Linux: Add support for UnrealVersionSelector.

	- Supports using UVS to launch without a project file. This will then launch the selected engine's project wizard.
	- Linux UVS uses Slate for the version selection and error log dialogs.
	- Mime-types and desktop file support added to DesktopPlatformLinux to allow associating with UVS as per the Windows binary and git builds.
	- Icons added for Linux.

	#review-3882197 @arciel.rekman, @brandon.schaefer

Change 3931293 by Alexis.Matte

	Add generic Levenshtein edit distance to core algo. This algorithm will help suggesting name matching when users have to resolve material name conflict when re-import fbx meshes.
	Add also plenty of automation tests for it.

	#jira none

Change 3931436 by Arciel.Rekman

	Stop RHI thread before shutting down RHI.

	- Prevents crashes for some drivers that create TLS objects with destructors; those destructors will get called after the thread exited, but the library will already be unloaded on RHI shutdown.

Change 3934287 by Alexis.Matte

	Fix crash when re-importing skeletal mesh. Skinned component render data resource is now release when re-importing.

	#jira none

Change 3937585 by Lauren.Ridge

	Added labels to the colors stored in the theme bar.

Change 3937738 by Alexis.Matte

	Make sure content browser do not show a preview asset created when we cancel an export animation preview

	#jira UE-49743

Change 3941345 by Michael.Dupuis

	#jira UE-26959: Prevent reusing multiple type the same grass type into the same material grass output node

Change 3941453 by Michael.Dupuis

	#jira UE-47492: Added a guard to validate LayerIndex

Change 3942065 by Jamie.Dale

	Fixed crash trying to use FSlateApplication when it wasn't available (eg, in a commandlet)

Change 3942573 by Alexis.Matte

	Fix static analysis

Change 3942623 by Michael.Dupuis

	#jira 0
	Cast to ulong as TaskIndex * NumStripes could exceed an int limit and add an assert if the wraparound is negative

Change 3942993 by Matt.Kuhlenschmidt

	PR #4547: Verify the return value of FT_New_Memory_Face (Contributed by jorgenpt)


Change 3942998 by Matt.Kuhlenschmidt

	PR #4554: Cleanup log printing (Contributed by projectgheist)


Change 3943003 by Matt.Kuhlenschmidt

	PR #4534: Prevent Fatal log when alt tabbing during a level save (Contributed by projectgheist)


Change 3943011 by Matt.Kuhlenschmidt

	PR #4518: edit (Contributed by pdlogingithub)


Change 3943027 by Matt.Kuhlenschmidt

	PR #4524: Notifications always render on the screen with the main viewport (Contributed by projectgheist)


Change 3943074 by Matt.Kuhlenschmidt

	PR #4484: Add group actor to folder (Contributed by ggsharkmob)


Change 3943079 by Matt.Kuhlenschmidt

	PR #4431: Git Plugin: replace usage of the 2 cli args "--work-tree" and "--git-dir" by "-C" (Contributed by SRombauts)


Change 3943092 by Matt.Kuhlenschmidt

	PR #4434: Git plugin: configure the default remote URL 'origin' (Contributed by SRombauts)


Change 3943132 by Matt.Kuhlenschmidt

	PR #4247: Add File picker to Git Path setting on GitSourceControl (Contributed by shiena)


Change 3943141 by Matt.Kuhlenschmidt

	PR #4303: Fix ULevelExporterT3D so that it works in a commandlet (Contributed by DSDambuster)


Change 3943349 by Jamie.Dale

	Cleaned up PR #4547

	Made the assert non-fatal to avoid it being able to take down the editor if you load up a bad font.
	Fixed some code that was deleted during the merge.

Change 3943976 by Michael.Trepka

	Copy of CL 3940687

	Fixed long link times when building for Mac in Debug by passing -no_deduplicate flag to the linker, which is what Xcode does in Debug configs.

	#jira none

Change 3944882 by Matt.Kuhlenschmidt

	Fix a few regressions with scene viewport activation locking can capturing the cursor in editor

	#jira UE-56080, UE-56081

Change 3947339 by Michael.Dupuis

	#jira UE-55664: Fixed undo/redo buffer handling so we remove from the beginning of the buffer during undo buffer where buffer is at max memory and from the end during redo operation.
	Fixed cancel also to re add removed transaction at the end or the start depending if we're doing a redo or undo operation
	Fixed the Undo History UI to listen to an event when the undo buffer changed instead of checking every frame, as when the buffer was full, no changes would occur, thus no UI update.

Change 3948179 by Jamie.Dale

	Fixed monochromatic font rendering

	- All non-8bpp images are now converted to 8bpp images for processing in Slate.
	- We convert the gray color of any images not using 256 grays (eg, monochromatic images that use 2 grays).
	- Fixed a case where the temporary bitmap wasn't being deleted.
	- Fixed a case where the bitmap could be used after it was deleted.
	- Added a CVar (Slate.EnableFontAntiAliasing) to control whether you want anti-aliased (256 grayscale) rendering (default), or monochromatic (2 grayscale) rendering.

Change 3949922 by Alexis.Matte

	Ensure fbx node name are not empty when loading a fbx file. I use the same naming convention as Maya

	#jira UE-56079

Change 3950202 by Rex.Hill

	Fix crash during editor asset automation tests.

	Now skips showing modal progress window when opening asset editor window. ActiveTopLevelWindow is not set when modal windows are open.

	#jira UE-56112

Change 3950484 by Michael.Dupuis

	#jira UE-52176: delete the Cluster tree when the builder is no longer needed

Change 3954628 by Michael.Dupuis

	Bring back 4.19/4.19.1 Landscape changes

Change 3957037 by Michael.Dupuis

	#jira UE-53343: Add foliage instances back when changing component size
	Changed the formulation for the Clip/Expand behavior to make it more explicit on what will happen
	Added SlowTask stuff to manage big landscape change

Change 3959020 by Rex.Hill

	Rename/move file MallocLeakDetection.h

Change 3960325 by Michael.Dupuis

	Fixed static analysis

Change 3961416 by Michael.Dupuis

	#jira UE-46100: Exposed UseDynamicInstanceBuffer on Foliage type, so user can decide if they want to update them dynamically
	#jira UE-55092: Fixed the warning to appear when having resource array as empty but VB as set up
	Added data conssitency that when using Dynamic buffer, Keep CPU Access should also be true, even if implicitly it's already the case, now it's explicit

Change 3962372 by Michael.Trepka

	Copy of CL 3884121

	Fix for SProgressBar rendering incorreclty on Mac

	#jira UE-56241

Change 3964931 by Anthony.Bills

	Linux: Add cross-compiled binary of UVS Shipping.

Change 3966719 by Matt.Kuhlenschmidt

	Fix parameters out of order here

	#jira UE-56399

Change 3966724 by Matt.Kuhlenschmidt

	PR #4585: Export symbols for the FDragTool (Contributed by Begounet)


Change 3966734 by Matt.Kuhlenschmidt

	PR #4596: fix the slider issue of the HighResolutionScreenshot window (Contributed by mamoniem)


Change 3966739 by Matt.Kuhlenschmidt

	Removed duplicated code

	#jira UE-56369

Change 3966744 by Matt.Kuhlenschmidt

	PR #4602: Fixes check for existing extensions when generating "All Extensions". (Contributed by PhilBax)


Change 3966758 by Matt.Kuhlenschmidt

	PR #4604: Fixed an issue where the Modules and DebugTools tabs would be unrecognized after startup if docked in the level editor (Contributed by tstaples)


Change 3966780 by Matt.Kuhlenschmidt

	Fix crash accessing graph node title widgets when objects have become stale.

	#jira UE-56442

Change 3966884 by Alexis.Matte

	Fix speedtree uninitialized values

	#jira none

Change 3967568 by Alexis.Matte

	Do not override the screensize when importing a skeletal mesh, let the value set by the AddLodInfo function

	#jira UE-56493

Change 3968333 by Brandon.Schaefer

	Fix order of operation

	#jira UE-56400

Change 3969070 by Anthony.Bills

	Linux: Make sure to set the UE_ENGINE_DIRECTORY

	#jira UE-56503

	#review-3966609 @arciel.rekman, @brandon.schaefer

Change 3971431 by Michael.Dupuis

	#jira UE-56515: Fixed an issue where ForcedLOD > MaxLOD and make sure that LastLOD will at least contain current streamed in LOD.
	#jira UE-56517: When using ParallelInitView 1 there was a memory leak related to a reallocate that happen with the TArray of FMemstack
	Pass correctly LODDistanceFactor instead of View.LODScale as we do not want StaticMeshScale to affect us.

Change 3971467 by Matt.Kuhlenschmidt

	Fixed crash deleting a texture with texture painting on it

	#jira UE-56994

Change 3971557 by Matt.Kuhlenschmidt

	Fix temporary exporter objects being potentially GC'd and causing crashes during export

	#jira UE-56981

Change 3971713 by Cody.Albert

	PR #4597: [FPS Template] Small null pointer check fix and cleanup (Contributed by TheCodez)


Change 3971846 by Michael.Dupuis

	#jira UE-56517: Properly "round" the count so we have the right amount of memory reserved
	#jira UE-56515: Still had a edge case left, so when using forced lod i simply make sure the value is in valid range, and allocate all the required data for this range

Change 3973035 by Nick.Atamas

	Line and Spline rendering changes:
	 * Lines/Splines now use 1 UV channel to anti-alias (this channel can be used for texturing)
	 * Anti-aliasing filter now adjusted based on resolution
	 * Modified Line/Spline topology to accomodate new UV requirements
	 * Disabled vertex snapping for anti-aliased lines/splines; previously vertexes were snapped, but vertex positions did not affect line rendering (behavior effectively unchanged)
	 * Splines now adaptively subdivided to avoid certain edge-cases

Change 3973345 by Nick.Atamas

	- Number tweaks to maintain previously perceived wire thickness in various editors.

Change 3977764 by Rex.Hill

	MallocTBB no longer debug fills bytes in development configuration

Change 3978713 by Arciel.Rekman

	UVS: Fix stale dependency.

Change 3980520 by Matt.Kuhlenschmidt

	Fix typo

	#jira UE-57059

Change 3980557 by Matt.Kuhlenschmidt

	Fixed negative pie window sizes causing crashes

	#jira UE-57100

Change 3980565 by Matt.Kuhlenschmidt

	PR #4628: Fixed revert action, now correctly uses CanRevert() condition (Contributed by Kryofenix)


Change 3980568 by Matt.Kuhlenschmidt

	PR #4626: UE-57111: Handle CaptureRegion for HighResShot in PIE (Contributed by projectgheist)


Change 3980580 by Matt.Kuhlenschmidt

	PR #4567: [Editor UI] Pick Parent Class dialog: set keyboard focus and handle Escape & Enter (Contributed by SRombauts)


Change 3980581 by Matt.Kuhlenschmidt

	PR #4565: [Editor UI] Add C++ Class dialog: set keyboard focus and handle Escape & Enter (Contributed by SRombauts)


Change 3981341 by Jamie.Dale

	Re-added GIsEditor condition around package namespace access

	#jira UE-55816

Change 3981808 by Ryan.Brucks

	Added LandscapeProxy functions to push RenderTarget data to Heightmaps and Weightmaps

Change 3983344 by Jack.Porter

	#include fixes for CL 3981808

	#jira 0

Change 3983391 by Jack.Porter

	One for #include fix for CL 3981808

	#jira 0

Change 3983562 by Michael.Dupuis

	#jira UE-53787: Make sure the material array is valid before trying to generate static mesh batch element
	#jira UE-56451: Instead of asserting, simply skip this element as it had invalid custom data anyway, so we can't render it

Change 3983600 by Matt.Kuhlenschmidt

	PR #4289: Pragma Once/Include guard cleanup (Contributed by projectgheist)


Change 3983637 by Matt.Kuhlenschmidt

	PR #4408: Add a template pregeneration hook (Contributed by mhutch)


Change 3984392 by Michael.Dupuis

	#jira UE-56314: Correctly apply LODBias on calculated LOD
	Fixed some Landscape popping that could occur when we were forcing a LOD that didn't match the component screen size

Change 3984950 by Rex.Hill

	Optimized texture import speed 2-3x depending on number of cpu cores and image size

Change 3985033 by Rex.Hill

	File drag and drop is more quick to respond when editor is in background

	#jira UE-57192

Change 3986218 by Jack.Porter

	Missing template parameter fix for CL 3981808

	#jira 0

Change 3986376 by Michael.Dupuis

	#jira UE-56453: Do not use the CreateDynamicMaterialInstance as it will change the parenting of the actor used material, instead simply use the function to generate the MID and parent it correctly.

Change 3989391 by Matt.Kuhlenschmidt

	Fix constant FName lookup in level editor when checking various states of level editor tabs

Change 3990182 by Rex.Hill

	Optimize editor startup time: GetCurrentProjectModules

Change 3990365 by Alexis.Matte

	Fix crash with spline mesh when the attach SM get a new imported LOD
	#jira UE-57119

Change 3991151 by Rex.Hill

	VR Editor module now waits to load images until VR mode activated in editor. Saves 0.4 seconds of editor startup time.

Change 3991164 by Rex.Hill

	Optimize editor startup time: FindModulePaths()
	- Invalidates cache when search paths added
	- Use cache during wildcard searches containing * and ?

Change 3995366 by Anthony.Bills

	Update BuildCrossToolchain script to allow a Linux host targeting multiple Linux architectures (including the hosts arch). Added a patch to support a gcc 4.8.5 based toolchain on windows (potentially useful for users crosscompiling using GCC and libstdc++ and targeting CentOS 7).

	#review-3848487 @arciel.rekman, @brandon.schaefer

Change 3996109 by Jamie.Dale

	Reworked BP error messages to be more localization friendly

	#jira UETOOL-1356

Change 3996123 by Michael.Dupuis

	#jira UE-57427: Update random color on load of the component
	#jira UE-56272:

Change 3996279 by Merritt.Cely

	Removed hardware survey from editor
	#jira an-2243

	#tests launched the editor

Change 3996626 by Alexis.Matte

	Fix crash when SkeletalMesh tangent buffer is empty after the build and we serialize the tangent array.

	#jira UE-57227

Change 3996663 by Max.Chen

	Sequencer: Fix fbx animation export - rotation and scale channels were flipped.

	#jira UE-57509
	#jira UE-57512
	#jira UE-57514

Change 4000331 by Brandon.Schaefer

	Add a GFNameTableForDebuggerVisualizers_MT back only for Unix under the Core module

	#review-3999426 @Arciel.Rekman

	#jira UE-55298

Change 4000450 by Matt.Kuhlenschmidt

	Another guard against a factory being destroyed during import

	#jira UE-57674

Change 4000459 by Matt.Kuhlenschmidt

	Added check for valid game viewport to see if this is the problem in UE-57677

	#jira UE-57677

Change 4000493 by Matt.Kuhlenschmidt

	Remove stale GC'd components when refreshing paint mode to prevent crashes

	#jira UE-52618

Change 4000683 by Jamie.Dale

	Fixed target being incorrect when added via the Localization Dashboard

	#jira UE-57588

Change 4000738 by Alexis.Matte

	Add a section settings to ignore the section when reducing

	#jira UE-52580

Change 4000920 by Alexis.Matte

	PR #4219: Fix for SColorGradingPicker preventing PIE (Contributed by projectgheist)
	author projectgheist projectgheist@gmail.com

Change 4001432 by Alexis.Matte

	Add a fbx re-import resolve material windows, user can now help resolving the material in case the importer fail to found a match.

Change 4001447 by Jamie.Dale

	Fixed property table not working with multi-line editable text

Change 4001449 by Jamie.Dale

	PR #4531: Localization multiline fix (Contributed by Lallapallooza)


Change 4001557 by Alexis.Matte

	Fix a check in fbx scene importer, in case the user import a fbx LOD group with no geometry under it

	#jira UE-57676

Change 4002539 by Alexis.Matte

	Make the fbx importer global transform options persist in the config file
	#jira UE-50897

Change 4002562 by Anthony.Bills

	Linux: Enable UVS registering for git builds only and remove old Mono and pre-UVS script code.

Change 4003241 by Alexis.Matte

	Fix the staticmesh import socket logic, it was duplicating socket when re-importing

	#jira UE-53635

Change 4003368 by Michael.Dupuis

	#jira UE-57276:
	#jira UE-56239:
	#jira UE-54547:
	Make sure we can't go above MaxLOD even for texture streaming

Change 4003534 by Alexis.Matte

	Fix re-import mesh name match

	#jira UE-56485

Change 4005069 by Michael.Dupuis

	#jira UE-57594: Add a guard to prevent crash if we have an invalid resource for the heightmap texture (happen when component is deleted, for example)

Change 4005468 by Lauren.Ridge

	Widgets should not be removed from parent when they are pending GC

	#jira UE-52260

Change 4006075 by Michael.Dupuis

	Fixed foliage density scaling to be applied even in editor, except in Foliage edit mode.

Change 4006332 by Arciel.Rekman

	UBT: Adding support for bundled toolchains on Linux.

	- Authored by Anthony Bills, with modifications.

Change 4007528 by Matt.Kuhlenschmidt

	PR #4665: Source control History Window: enlarge column Description (Contributed by SRombauts)


Change 4007531 by Matt.Kuhlenschmidt

	PR #4656: UE-57200: Ignore reference to actor if same actor (Contributed by projectgheist)


Change 4007548 by Matt.Kuhlenschmidt

	PR #4664: Set Password on EditableText (Contributed by projectgheist)


Change 4007730 by Brandon.Schaefer

	Add a new way to symbolicate symbols for a crash at runtime

	Two new tools are used for this.
	1) dump_syms Will generate a symbol file, which is to large to read from at runtime
	2) BreakpadSymbolEncoder Takes the dump_syms file and encodes it in such a way we can do
	    a binary search at runtime to find a Program Counter to a symbol we are looking for

	#review @Arciel.Rekman, @Anthony.Bills

	#jira UETOOL-1206

Change 4008429 by Lauren.Ridge

	Fixing undo bug when deleting user widgets from the widget tree

	#jira UE-56394

Change 4008581 by Cody.Albert

	Reinitialize needs to set the audio and caption tracks in addition to the video track or the currently selected track will be lost

Change 4009605 by Lauren.Ridge

	Added Recently Opened assets filter under Other Filters in the Content  Browser

Change 4009797 by Anthony.Bills

	Linux: Update MultiArchRoot path to not cache. Move in tree toolchain location to match UBT convention and make sure the MultiArchRoot is checked before the system.

Change 4010266 by Michael.Trepka

	Copy of CL 4010052

	Moved some key event handling calls to the main thread on Mac to satisfy new macOS requirements

	#jira UE-54623

Change 4010838 by Arciel.Rekman

	Linux: limit allowed clang versions to 3.8-6.0.

Change 4012160 by Matt.Kuhlenschmidt

	Changed the messagiing on the crash reporter dialog to reflect new bug submission process

	#jira UE-56475

Change 4013432 by Lauren.Ridge

	Fix for non-assets attempting to add to the Content Browser's recent filter

	#jira none

Change 4016353 by Cody.Albert

	Improved copy/paste behavior for UMG editor:

	-Pasting in the designer while a canvas is selected will place the new widget under the cursor
	-Pasting multiple times while a canvas panel is selected in the hierarchy view will cascade the widgets starting at 0,0
	-Pasting while something that isn't a panel is selected is now allowed, and will cascade the pasted widgets off the position of the selected widget (as siblings)
	-Newly pasted widgets will now be selected automatically
	-Pasting multiple widgets at once will try and maintain their relative positions if they're being pasted into a canvas panel

Change 4017274 by Matt.Kuhlenschmidt

	Added some guards against invalid property handle access

	#jira UE-58026

Change 4017295 by Matt.Kuhlenschmidt

	Fix trying to apply delta to a mix of scene components and non scene components.  Its acceptable to not have scene components in the selected component list

	#jira  UE-57980

Change 4022021 by Rex.Hill

	Fix for audio desync and video fast-forwarding behavior.
	There long delay (500ms+) until samples start arriving unless we use RequestedTimeCurrent.
	After delay occurs samples begin arriving at accelerated speed until caught up to playback time leading to visual and audio problems.

	#jira UE-54592

Change 4023608 by Brandon.Schaefer

	Downscale memory if we dont have enough

	#jira UE-58073

	#review-4023609 @Arciel.Rekman

Change 4025618 by Michael.Dupuis

	#jira UE-58036: Apply world position offset correctly

Change 4025661 by Michael.Dupuis

	#jira UE-57681: Added guard to prevent possible crash if either we have an invalid material or the material parent is invalid

Change 4025675 by Michael.Dupuis

	#jira UE-52919: if no actor was found in the level skip moving the instances

Change 4026336 by Brandon.Schaefer

	Manually generate *.sym files for Physx3

	This should be done in the BuildPhysx file

Change 4026627 by Rex.Hill

	Fix memory leak fix when playing video and main thread blocks
	#jira UE-57873

Change 4029635 by Yannick.Lange

	Fix VRMode loading assets only when VRMode starts.
	#jira UE-57797

Change 4030288 by Jamie.Dale

	Null FreeType face on load error to prevent potential crashes

Change 4030782 by Rex.Hill

	Fix save BuildData after changing reflection capture in a new level
	#jira UE-57949

Change 4033560 by Michael.Dupuis

	#jira UE-57710: Added some guard to prevent crash/assert

Change 4034244 by Michael.Trepka

	Copy of CL 4034116

	Fixed arrow keys handling on Mac

Change 4034708 by Lauren.Ridge

	PR #4699: UE-8508: Update config file to keep folder color in sync (Contributed by projectgheist)


	#jira UE-58251

Change 4034746 by Lauren.Ridge

	PR #4701: Add option to close tabs to the right of the active tab (Contributed by jesseyeh)

	#jira UE-58277

Change 4034873 by Lauren.Ridge

	Fix for not being able to enter simulate more than once in a row.

	#jira UE-58261

Change 4034922 by Lauren.Ridge

	PR #4387: Commands mapped in incorrect location (Contributed by projectgheist)

	#jira UE-53752

Change 4035484 by Lauren.Ridge

	Tentative fix for crash on pasting comment. All other accesses to UMaterialExpressionComment check its validity first
	#jira UE-57979

Change 4037111 by Brandon.Schaefer

	Try to use absolute path from dladdr if we can to find the sym files

	#jira UE-57858

	#review-4013964 @Arciel.Rekman

Change 4037366 by Brandon.Schaefer

	Dont check the command line before its inited

	#review-4037183 @Arciel.Rekman
	#jira UE-57947

Change 4037418 by Alexis.Matte

	Remove the checkSlow when adding polygon

Change 4037745 by Brandon.Schaefer

	Use as much info as we can during ensure

	Just as fast as the old way but with more information

	#review-4037495 @Arciel.Rekman
	#jira UE-47770

Change 4037816 by Rex.Hill

	Import mesh optimization, BuildVertexBuffer

Change 4037957 by Arciel.Rekman

	UBT: make it easier to try XGE on Linux.

Change 4038401 by Lauren.Ridge

	Reordering is now correctly handled by undo. Reordering and then undoing will no longer cause a "ghost" widget to also be part of the tree.

	#jira UE-58206

Change 4039612 by Anthony.Bills

	Unix: Check for null StdOut and ReturnCode parameters, otherwise the code may dereference a null variable when the process fails to create.

Change 4039754 by Alexis.Matte

	Remove the Render meshdescription, no need to carry this temporary data in the staticmesh

Change 4039806 by Anthony.Bills

	Linux: UVS fixes

	- Update to use new Unix base platform.
	- Use bin/bash instead of usr/bin/bash (may need revisiting later).
	- Recompile Shipping version with changes.
	- Update Setup.sh to run from correct CWD (due to current limitations in the relative directory handling).

Change 4039883 by Lauren.Ridge

	PR #4576: Save editor config to file first time a fav folder is added in the co. (Contributed by projectgheist)

	#jira UE-56249

Change 4040117 by Lauren.Ridge

	Replacing widgets should now also clear out references to the widget

	#jira UE-57045

Change 4040790 by Lauren.Ridge

	Tentative fix for Project Launcher crash when platform info not found
	#jira UE-58371

Change 4042136 by Arciel.Rekman

	UBT: refactor of LinuxToolChain to make it leaner and more configurable.

	- Made it possible to override SDK passed to the toolchain.
	- Simplified the code by using the same executable names on Windows and Linux (as .exe is optional), except where File.Exists() is needed (also remove a few)
	- Some minor renames to make it clear that SystemSDK means system compiler (which otherwise may be unclear)
	- Made changes to accomodate the new debug format.

Change 4042930 by Brandon.Schaefer

	GCoreObjectArrayForDebugVisualizers was changed to FChunkedFixedUObjectArray reflect that in the Unix part

Change 4043539 by Brandon.Schaefer

	Fix callsite address being used at times for the Program Counter
	Fix only reporting the actual callstack and not the crash handling callstacks

	#review-4041370 @Arciel.Rekman
	#jira UE-58477

Change 4043674 by Arciel.Rekman

	Added Linux ARM64 (AArch64) lib for MikkTSpace.

	- Now required for standalone games due to EditableMesh runtime plugin.

Change 4043677 by Arciel.Rekman

	Linux: updated ARM64 (AArch64) version of SDL2.

Change 4043690 by Arciel.Rekman

	Linux: allow compiling VulkanRHI for AArch64 (ARM64).

Change 4045467 by Brandon.Schaefer

	Add Anthony Bills SetupToolchain.sh script

	Used to download the latest toolchain

Change 4045940 by Michael.Trepka

	Return empty list instead of null from Mac GetDebugInfoExtensions() in UBT

	#jira UE-58470

Change 4046542 by Alexis.Matte

	Fix skeletal re-import material assignation

	#jira UE-58551

Change 4048262 by Brandon.Schaefer

	Rebuild SDL with pulse audio libs

	#jira UE-58577

Change 3887093 by Anthony.Bills

	Add bundled mono binary for Linux.

	- Unify some of the script structure across Mac and Linux.
	- This currently uses the same mono C# assemblies as Mac to keep the additional source size down.
	- If the Mac mono version is updated, the Linux version will also need to be updated to match the same mono git revision.
	- The system version of mono can still be used by setting the UE_USE_SYSTEM_MONO env var to 1.

Change 4003226 by Michael.Dupuis

	Refactored StaticMeshInstancing to now use a command buffer to communicate with the GPU to prevent concurent access issues. It's mostly used in Editor or if runtime changes occur, otherwise the data is built and send to the GPU directly without keeping CPU copy.
	Changed how the density scaling was applied to be more optimal
	Removed UseDynamicInstanceBuffer as the concept is now irrelevant

Change 3833097 by Jamie.Dale

	Localization Pipeline Optimization

	Manifest/Archives:
	Added FLocKey to keep an immutable string and its hash. This is used in several places within manifests and archives to minimize string hashing. FLocTextHelper also now take these in its API.
	This also fixes some places where manifests were being iterated by key rather than source string (as this was causing redundant work).

	Portable Object:
	Cleaned up a lot of redundant code, changed things to use FLocKey, and simplified a lot of string manipulation to use algorithms instead (which proved to be faster).

	Asset Gathering:
	Optimized the way garbage collection runs while gathering from assets so that we avoid purging assets that we still need to gather from (or are still active dependencies). This also sorts the assets so that we can try and evict dependencies from memory as soon as possible (in much the same way that the cooker does).

	Automation:
	The gather commandlet can now take multiple configs to process. This is used by automation to avoid starting the editor several times (which can save a significant amount of start-up overhead).

[CL 4052378 by Lauren Ridge in Main branch]
2018-05-04 14:14:10 -04:00

2239 lines
70 KiB
C++

// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.
#include "TextureCompressorModule.h"
#include "Math/RandomStream.h"
#include "Containers/IndirectArray.h"
#include "Stats/Stats.h"
#include "Async/AsyncWork.h"
#include "Async/ParallelFor.h"
#include "Modules/ModuleManager.h"
#include "Engine/Texture.h"
#include "Interfaces/ITargetPlatformManagerModule.h"
#include "Interfaces/ITextureFormat.h"
#include "ImageCore.h"
#if PLATFORM_WINDOWS
#include "Windows/WindowsHWrapper.h"
#endif
DEFINE_LOG_CATEGORY_STATIC(LogTextureCompressor, Log, All);
/*------------------------------------------------------------------------------
Mip-Map Generation
------------------------------------------------------------------------------*/
enum EMipGenAddressMode
{
MGTAM_Wrap,
MGTAM_Clamp,
MGTAM_BorderBlack,
};
/**
* 2D view into one slice of an image.
*/
struct FImageView2D
{
/** Pointer to colors in the slice. */
FLinearColor* SliceColors;
/** Width of the slice. */
int32 SizeX;
/** Height of the slice. */
int32 SizeY;
FImageView2D() : SliceColors(nullptr), SizeX(0), SizeY(0) {}
/** Initialization constructor. */
FImageView2D(FImage& Image, int32 SliceIndex)
{
SizeX = Image.SizeX;
SizeY = Image.SizeY;
SliceColors = Image.AsRGBA32F() + SliceIndex * SizeY * SizeX;
}
/** Access a single texel. */
FLinearColor& Access(int32 X, int32 Y)
{
return SliceColors[X + Y * SizeX];
}
/** Const access to a single texel. */
const FLinearColor& Access(int32 X, int32 Y) const
{
return SliceColors[X + Y * SizeX];
}
bool IsValid() const { return SliceColors != nullptr; }
};
// 2D sample lookup with input conversion
// requires SourceImageData.SizeX and SourceImageData.SizeY to be power of two
template <EMipGenAddressMode AddressMode>
FLinearColor LookupSourceMip(const FImageView2D& SourceImageData, int32 X, int32 Y)
{
if(AddressMode == MGTAM_Wrap)
{
// wrap
X = (int32)((uint32)X) & (SourceImageData.SizeX - 1);
Y = (int32)((uint32)Y) & (SourceImageData.SizeY - 1);
}
else if(AddressMode == MGTAM_Clamp)
{
// clamp
X = FMath::Clamp(X, 0, SourceImageData.SizeX - 1);
Y = FMath::Clamp(Y, 0, SourceImageData.SizeY - 1);
}
else if(AddressMode == MGTAM_BorderBlack)
{
// border color 0
if((uint32)X >= (uint32)SourceImageData.SizeX
|| (uint32)Y >= (uint32)SourceImageData.SizeY)
{
return FLinearColor(0, 0, 0, 0);
}
}
else
{
check(0);
}
//return *(SourceImageData.AsRGBA32F() + X + Y * SourceImageData.SizeX);
return SourceImageData.Access(X,Y);
}
// Kernel class for image filtering operations like image downsampling
// at max MaxKernelExtend x MaxKernelExtend
class FImageKernel2D
{
public:
FImageKernel2D() :FilterTableSize(0)
{
}
// @param TableSize1D 2 for 2x2, 4 for 4x4, 6 for 6x6, 8 for 8x8
// @param SharpenFactor can be negative to blur
// generate normalized 2D Kernel with sharpening
void BuildSeparatableGaussWithSharpen(uint32 TableSize1D, float SharpenFactor = 0.0f)
{
if(TableSize1D > MaxKernelExtend)
{
TableSize1D = MaxKernelExtend;
}
float Table1D[MaxKernelExtend];
float NegativeTable1D[MaxKernelExtend];
FilterTableSize = TableSize1D;
if(SharpenFactor < 0.0f)
{
// blur only
BuildGaussian1D(Table1D, TableSize1D, 1.0f, -SharpenFactor);
BuildFilterTable2DFrom1D(KernelWeights, Table1D, TableSize1D);
return;
}
else if(TableSize1D == 2)
{
// 2x2 kernel: simple average
KernelWeights[0] = KernelWeights[1] = KernelWeights[2] = KernelWeights[3] = 0.25f;
return;
}
else if(TableSize1D == 4)
{
// 4x4 kernel with sharpen or blur: can alias a bit
BuildFilterTable1DBase(Table1D, TableSize1D, 1.0f + SharpenFactor);
BuildFilterTable1DBase(NegativeTable1D, TableSize1D, -SharpenFactor);
BlurFilterTable1D(NegativeTable1D, TableSize1D, 1);
}
else if(TableSize1D == 6)
{
// 6x6 kernel with sharpen or blur: still can alias
BuildFilterTable1DBase(Table1D, TableSize1D, 1.0f + SharpenFactor);
BuildFilterTable1DBase(NegativeTable1D, TableSize1D, -SharpenFactor);
BlurFilterTable1D(NegativeTable1D, TableSize1D, 2);
}
else if(TableSize1D == 8)
{
//8x8 kernel with sharpen or blur
// * 2 to get similar appearance as for TableSize 6
SharpenFactor = SharpenFactor * 2.0f;
BuildFilterTable1DBase(Table1D, TableSize1D, 1.0f + SharpenFactor);
// positive lobe is blurred a bit for better quality
BlurFilterTable1D(Table1D, TableSize1D, 1);
BuildFilterTable1DBase(NegativeTable1D, TableSize1D, -SharpenFactor);
BlurFilterTable1D(NegativeTable1D, TableSize1D, 3);
}
else
{
// not yet supported
check(0);
}
AddFilterTable1D(Table1D, NegativeTable1D, TableSize1D);
BuildFilterTable2DFrom1D(KernelWeights, Table1D, TableSize1D);
}
inline uint32 GetFilterTableSize() const
{
return FilterTableSize;
}
inline float GetAt(uint32 X, uint32 Y) const
{
checkSlow(X < FilterTableSize);
checkSlow(Y < FilterTableSize);
return KernelWeights[X + Y * FilterTableSize];
}
inline float& GetRefAt(uint32 X, uint32 Y)
{
checkSlow(X < FilterTableSize);
checkSlow(Y < FilterTableSize);
return KernelWeights[X + Y * FilterTableSize];
}
private:
inline static float NormalDistribution(float X, float Variance)
{
const float StandardDeviation = FMath::Sqrt(Variance);
return FMath::Exp(-FMath::Square(X) / (2.0f * Variance)) / (StandardDeviation * FMath::Sqrt(2.0f * (float)PI));
}
// support even and non even sized filters
static void BuildGaussian1D(float *InOutTable, uint32 TableSize, float Sum, float Variance)
{
float Center = TableSize * 0.5f;
float CurrentSum = 0;
for(uint32 i = 0; i < TableSize; ++i)
{
float Actual = NormalDistribution(i - Center + 0.5f, Variance);
InOutTable[i] = Actual;
CurrentSum += Actual;
}
// Normalize
float InvSum = Sum / CurrentSum;
for(uint32 i = 0; i < TableSize; ++i)
{
InOutTable[i] *= InvSum;
}
}
//
static void BuildFilterTable1DBase(float *InOutTable, uint32 TableSize, float Sum )
{
// we require a even sized filter
check(TableSize % 2 == 0);
float Inner = 0.5f * Sum;
uint32 Center = TableSize / 2;
for(uint32 x = 0; x < TableSize; ++x)
{
if(x == Center || x == Center - 1)
{
// center elements
InOutTable[x] = Inner;
}
else
{
// outer elements
InOutTable[x] = 0.0f;
}
}
}
// InOutTable += InTable
static void AddFilterTable1D( float *InOutTable, float *InTable, uint32 TableSize )
{
for(uint32 x = 0; x < TableSize; ++x)
{
InOutTable[x] += InTable[x];
}
}
// @param Times 1:box, 2:triangle, 3:pow2, 4:pow3, ...
// can be optimized with double buffering but doesn't need to be fast
static void BlurFilterTable1D( float *InOutTable, uint32 TableSize, uint32 Times )
{
check(Times>0);
check(TableSize<32);
float Intermediate[32];
for(uint32 Pass = 0; Pass < Times; ++Pass)
{
for(uint32 x = 0; x < TableSize; ++x)
{
Intermediate[x] = InOutTable[x];
}
for(uint32 x = 0; x < TableSize; ++x)
{
float sum = Intermediate[x];
if(x)
{
sum += Intermediate[x-1];
}
if(x < TableSize - 1)
{
sum += Intermediate[x+1];
}
InOutTable[x] = sum / 3.0f;
}
}
}
static void BuildFilterTable2DFrom1D( float *OutTable2D, float *InTable1D, uint32 TableSize )
{
for(uint32 y = 0; y < TableSize; ++y)
{
for(uint32 x = 0; x < TableSize; ++x)
{
OutTable2D[x + y * TableSize] = InTable1D[y] * InTable1D[x];
}
}
}
// at max we support MaxKernelExtend x MaxKernelExtend kernels
const static uint32 MaxKernelExtend = 12;
// 0 if no kernel was setup yet
uint32 FilterTableSize;
// normalized, means the sum of it should be 1.0f
float KernelWeights[MaxKernelExtend * MaxKernelExtend];
};
template <EMipGenAddressMode AddressMode>
static FVector4 ComputeAlphaCoverage(const FVector4& Thresholds, const FVector4& Scales, const FImageView2D& SourceImageData)
{
FVector4 Coverage(0, 0, 0, 0);
int32 NumJobs = FTaskGraphInterface::Get().GetNumWorkerThreads();
int32 NumRowsEachJob = SourceImageData.SizeY / NumJobs;
if (NumRowsEachJob * NumJobs < SourceImageData.SizeY)
{
++NumRowsEachJob;
}
int32 CommonResults[4] = { 0, 0, 0, 0 };
ParallelFor(NumJobs, [&](int32 Index)
{
int32 StartIndex = Index * NumRowsEachJob;
int32 EndIndex = FMath::Min(StartIndex + NumRowsEachJob, SourceImageData.SizeY);
int32 LocalCoverage[4] = { 0, 0, 0, 0 };
for (int32 y = StartIndex; y < EndIndex; ++y)
{
for (int32 x = 0; x < SourceImageData.SizeX; ++x)
{
// Sample channel values at pixel neighborhood
FVector4 PixelValue(LookupSourceMip<AddressMode>(SourceImageData, x, y));
// Calculate coverage for each channel (if being used as an alpha mask)
for (int32 i = 0; i < 4; ++i)
{
// Skip channel if Threshold is 0
if (Thresholds[i] == 0)
{
continue;
}
if (PixelValue[i] * Scales[i] >= Thresholds[i])
{
++LocalCoverage[i];
}
}
}
}
for (int32 i = 0; i < 4; ++i)
{
FPlatformAtomics::InterlockedAdd(&CommonResults[i], LocalCoverage[i]);
}
});
for (int32 i = 0; i < 4; ++i)
{
Coverage[i] = float(CommonResults[i]);
}
return Coverage / float(SourceImageData.SizeX * SourceImageData.SizeY);
}
template <EMipGenAddressMode AddressMode>
static FVector4 ComputeAlphaScale(const FVector4& Coverages, const FVector4& AlphaThresholds, const FImageView2D& SourceImageData)
{
FVector4 MinAlphaScales (0, 0, 0, 0);
FVector4 MaxAlphaScales (4, 4, 4, 4);
FVector4 AlphaScales (1, 1, 1, 1);
//Binary Search to find Alpha Scale
for (int32 i = 0; i < 8; ++i)
{
FVector4 ComputedCoverages = ComputeAlphaCoverage<AddressMode>(AlphaThresholds, AlphaScales, SourceImageData);
for (int32 j = 0; j < 4; ++j)
{
if (AlphaThresholds[j] == 0 || fabs(ComputedCoverages[j] - Coverages[j]) < KINDA_SMALL_NUMBER)
{
continue;
}
if (ComputedCoverages[j] < Coverages[j])
{
MinAlphaScales[j] = AlphaScales[j];
}
else if (ComputedCoverages[j] > Coverages[j])
{
MaxAlphaScales[j] = AlphaScales[j];
}
AlphaScales[j] = (MinAlphaScales[j] + MaxAlphaScales[j]) * 0.5f;
}
if (ComputedCoverages.Equals(Coverages))
{
break;
}
}
return AlphaScales;
}
/**
* Generates a mip-map for an 2D B8G8R8A8 image using a 4x4 filter with sharpening
* @param SourceImageData - The source image's data.
* @param DestImageData - The destination image's data.
* @param ImageFormat - The format of both the source and destination images.
* @param FilterTable2D - [FilterTableSize * FilterTableSize]
* @param FilterTableSize - >= 2
* @param ScaleFactor 1 / 2:for downsampling
*/
template <EMipGenAddressMode AddressMode>
static void GenerateSharpenedMipB8G8R8A8Templ(
const FImageView2D& SourceImageData,
FImageView2D& DestImageData,
bool bDitherMipMapAlpha,
FVector4 AlphaCoverages,
FVector4 AlphaThresholds,
const FImageKernel2D& Kernel,
uint32 ScaleFactor,
bool bSharpenWithoutColorShift )
{
check( SourceImageData.SizeX == ScaleFactor * DestImageData.SizeX || DestImageData.SizeX == 1 );
check( SourceImageData.SizeY == ScaleFactor * DestImageData.SizeY || DestImageData.SizeY == 1 );
check( Kernel.GetFilterTableSize() >= 2 );
const int32 KernelCenter = (int32)Kernel.GetFilterTableSize() / 2 - 1;
// Set up a random number stream for dithering.
FRandomStream RandomStream(0);
FVector4 AlphaScale(1, 1, 1, 1);
if (AlphaThresholds != FVector4(0,0,0,0))
{
AlphaScale = ComputeAlphaScale<AddressMode>(AlphaCoverages, AlphaThresholds, SourceImageData);
}
ParallelFor(DestImageData.SizeY, [&](int32 DestY)
{
for ( int32 DestX = 0;DestX < DestImageData.SizeX; DestX++ )
{
const int32 SourceX = DestX * ScaleFactor;
const int32 SourceY = DestY * ScaleFactor;
FLinearColor FilteredColor(0, 0, 0, 0);
if ( bSharpenWithoutColorShift )
{
FLinearColor SharpenedColor(0, 0, 0, 0);
for ( uint32 KernelY = 0; KernelY < Kernel.GetFilterTableSize(); ++KernelY )
{
for ( uint32 KernelX = 0; KernelX < Kernel.GetFilterTableSize(); ++KernelX )
{
float Weight = Kernel.GetAt( KernelX, KernelY );
FLinearColor Sample = LookupSourceMip<AddressMode>( SourceImageData, SourceX + KernelX - KernelCenter, SourceY + KernelY - KernelCenter );
SharpenedColor += Weight * Sample;
}
}
float NewLuminance = SharpenedColor.ComputeLuminance();
// simple 2x2 kernel to compute the color
FilteredColor =
( LookupSourceMip<AddressMode>( SourceImageData, SourceX + 0, SourceY + 0 )
+ LookupSourceMip<AddressMode>( SourceImageData, SourceX + 1, SourceY + 0 )
+ LookupSourceMip<AddressMode>( SourceImageData, SourceX + 0, SourceY + 1 )
+ LookupSourceMip<AddressMode>( SourceImageData, SourceX + 1, SourceY + 1 ) ) * 0.25f;
float OldLuminance = FilteredColor.ComputeLuminance();
if ( OldLuminance > 0.001f )
{
float Factor = NewLuminance / OldLuminance;
FilteredColor.R *= Factor;
FilteredColor.G *= Factor;
FilteredColor.B *= Factor;
}
// We also want to sharpen the alpha channel (was missing before)
FilteredColor.A = SharpenedColor.A;
}
else
{
for ( uint32 KernelY = 0; KernelY < Kernel.GetFilterTableSize(); ++KernelY )
{
for ( uint32 KernelX = 0; KernelX < Kernel.GetFilterTableSize(); ++KernelX )
{
float Weight = Kernel.GetAt( KernelX, KernelY );
FLinearColor Sample = LookupSourceMip<AddressMode>( SourceImageData, SourceX + KernelX - KernelCenter, SourceY + KernelY - KernelCenter );
FilteredColor += Weight * Sample;
}
}
}
// Apply computed alpha scales to each channel
FilteredColor.R *= AlphaScale.X;
FilteredColor.G *= AlphaScale.Y;
FilteredColor.B *= AlphaScale.Z;
FilteredColor.A *= AlphaScale.W;
if ( bDitherMipMapAlpha )
{
// Dither the alpha of any pixel which passes an alpha threshold test.
const int32 DitherAlphaThreshold = 5.0f / 255.0f;
const float MinRandomAlpha = 85.0f;
const float MaxRandomAlpha = 255.0f;
if ( FilteredColor.A > DitherAlphaThreshold)
{
FilteredColor.A = FMath::TruncToInt( FMath::Lerp( MinRandomAlpha, MaxRandomAlpha, RandomStream.GetFraction() ) );
}
}
// Set the destination pixel.
//FLinearColor& DestColor = *(DestImageData.AsRGBA32F() + DestX + DestY * DestImageData.SizeX);
FLinearColor& DestColor = DestImageData.Access(DestX, DestY);
DestColor = FilteredColor;
}
});
}
// to switch conveniently between different texture wrapping modes for the mip map generation
// the template can optimize the inner loop using a constant AddressMode
static void GenerateSharpenedMipB8G8R8A8(
const FImageView2D& SourceImageData,
const FImageView2D& SourceImageData2, // Only used with volume texture.
FImageView2D& DestImageData,
EMipGenAddressMode AddressMode,
bool bDitherMipMapAlpha,
FVector4 AlphaCoverages,
FVector4 AlphaThresholds,
const FImageKernel2D &Kernel,
uint32 ScaleFactor,
bool bSharpenWithoutColorShift
)
{
switch(AddressMode)
{
case MGTAM_Wrap:
GenerateSharpenedMipB8G8R8A8Templ<MGTAM_Wrap>(SourceImageData, DestImageData, bDitherMipMapAlpha, AlphaCoverages, AlphaThresholds, Kernel, ScaleFactor, bSharpenWithoutColorShift);
break;
case MGTAM_Clamp:
GenerateSharpenedMipB8G8R8A8Templ<MGTAM_Clamp>(SourceImageData, DestImageData, bDitherMipMapAlpha, AlphaCoverages, AlphaThresholds, Kernel, ScaleFactor, bSharpenWithoutColorShift);
break;
case MGTAM_BorderBlack:
GenerateSharpenedMipB8G8R8A8Templ<MGTAM_BorderBlack>(SourceImageData, DestImageData, bDitherMipMapAlpha, AlphaCoverages, AlphaThresholds, Kernel, ScaleFactor, bSharpenWithoutColorShift);
break;
default:
check(0);
}
// For volume texture, do the average between the 2.
if (SourceImageData2.IsValid())
{
FImage Temp(DestImageData.SizeX, DestImageData.SizeY, 1, ERawImageFormat::RGBA32F);
FImageView2D TempImageData (Temp, 0);
switch(AddressMode)
{
case MGTAM_Wrap:
GenerateSharpenedMipB8G8R8A8Templ<MGTAM_Wrap>(SourceImageData2, TempImageData, bDitherMipMapAlpha, AlphaCoverages, AlphaThresholds, Kernel, ScaleFactor, bSharpenWithoutColorShift);
break;
case MGTAM_Clamp:
GenerateSharpenedMipB8G8R8A8Templ<MGTAM_Clamp>(SourceImageData2, TempImageData, bDitherMipMapAlpha, AlphaCoverages, AlphaThresholds, Kernel, ScaleFactor, bSharpenWithoutColorShift);
break;
case MGTAM_BorderBlack:
GenerateSharpenedMipB8G8R8A8Templ<MGTAM_BorderBlack>(SourceImageData2, TempImageData, bDitherMipMapAlpha, AlphaCoverages, AlphaThresholds, Kernel, ScaleFactor, bSharpenWithoutColorShift);
break;
default:
check(0);
}
const int32 NumColors = DestImageData.SizeX * DestImageData.SizeY;
for (int32 ColorIndex = 0; ColorIndex < NumColors; ++ColorIndex)
{
DestImageData.SliceColors[ColorIndex] += TempImageData.SliceColors[ColorIndex];
DestImageData.SliceColors[ColorIndex] *= .5;
}
}
}
// Update border texels after normal mip map generation to preserve the colors there (useful for particles and decals).
static void GenerateMipBorder(
const FImageView2D& SrcImageData,
FImageView2D& DestImageData
)
{
check( SrcImageData.SizeX == 2 * DestImageData.SizeX || DestImageData.SizeX == 1 );
check( SrcImageData.SizeY == 2 * DestImageData.SizeY || DestImageData.SizeY == 1 );
for ( int32 DestY = 0; DestY < DestImageData.SizeY; DestY++ )
{
for ( int32 DestX = 0; DestX < DestImageData.SizeX; )
{
FLinearColor FilteredColor(0, 0, 0, 0);
{
float WeightSum = 0.0f;
for ( int32 KernelY = 0; KernelY < 2; ++KernelY )
{
for ( int32 KernelX = 0; KernelX < 2; ++KernelX )
{
const int32 SourceX = DestX * 2 + KernelX;
const int32 SourceY = DestY * 2 + KernelY;
// only average the source border
if ( SourceX == 0 ||
SourceX == SrcImageData.SizeX - 1 ||
SourceY == 0 ||
SourceY == SrcImageData.SizeY - 1 )
{
FLinearColor Sample = LookupSourceMip<MGTAM_Wrap>( SrcImageData, SourceX, SourceY );
FilteredColor += Sample;
WeightSum += 1.0f;
}
}
}
FilteredColor /= WeightSum;
}
// Set the destination pixel.
//FLinearColor& DestColor = *(DestImageData.AsRGBA32F() + DestX + DestY * DestImageData.SizeX);
FLinearColor& DestColor = DestImageData.Access(DestX, DestY);
DestColor = FilteredColor;
++DestX;
if ( DestY > 0 &&
DestY < DestImageData.SizeY - 1 &&
DestX > 0 &&
DestX < DestImageData.SizeX - 1 )
{
// jump over the non border area
DestX += FMath::Max( 1, DestImageData.SizeX - 2 );
}
}
}
}
// how should be treat lookups outside of the image
static EMipGenAddressMode ComputeAdressMode(const FTextureBuildSettings& Settings)
{
EMipGenAddressMode AddressMode = MGTAM_Wrap;
if(Settings.bPreserveBorder)
{
AddressMode = Settings.bBorderColorBlack ? MGTAM_BorderBlack : MGTAM_Clamp;
}
return AddressMode;
}
static void GenerateTopMip(const FImage& SrcImage, FImage& DestImage, const FTextureBuildSettings& Settings)
{
EMipGenAddressMode AddressMode = ComputeAdressMode(Settings);
FImageKernel2D KernelDownsample;
// /2 as input resolution is same as output resolution and the settings assumed the output is half resolution
KernelDownsample.BuildSeparatableGaussWithSharpen( FMath::Max( 2u, Settings.SharpenMipKernelSize / 2 ), Settings.MipSharpening );
DestImage.Init(SrcImage.SizeX, SrcImage.SizeY, SrcImage.NumSlices, SrcImage.Format, SrcImage.GammaSpace);
for (int32 SliceIndex = 0; SliceIndex < SrcImage.NumSlices; ++SliceIndex)
{
FImageView2D SrcView((FImage&)SrcImage, SliceIndex);
FImageView2D DestView(DestImage, SliceIndex);
// generate DestImage: down sample with sharpening
GenerateSharpenedMipB8G8R8A8(
SrcView,
FImageView2D(),
DestView,
AddressMode,
Settings.bDitherMipMapAlpha,
FVector4(0, 0, 0, 0),
FVector4(0, 0, 0, 0),
KernelDownsample,
1,
Settings.bSharpenWithoutColorShift
);
}
}
/**
* Generate a full mip chain. The input mip chain must have one or more mips.
* @param Settings - Preprocess settings.
* @param BaseImage - An image that will serve as the source for the generation of the mip chain.
* @param OutMipChain - An array that will contain the resultant mip images. Generated mip levels are appended to the array.
* @param MipChainDepth - number of mip images to produce. Mips chain is finished when either a 1x1 mip is produced or 'MipChainDepth' images have been produced.
*/
static void GenerateMipChain(
const FTextureBuildSettings& Settings,
const FImage& BaseImage,
TArray<FImage> &OutMipChain,
uint32 MipChainDepth = MAX_uint32
)
{
check(BaseImage.Format == ERawImageFormat::RGBA32F);
const FImage& BaseMip = BaseImage;
const int32 SrcWidth = BaseMip.SizeX;
const int32 SrcHeight= BaseMip.SizeY;
const int32 SrcNumSlices = BaseMip.NumSlices;
const ERawImageFormat::Type ImageFormat = ERawImageFormat::RGBA32F;
FVector4 AlphaScales(1, 1, 1, 1);
FVector4 AlphaCoverages(0, 0, 0, 0);
// space for one source mip and one destination mip
FImage IntermediateSrc(SrcWidth, SrcHeight, SrcNumSlices, ImageFormat);
FImage IntermediateDst(FMath::Max<uint32>( 1, SrcWidth >> 1 ), FMath::Max<uint32>( 1, SrcHeight >> 1 ), Settings.bVolume ? FMath::Max<uint32>( 1, SrcNumSlices >> 1 ) : SrcNumSlices, ImageFormat);
// copy base mip
BaseMip.CopyTo(IntermediateSrc, ERawImageFormat::RGBA32F, EGammaSpace::Linear);
// Filtering kernels.
FImageKernel2D KernelSimpleAverage;
FImageKernel2D KernelDownsample;
KernelSimpleAverage.BuildSeparatableGaussWithSharpen( 2 );
KernelDownsample.BuildSeparatableGaussWithSharpen( Settings.SharpenMipKernelSize, Settings.MipSharpening );
//@TODO : add a true 3D kernel.
EMipGenAddressMode AddressMode = ComputeAdressMode(Settings);
bool bReDrawBorder = false;
if( Settings.bPreserveBorder )
{
bReDrawBorder = !Settings.bBorderColorBlack;
}
// Calculate alpha coverage value to preserve along mip chain
if (Settings.AlphaCoverageThresholds != FVector4(0,0,0,0))
{
FImageView2D IntermediateSrcView(IntermediateSrc, 0);
switch (AddressMode)
{
case MGTAM_Wrap:
AlphaCoverages = ComputeAlphaCoverage<MGTAM_Wrap>(Settings.AlphaCoverageThresholds, AlphaScales, IntermediateSrcView);
break;
case MGTAM_Clamp:
AlphaCoverages = ComputeAlphaCoverage<MGTAM_Clamp>(Settings.AlphaCoverageThresholds, AlphaScales, IntermediateSrcView);
break;
case MGTAM_BorderBlack:
AlphaCoverages = ComputeAlphaCoverage<MGTAM_BorderBlack>(Settings.AlphaCoverageThresholds, AlphaScales, IntermediateSrcView);
break;
default:
check(0);
}
}
// Generate mips
for (; MipChainDepth != 0 ; --MipChainDepth)
{
FImage& DestImage = *new(OutMipChain) FImage(IntermediateDst.SizeX, IntermediateDst.SizeY, IntermediateDst.NumSlices, ImageFormat);
for (int32 SliceIndex = 0; SliceIndex < IntermediateDst.NumSlices; ++SliceIndex)
{
const int32 SrcSliceIndex = Settings.bVolume ? (SliceIndex * 2) : SliceIndex;
FImageView2D IntermediateSrcView(IntermediateSrc, SrcSliceIndex);
FImageView2D IntermediateSrcView2 = Settings.bVolume ? FImageView2D(IntermediateSrc, SrcSliceIndex + 1) : FImageView2D(); // Volume texture mips take 2 slices
FImageView2D DestView(DestImage, SliceIndex);
FImageView2D IntermediateDstView(IntermediateDst, SliceIndex);
GenerateSharpenedMipB8G8R8A8(
IntermediateSrcView,
IntermediateSrcView2,
DestView,
AddressMode,
Settings.bDitherMipMapAlpha,
AlphaCoverages,
Settings.AlphaCoverageThresholds,
KernelDownsample,
2,
Settings.bSharpenWithoutColorShift
);
// generate IntermediateDstImage:
if ( Settings.bDownsampleWithAverage )
{
// down sample without sharpening for the next iteration
GenerateSharpenedMipB8G8R8A8(
IntermediateSrcView,
IntermediateSrcView2,
IntermediateDstView,
AddressMode,
Settings.bDitherMipMapAlpha,
AlphaCoverages,
Settings.AlphaCoverageThresholds,
KernelSimpleAverage,
2,
Settings.bSharpenWithoutColorShift
);
}
}
if ( Settings.bDownsampleWithAverage == false )
{
FMemory::Memcpy( IntermediateDst.AsRGBA32F(), DestImage.AsRGBA32F(),
IntermediateDst.SizeX * IntermediateDst.SizeY * IntermediateDst.NumSlices * sizeof(FLinearColor) );
}
if ( bReDrawBorder )
{
for (int32 SliceIndex = 0; SliceIndex < IntermediateDst.NumSlices; ++SliceIndex)
{
FImageView2D IntermediateSrcView(IntermediateSrc, SliceIndex);
FImageView2D DestView(DestImage, SliceIndex);
FImageView2D IntermediateDstView(IntermediateDst, SliceIndex);
GenerateMipBorder( IntermediateSrcView, DestView );
GenerateMipBorder( IntermediateSrcView, IntermediateDstView );
}
}
// Once we've created mip-maps down to 1x1, we're done.
if ( IntermediateDst.SizeX == 1 && IntermediateDst.SizeY == 1 && (!Settings.bVolume || IntermediateDst.NumSlices == 1))
{
break;
}
// last destination becomes next source
FMemory::Memcpy(IntermediateSrc.AsRGBA32F(), IntermediateDst.AsRGBA32F(),
IntermediateDst.SizeX * IntermediateDst.SizeY * IntermediateDst.NumSlices * sizeof(FLinearColor));
// Sizes for the next iteration.
IntermediateSrc.SizeX = FMath::Max<uint32>( 1, IntermediateSrc.SizeX >> 1 );
IntermediateSrc.SizeY = FMath::Max<uint32>( 1, IntermediateSrc.SizeY >> 1 );
IntermediateSrc.NumSlices = Settings.bVolume ? FMath::Max<uint32>( 1, IntermediateSrc.NumSlices >> 1 ) : IntermediateSrc.NumSlices;
IntermediateDst.SizeX = FMath::Max<uint32>( 1, IntermediateDst.SizeX >> 1 );
IntermediateDst.SizeY = FMath::Max<uint32>( 1, IntermediateDst.SizeY >> 1 );
IntermediateDst.NumSlices = Settings.bVolume ? FMath::Max<uint32>( 1, IntermediateDst.NumSlices >> 1 ) : IntermediateDst.NumSlices;
}
}
/*------------------------------------------------------------------------------
Angular Filtering for HDR Cubemaps.
------------------------------------------------------------------------------*/
/**
* View in to an image that allows access by converting a direction to longitude and latitude.
*/
struct FImageViewLongLat
{
/** Image colors. */
FLinearColor* ImageColors;
/** Width of the image. */
int32 SizeX;
/** Height of the image. */
int32 SizeY;
/** Initialization constructor. */
explicit FImageViewLongLat(FImage& Image)
{
ImageColors = Image.AsRGBA32F();
SizeX = Image.SizeX;
SizeY = Image.SizeY;
}
/** Wraps X around W. */
static void WrapTo(int32& X, int32 W)
{
X = X % W;
if(X < 0)
{
X += W;
}
}
/** Const access to a texel. */
FLinearColor Access(int32 X, int32 Y) const
{
return ImageColors[X + Y * SizeX];
}
/** Makes a filtered lookup. */
FLinearColor LookupFiltered(float X, float Y) const
{
int32 X0 = (int32)floor(X);
int32 Y0 = (int32)floor(Y);
float FracX = X - X0;
float FracY = Y - Y0;
int32 X1 = X0 + 1;
int32 Y1 = Y0 + 1;
WrapTo(X0, SizeX);
WrapTo(X1, SizeX);
Y0 = FMath::Clamp(Y0, 0, (int32)(SizeY - 1));
Y1 = FMath::Clamp(Y1, 0, (int32)(SizeY - 1));
FLinearColor CornerRGB00 = Access(X0, Y0);
FLinearColor CornerRGB10 = Access(X1, Y0);
FLinearColor CornerRGB01 = Access(X0, Y1);
FLinearColor CornerRGB11 = Access(X1, Y1);
FLinearColor CornerRGB0 = FMath::Lerp(CornerRGB00, CornerRGB10, FracX);
FLinearColor CornerRGB1 = FMath::Lerp(CornerRGB01, CornerRGB11, FracX);
return FMath::Lerp(CornerRGB0, CornerRGB1, FracY);
}
/** Makes a filtered lookup using a direction. */
FLinearColor LookupLongLat(FVector NormalizedDirection) const
{
// see http://gl.ict.usc.edu/Data/HighResProbes
// latitude-longitude panoramic format = equirectangular mapping
float X = (1 + atan2(NormalizedDirection.X, - NormalizedDirection.Z) / PI) / 2 * SizeX;
float Y = acos(NormalizedDirection.Y) / PI * SizeY;
return LookupFiltered(X, Y);
}
};
// transform world space vector to a space relative to the face
static FVector TransformSideToWorldSpace(uint32 CubemapFace, FVector InDirection)
{
float x = InDirection.X, y = InDirection.Y, z = InDirection.Z;
FVector Ret = FVector(0, 0, 0);
// see http://msdn.microsoft.com/en-us/library/bb204881(v=vs.85).aspx
switch(CubemapFace)
{
case 0: Ret = FVector(+z, -y, -x); break;
case 1: Ret = FVector(-z, -y, +x); break;
case 2: Ret = FVector(+x, +z, +y); break;
case 3: Ret = FVector(+x, -z, -y); break;
case 4: Ret = FVector(+x, -y, +z); break;
case 5: Ret = FVector(-x, -y, -z); break;
default:
checkSlow(0);
}
// this makes it with the Unreal way (z and y are flipped)
return FVector(Ret.X, Ret.Z, Ret.Y);
}
// transform vector relative to the face to world space
static FVector TransformWorldToSideSpace(uint32 CubemapFace, FVector InDirection)
{
// undo Unreal way (z and y are flipped)
float x = InDirection.X, y = InDirection.Z, z = InDirection.Y;
FVector Ret = FVector(0, 0, 0);
// see http://msdn.microsoft.com/en-us/library/bb204881(v=vs.85).aspx
switch(CubemapFace)
{
case 0: Ret = FVector(-z, -y, +x); break;
case 1: Ret = FVector(+z, -y, -x); break;
case 2: Ret = FVector(+x, +z, +y); break;
case 3: Ret = FVector(+x, -z, -y); break;
case 4: Ret = FVector(+x, -y, +z); break;
case 5: Ret = FVector(-x, -y, -z); break;
default:
checkSlow(0);
}
return Ret;
}
FVector ComputeSSCubeDirectionAtTexelCenter(uint32 x, uint32 y, float InvSideExtent)
{
// center of the texels
FVector DirectionSS((x + 0.5f) * InvSideExtent * 2 - 1, (y + 0.5f) * InvSideExtent * 2 - 1, 1);
DirectionSS.Normalize();
return DirectionSS;
}
static FVector ComputeWSCubeDirectionAtTexelCenter(uint32 CubemapFace, uint32 x, uint32 y, float InvSideExtent)
{
FVector DirectionSS = ComputeSSCubeDirectionAtTexelCenter(x, y, InvSideExtent);
FVector DirectionWS = TransformSideToWorldSpace(CubemapFace, DirectionSS);
return DirectionWS;
}
static int32 ComputeLongLatCubemapExtents(const FImage& SrcImage, const int32 MaxCubemapTextureResolution)
{
return FMath::Clamp(1 << FMath::FloorLog2(SrcImage.SizeX / 2), 32, MaxCubemapTextureResolution);
}
/**
* Generates the base cubemap mip from a longitude-latitude 2D image.
* @param OutMip - The output mip.
* @param SrcImage - The source longlat image.
*/
static void GenerateBaseCubeMipFromLongitudeLatitude2D(FImage* OutMip, const FImage& SrcImage, const int32 MaxCubemapTextureResolution)
{
FImage LongLatImage;
SrcImage.CopyTo(LongLatImage, ERawImageFormat::RGBA32F, EGammaSpace::Linear);
FImageViewLongLat LongLatView(LongLatImage);
// TODO_TEXTURE: Expose target size to user.
int32 Extent = ComputeLongLatCubemapExtents(LongLatImage, MaxCubemapTextureResolution);
float InvExtent = 1.0f / Extent;
OutMip->Init(Extent, Extent, 6, ERawImageFormat::RGBA32F, EGammaSpace::Linear);
for(uint32 Face = 0; Face < 6; ++Face)
{
FImageView2D MipView(*OutMip, Face);
for(int32 y = 0; y < Extent; ++y)
{
for(int32 x = 0; x < Extent; ++x)
{
FVector DirectionWS = ComputeWSCubeDirectionAtTexelCenter(Face, x, y, InvExtent);
MipView.Access(x, y) = LongLatView.LookupLongLat(DirectionWS);
}
}
}
}
class FTexelProcessor
{
public:
// @param InConeAxisSS - normalized, in side space
// @param TexelAreaArray - precomputed area of each texel for correct weighting
FTexelProcessor(const FVector& InConeAxisSS, float ConeAngle, const FLinearColor* InSideData, const float* InTexelAreaArray, uint32 InFullExtent)
: ConeAxisSS(InConeAxisSS)
, AccumulatedColor(0, 0, 0, 0)
, SideData(InSideData)
, TexelAreaArray(InTexelAreaArray)
, FullExtent(InFullExtent)
{
ConeAngleSin = sinf(ConeAngle);
ConeAngleCos = cosf(ConeAngle);
// *2 as the position is from -1 to 1
// / InFullExtent as x and y is in the range 0..InFullExtent-1
PositionToWorldScale = 2.0f / InFullExtent;
InvFullExtent = 1.0f / FullExtent;
// examples: 0 to diffuse convolution, 0.95f for glossy
DirDot = FMath::Min(FMath::Cos(ConeAngle), 0.9999f);
InvDirOneMinusDot = 1.0f / (1.0f - DirDot);
// precomputed sqrt(2.0f * 2.0f + 2.0f * 2.0f)
float Sqrt8 = 2.8284271f;
RadiusToWorldScale = Sqrt8 / (float)InFullExtent;
}
// @return true: yes, traverse deeper, false: not relevant
bool TestIfRelevant(uint32 x, uint32 y, uint32 LocalExtent) const
{
float HalfExtent = LocalExtent * 0.5f;
float U = (x + HalfExtent) * PositionToWorldScale - 1.0f;
float V = (y + HalfExtent) * PositionToWorldScale - 1.0f;
float SphereRadius = RadiusToWorldScale * LocalExtent;
FVector SpherePos(U, V, 1);
return FMath::SphereConeIntersection(SpherePos, SphereRadius, ConeAxisSS, ConeAngleSin, ConeAngleCos);
}
void Process(uint32 x, uint32 y)
{
const FLinearColor* In = &SideData[x + y * FullExtent];
FVector DirectionSS = ComputeSSCubeDirectionAtTexelCenter(x, y, InvFullExtent);
float DotValue = ConeAxisSS | DirectionSS;
if(DotValue > DirDot)
{
// 0..1, 0=at kernel border..1=at kernel center
float KernelWeight = 1.0f - (1.0f - DotValue) * InvDirOneMinusDot;
// apply smoothstep function (softer, less linear result)
KernelWeight = KernelWeight * KernelWeight * (3 - 2 * KernelWeight);
float AreaCompensation = TexelAreaArray[x + y * FullExtent];
// AreaCompensation would be need for correctness but seems it has a but
// as it looks much better (no seam) without, the effect is minor so it's deactivated for now.
// float Weight = KernelWeight * AreaCompensation;
float Weight = KernelWeight;
AccumulatedColor.R += Weight * In->R;
AccumulatedColor.G += Weight * In->G;
AccumulatedColor.B += Weight * In->B;
AccumulatedColor.A += Weight;
}
}
// normalized, in side space
FVector ConeAxisSS;
FLinearColor AccumulatedColor;
// cached for better performance
float ConeAngleSin;
float ConeAngleCos;
float PositionToWorldScale;
float RadiusToWorldScale;
float InvFullExtent;
// 0 to diffuse convolution, 0.95f for glossy
float DirDot;
float InvDirOneMinusDot;
/** [x + y * FullExtent] */
const FLinearColor* SideData;
const float* TexelAreaArray;
uint32 FullExtent;
};
template <class TVisitor>
void TCubemapSideRasterizer(TVisitor &TexelProcessor, int32 x, uint32 y, uint32 Extent)
{
if(Extent > 1)
{
if(!TexelProcessor.TestIfRelevant(x, y, Extent))
{
return;
}
Extent /= 2;
TCubemapSideRasterizer(TexelProcessor, x, y, Extent);
TCubemapSideRasterizer(TexelProcessor, x + Extent, y, Extent);
TCubemapSideRasterizer(TexelProcessor, x, y + Extent, Extent);
TCubemapSideRasterizer(TexelProcessor, x + Extent, y + Extent, Extent);
}
else
{
TexelProcessor.Process(x, y);
}
}
static FLinearColor IntegrateAngularArea(FImage& Image, FVector FilterDirectionWS, float ConeAngle, const float* TexelAreaArray)
{
// Alpha channel is used to renormalize later
FLinearColor ret(0, 0, 0, 0);
int32 Extent = Image.SizeX;
for(uint32 Face = 0; Face < 6; ++Face)
{
FImageView2D ImageView(Image, Face);
FVector FilterDirectionSS = TransformWorldToSideSpace(Face, FilterDirectionWS);
FTexelProcessor Processor(FilterDirectionSS, ConeAngle, &ImageView.Access(0,0), TexelAreaArray, Extent);
// recursively split the (0,0)-(Extent-1,Extent-1), tests for intersection and processes only colors inside
TCubemapSideRasterizer(Processor, 0, 0, Extent);
ret += Processor.AccumulatedColor;
}
if(ret.A != 0)
{
float Inv = 1.0f / ret.A;
ret.R *= Inv;
ret.G *= Inv;
ret.B *= Inv;
}
else
{
// should not happen
// checkSlow(0);
}
ret.A = 0;
return ret;
}
// @return 2 * computed triangle area
static inline float TriangleArea2_3D(FVector A, FVector B, FVector C)
{
return ((A-B) ^ (C-B)).Size();
}
static inline float ComputeTexelArea(uint32 x, uint32 y, float InvSideExtentMul2)
{
float fU = x * InvSideExtentMul2 - 1;
float fV = y * InvSideExtentMul2 - 1;
FVector CornerA = FVector(fU, fV, 1);
FVector CornerB = FVector(fU + InvSideExtentMul2, fV, 1);
FVector CornerC = FVector(fU, fV + InvSideExtentMul2, 1);
FVector CornerD = FVector(fU + InvSideExtentMul2, fV + InvSideExtentMul2, 1);
CornerA.Normalize();
CornerB.Normalize();
CornerC.Normalize();
CornerD.Normalize();
return TriangleArea2_3D(CornerA, CornerB, CornerC) + TriangleArea2_3D(CornerC, CornerB, CornerD) * 0.5f;
}
/**
* Generate a mip using angular filtering.
* @param DestMip - The filtered mip.
* @param SrcMip - The source mip which will be filtered.
* @param ConeAngle - The cone angle with which to filter.
*/
static void GenerateAngularFilteredMip(FImage* DestMip, FImage& SrcMip, float ConeAngle)
{
int32 MipExtent = DestMip->SizeX;
float MipInvSideExtent = 1.0f / MipExtent;
TArray<float> TexelAreaArray;
TexelAreaArray.AddUninitialized(SrcMip.SizeX * SrcMip.SizeY);
// precompute the area size for one face (is the same for each face)
for(int32 y = 0; y < SrcMip.SizeY; ++y)
{
for(int32 x = 0; x < SrcMip.SizeX; ++x)
{
TexelAreaArray[x + y * SrcMip.SizeX] = ComputeTexelArea(x, y, MipInvSideExtent * 2);
}
}
// We start getting gains running threaded upwards of sizes >= 128
if (SrcMip.SizeX >= 128)
{
// Quick workaround: Do a thread per mip
struct FAsyncGenerateMipsPerFaceWorker : public FNonAbandonableTask
{
int32 Face;
FImage* DestMip;
int32 Extent;
float ConeAngle;
const float* TexelAreaArray;
FImage* SrcMip;
FAsyncGenerateMipsPerFaceWorker(int32 InFace, FImage* InDestMip, int32 InExtent, float InConeAngle, const float* InTexelAreaArray, FImage* InSrcMip) :
Face(InFace),
DestMip(InDestMip),
Extent(InExtent),
ConeAngle(InConeAngle),
TexelAreaArray(InTexelAreaArray),
SrcMip(InSrcMip)
{
}
void DoWork()
{
const float InvSideExtent = 1.0f / Extent;
FImageView2D DestMipView(*DestMip, Face);
for (int32 y = 0; y < Extent; ++y)
{
for (int32 x = 0; x < Extent; ++x)
{
FVector DirectionWS = ComputeWSCubeDirectionAtTexelCenter(Face, x, y, InvSideExtent);
DestMipView.Access(x, y) = IntegrateAngularArea(*SrcMip, DirectionWS, ConeAngle, TexelAreaArray);
}
}
}
FORCEINLINE TStatId GetStatId() const
{
RETURN_QUICK_DECLARE_CYCLE_STAT(FAsyncGenerateMipsPerFaceWorker, STATGROUP_ThreadPoolAsyncTasks);
}
};
typedef FAsyncTask<FAsyncGenerateMipsPerFaceWorker> FAsyncGenerateMipsPerFaceTask;
TIndirectArray<FAsyncGenerateMipsPerFaceTask> AsyncTasks;
for (int32 Face = 0; Face < 6; ++Face)
{
auto* AsyncTask = new(AsyncTasks) FAsyncGenerateMipsPerFaceTask(Face, DestMip, MipExtent, ConeAngle, TexelAreaArray.GetData(), &SrcMip);
AsyncTask->StartBackgroundTask();
}
for (int32 TaskIndex = 0; TaskIndex < AsyncTasks.Num(); ++TaskIndex)
{
auto& AsyncTask = AsyncTasks[TaskIndex];
AsyncTask.EnsureCompletion();
}
}
else
{
for (int32 Face = 0; Face < 6; ++Face)
{
FImageView2D DestMipView(*DestMip, Face);
for (int32 y = 0; y < MipExtent; ++y)
{
for (int32 x = 0; x < MipExtent; ++x)
{
FVector DirectionWS = ComputeWSCubeDirectionAtTexelCenter(Face, x, y, MipInvSideExtent);
DestMipView.Access(x, y) = IntegrateAngularArea(SrcMip, DirectionWS, ConeAngle, TexelAreaArray.GetData());
}
}
}
}
}
/**
* Generates angularly filtered mips.
* @param InOutMipChain - The mip chain to angularly filter.
* @param NumMips - The number of mips the chain should have.
* @param DiffuseConvolveMipLevel - The mip level that contains the diffuse convolution.
*/
static void GenerateAngularFilteredMips(TArray<FImage>& InOutMipChain, int32 NumMips, uint32 DiffuseConvolveMipLevel)
{
TArray<FImage> SrcMipChain;
Exchange(SrcMipChain, InOutMipChain);
InOutMipChain.Empty(NumMips);
// Generate simple averaged mips to accelerate angular filtering.
for (int32 MipIndex = SrcMipChain.Num(); MipIndex < NumMips; ++MipIndex)
{
FImage& BaseMip = SrcMipChain[MipIndex - 1];
int32 BaseExtent = BaseMip.SizeX;
int32 MipExtent = FMath::Max(BaseExtent >> 1, 1);
FImage* Mip = new(SrcMipChain) FImage(MipExtent, MipExtent, BaseMip.NumSlices, BaseMip.Format);
for(int32 Face = 0; Face < 6; ++Face)
{
FImageView2D BaseMipView(BaseMip, Face);
FImageView2D MipView(*Mip, Face);
for(int32 y = 0; y < MipExtent; ++y)
{
for(int32 x = 0; x < MipExtent; ++x)
{
FLinearColor Sum = (
BaseMipView.Access(x*2, y*2) +
BaseMipView.Access(x*2+1, y*2) +
BaseMipView.Access(x*2, y*2+1) +
BaseMipView.Access(x*2+1, y*2+1)
) * 0.25f;
MipView.Access(x,y) = Sum;
}
}
}
}
int32 Extent = 1 << (NumMips - 1);
int32 BaseExtent = Extent;
for (int32 i = 0; i < NumMips; ++i)
{
// 0:top mip 1:lowest mip = diffuse convolve
float NormalizedMipLevel = i / (float)(NumMips - DiffuseConvolveMipLevel);
float AdjustedMipLevel = NormalizedMipLevel * NumMips;
float NormalizedWidth = BaseExtent * FMath::Pow(2.0f, -AdjustedMipLevel);
float TexelSize = 1.0f / NormalizedWidth;
// 0.001f:sharp .. PI/2: diffuse convolve
// all lower mips are used for diffuse convolve
// above that the angle blends from sharp to diffuse convolved version
float ConeAngle = PI / 2.0f * TexelSize;
// restrict to reasonable range
ConeAngle = FMath::Clamp(ConeAngle, 0.002f, (float)PI / 2.0f);
UE_LOG(LogTextureCompressor, Verbose, TEXT("GenerateAngularFilteredMips %f %f %f %f %f"), NormalizedMipLevel, AdjustedMipLevel, NormalizedWidth, TexelSize, ConeAngle * 180 / PI);
// 0:normal, -1:4x faster, +1:4 times slower but more precise, -2, 2 ...
float QualityBias = 3.0f;
// defined to result in a area of 1.0f (NormalizedArea)
// optimized = 0.5f * FMath::Sqrt(1.0f / PI);
float SphereRadius = 0.28209478f;
float SegmentHeight = SphereRadius * (1.0f - FMath::Cos(ConeAngle));
// compute SphereSegmentArea
float AreaCoveredInNormalizedArea = 2 * PI * SphereRadius * SegmentHeight;
checkSlow(AreaCoveredInNormalizedArea <= 0.5f);
// unoptimized
// float FloatInputMip = FMath::Log2(FMath::Sqrt(AreaCoveredInNormalizedArea)) + InputMipCount - QualityBias;
// optimized
float FloatInputMip = 0.5f * FMath::Log2(AreaCoveredInNormalizedArea) + NumMips - QualityBias;
uint32 InputMip = FMath::Clamp(FMath::TruncToInt(FloatInputMip), 0, NumMips - 1);
FImage* Mip = new(InOutMipChain) FImage(Extent, Extent, 6, ERawImageFormat::RGBA32F);
GenerateAngularFilteredMip(Mip, SrcMipChain[InputMip], ConeAngle);
Extent = FMath::Max(Extent >> 1, 1);
}
}
/*------------------------------------------------------------------------------
Image Processing.
------------------------------------------------------------------------------*/
/**
* Adjusts the colors of the image using the specified settings
*
* @param Image Image to adjust
* @param InBuildSettings Image build settings
*/
static void AdjustImageColors( FImage& Image, const FTextureBuildSettings& InBuildSettings )
{
const FColorAdjustmentParameters& InParams = InBuildSettings.ColorAdjustment;
check( Image.SizeX > 0 && Image.SizeY > 0 );
if( !FMath::IsNearlyEqual( InParams.AdjustBrightness, 1.0f, (float)KINDA_SMALL_NUMBER ) ||
!FMath::IsNearlyEqual( InParams.AdjustBrightnessCurve, 1.0f, (float)KINDA_SMALL_NUMBER ) ||
!FMath::IsNearlyEqual( InParams.AdjustSaturation, 1.0f, (float)KINDA_SMALL_NUMBER ) ||
!FMath::IsNearlyEqual( InParams.AdjustVibrance, 0.0f, (float)KINDA_SMALL_NUMBER ) ||
!FMath::IsNearlyEqual( InParams.AdjustRGBCurve, 1.0f, (float)KINDA_SMALL_NUMBER ) ||
!FMath::IsNearlyEqual( InParams.AdjustHue, 0.0f, (float)KINDA_SMALL_NUMBER ) ||
!FMath::IsNearlyEqual( InParams.AdjustMinAlpha, 0.0f, (float)KINDA_SMALL_NUMBER ) ||
!FMath::IsNearlyEqual( InParams.AdjustMaxAlpha, 1.0f, (float)KINDA_SMALL_NUMBER ) ||
InBuildSettings.bChromaKeyTexture )
{
const FLinearColor ChromaKeyTarget = InBuildSettings.ChromaKeyColor;
const float ChromaKeyThreshold = InBuildSettings.ChromaKeyThreshold + SMALL_NUMBER;
const int32 NumPixels = Image.SizeX * Image.SizeY * Image.NumSlices;
FLinearColor* ImageColors = Image.AsRGBA32F();
for( int32 CurPixelIndex = 0; CurPixelIndex < NumPixels; ++CurPixelIndex )
{
const FLinearColor OriginalColorRaw = ImageColors[ CurPixelIndex ];
FLinearColor OriginalColor = OriginalColorRaw;
if (InBuildSettings.bChromaKeyTexture && (OriginalColor.Equals(ChromaKeyTarget, ChromaKeyThreshold)))
{
OriginalColor = FLinearColor::Transparent;
}
// Convert to HSV
FLinearColor HSVColor = OriginalColor.LinearRGBToHSV();
float& PixelHue = HSVColor.R;
float& PixelSaturation = HSVColor.G;
float& PixelValue = HSVColor.B;
// Apply brightness adjustment
PixelValue *= InParams.AdjustBrightness;
// Apply brightness power adjustment
if( !FMath::IsNearlyEqual( InParams.AdjustBrightnessCurve, 1.0f, (float)KINDA_SMALL_NUMBER ) && InParams.AdjustBrightnessCurve != 0.0f )
{
// Raise HSV.V to the specified power
PixelValue = FMath::Pow( PixelValue, InParams.AdjustBrightnessCurve );
}
// Apply "vibrance" adjustment
if( !FMath::IsNearlyZero( InParams.AdjustVibrance, (float)KINDA_SMALL_NUMBER ) )
{
const float SatRaisePow = 5.0f;
const float InvSatRaised = FMath::Pow( 1.0f - PixelSaturation, SatRaisePow );
const float ClampedVibrance = FMath::Clamp( InParams.AdjustVibrance, 0.0f, 1.0f );
const float HalfVibrance = ClampedVibrance * 0.5f;
const float SatProduct = HalfVibrance * InvSatRaised;
PixelSaturation += SatProduct;
}
// Apply saturation adjustment
PixelSaturation *= InParams.AdjustSaturation;
// Apply hue adjustment
PixelHue += InParams.AdjustHue;
// Clamp HSV values
{
PixelHue = FMath::Fmod( PixelHue, 360.0f );
if( PixelHue < 0.0f )
{
// Keep the hue value positive as HSVToLinearRGB prefers that
PixelHue += 360.0f;
}
PixelSaturation = FMath::Clamp( PixelSaturation, 0.0f, 1.0f );
PixelValue = FMath::Clamp( PixelValue, 0.0f, 1.0f );
}
// Convert back to a linear color
FLinearColor LinearColor = HSVColor.HSVToLinearRGB();
// Apply RGB curve adjustment (linear space)
if( !FMath::IsNearlyEqual( InParams.AdjustRGBCurve, 1.0f, (float)KINDA_SMALL_NUMBER ) && InParams.AdjustRGBCurve != 0.0f )
{
LinearColor.R = FMath::Pow( LinearColor.R, InParams.AdjustRGBCurve );
LinearColor.G = FMath::Pow( LinearColor.G, InParams.AdjustRGBCurve );
LinearColor.B = FMath::Pow( LinearColor.B, InParams.AdjustRGBCurve );
}
// Remap the alpha channel
LinearColor.A = FMath::Lerp(InParams.AdjustMinAlpha, InParams.AdjustMaxAlpha, OriginalColor.A);
ImageColors[ CurPixelIndex ] = LinearColor;
}
}
}
/**
* Compute the alpha channel how BokehDOF needs it setup
*
* @param Image Image to adjust
*/
static void ComputeBokehAlpha(FImage& Image)
{
check( Image.SizeX > 0 && Image.SizeY > 0 );
const int32 NumPixels = Image.SizeX * Image.SizeY * Image.NumSlices;
FLinearColor* ImageColors = Image.AsRGBA32F();
// compute LinearAverage
FLinearColor LinearAverage;
{
FLinearColor LinearSum(0, 0, 0, 0);
for( int32 CurPixelIndex = 0; CurPixelIndex < NumPixels; ++CurPixelIndex )
{
LinearSum += ImageColors[ CurPixelIndex ];
}
LinearAverage = LinearSum / (float)NumPixels;
}
FLinearColor Scale(1, 1, 1, 1);
// we want to normalize the image to have 0.5 as average luminance, this is assuming clamping doesn't happen (can happen when using a very small Bokeh shape)
{
float RGBLum = (LinearAverage.R + LinearAverage.G + LinearAverage.B) / 3.0f;
// ideally this would be 1 but then some pixels would need to be >1 which is not supported for the textureformat we want to use.
// The value affects the occlusion computation of the BokehDOF
const float LumGoal = 0.25f;
// clamp to avoid division by 0
Scale *= LumGoal / FMath::Max(RGBLum, 0.001f);
}
{
for( int32 CurPixelIndex = 0; CurPixelIndex < NumPixels; ++CurPixelIndex )
{
const FLinearColor OriginalColor = ImageColors[ CurPixelIndex ];
// Convert to a linear color
FLinearColor LinearColor = OriginalColor * Scale;
float RGBLum = (LinearColor.R + LinearColor.G + LinearColor.B) / 3.0f;
LinearColor.A = FMath::Clamp(RGBLum, 0.0f, 1.0f);
ImageColors[ CurPixelIndex ] = LinearColor;
}
}
}
/**
* Replicates the contents of the red channel to the green, blue, and alpha channels.
*/
static void ReplicateRedChannel( TArray<FImage>& InOutMipChain )
{
const uint32 MipCount = InOutMipChain.Num();
for ( uint32 MipIndex = 0; MipIndex < MipCount; ++MipIndex )
{
FImage& SrcMip = InOutMipChain[MipIndex];
FLinearColor* FirstColor = SrcMip.AsRGBA32F();
FLinearColor* LastColor = FirstColor + (SrcMip.SizeX * SrcMip.SizeY * SrcMip.NumSlices);
for ( FLinearColor* Color = FirstColor; Color < LastColor; ++Color )
{
*Color = FLinearColor( Color->R, Color->R, Color->R, Color->R );
}
}
}
/**
* Replicates the contents of the alpha channel to the red, green, and blue channels.
*/
static void ReplicateAlphaChannel( TArray<FImage>& InOutMipChain )
{
const uint32 MipCount = InOutMipChain.Num();
for ( uint32 MipIndex = 0; MipIndex < MipCount; ++MipIndex )
{
FImage& SrcMip = InOutMipChain[MipIndex];
FLinearColor* FirstColor = SrcMip.AsRGBA32F();
FLinearColor* LastColor = FirstColor + (SrcMip.SizeX * SrcMip.SizeY * SrcMip.NumSlices);
for ( FLinearColor* Color = FirstColor; Color < LastColor; ++Color )
{
*Color = FLinearColor( Color->A, Color->A, Color->A, Color->A );
}
}
}
/**
* Flips the contents of the green channel.
* @param InOutMipChain - The mip chain on which the green channel shall be flipped.
*/
static void FlipGreenChannel( FImage& Image )
{
FLinearColor* FirstColor = Image.AsRGBA32F();
FLinearColor* LastColor = FirstColor + (Image.SizeX * Image.SizeY * Image.NumSlices);
for ( FLinearColor* Color = FirstColor; Color < LastColor; ++Color )
{
Color->G = 1.0f - FMath::Clamp(Color->G, 0.0f, 1.0f);
}
}
/**
* Detects whether or not the image contains an alpha channel where at least one texel is != 255.
*/
static bool DetectAlphaChannel(const FImage& InImage)
{
// Uncompressed data is required to check for an alpha channel.
const FLinearColor* SrcColors = InImage.AsRGBA32F();
const FLinearColor* LastColor = SrcColors + (InImage.SizeX * InImage.SizeY * InImage.NumSlices);
while (SrcColors < LastColor)
{
if (SrcColors->A < (1.0f - SMALL_NUMBER))
{
return true;
}
++SrcColors;
}
return false;
}
float RoughnessToSpecularPower(float Roughness)
{
float Div = FMath::Pow(Roughness, 4);
// Roughness of 0 should result in a high specular power
float MaxSpecPower = 10000000000.0f;
Div = FMath::Max(Div, 2.0f / (MaxSpecPower + 2.0f));
return 2.0f / Div - 2.0f;
}
float SpecularPowerToRoughness(float SpecularPower)
{
float Out = FMath::Pow( SpecularPower * 0.5f + 1.0f, -0.25f );
return Out;
}
// @param CompositeTextureMode original type ECompositeTextureMode
void ApplyCompositeTexture(FImage& RoughnessSourceMips, const FImage& NormalSourceMips, uint8 CompositeTextureMode, float CompositePower)
{
check(RoughnessSourceMips.SizeX == NormalSourceMips.SizeX);
check(RoughnessSourceMips.SizeY == NormalSourceMips.SizeY);
FLinearColor* FirstColor = RoughnessSourceMips.AsRGBA32F();
const FLinearColor* NormalColors = NormalSourceMips.AsRGBA32F();
FLinearColor* LastColor = FirstColor + (RoughnessSourceMips.SizeX * RoughnessSourceMips.SizeY * RoughnessSourceMips.NumSlices);
for ( FLinearColor* Color = FirstColor; Color < LastColor; ++Color, ++NormalColors )
{
FVector Normal = FVector(NormalColors->R * 2.0f - 1.0f, NormalColors->G * 2.0f - 1.0f, NormalColors->B * 2.0f - 1.0f);
// to prevent crash for unknown CompositeTextureMode
float Dummy;
float* RefValue = &Dummy;
switch((ECompositeTextureMode)CompositeTextureMode)
{
case CTM_NormalRoughnessToRed:
RefValue = &Color->R;
break;
case CTM_NormalRoughnessToGreen:
RefValue = &Color->G;
break;
case CTM_NormalRoughnessToBlue:
RefValue = &Color->B;
break;
case CTM_NormalRoughnessToAlpha:
RefValue = &Color->A;
break;
default:
checkSlow(0);
}
// Toksvig estimation of variance
float LengthN = FMath::Min( Normal.Size(), 1.0f );
float Variance = ( 1.0f - LengthN ) / LengthN;
Variance = FMath::Max( 0.0f, Variance - 0.00004f );
Variance *= CompositePower;
float Roughness = *RefValue;
#if 0
float Power = RoughnessToSpecularPower( Roughness );
Power = Power / ( 1.0f + Variance * Power );
Roughness = SpecularPowerToRoughness( Power );
#else
// Refactored above to avoid divide by zero
float a = Roughness * Roughness;
float a2 = a * a;
float B = 2.0f * Variance * (a2 - 1.0f);
a2 = ( B - a2 ) / ( B - 1.0f );
Roughness = FMath::Pow( a2, 0.25f );
#endif
*RefValue = Roughness;
}
}
/*------------------------------------------------------------------------------
Image Compression.
------------------------------------------------------------------------------*/
/**
* Asynchronous compression, used for compressing mips simultaneously.
*/
class FAsyncCompressionWorker : public FNonAbandonableTask
{
public:
/**
* Initializes the data and creates the async compression task.
*/
FAsyncCompressionWorker(const ITextureFormat* InTextureFormat, const FImage* InImage, const FTextureBuildSettings& InBuildSettings, bool bInImageHasAlphaChannel)
: TextureFormat(*InTextureFormat)
, SourceImage(*InImage)
, BuildSettings(InBuildSettings)
, bImageHasAlphaChannel(bInImageHasAlphaChannel)
, bCompressionResults(false)
{
}
/**
* Compresses the texture
*/
void DoWork()
{
bCompressionResults = TextureFormat.CompressImage(
SourceImage,
BuildSettings,
bImageHasAlphaChannel,
CompressedImage
);
}
FORCEINLINE TStatId GetStatId() const
{
RETURN_QUICK_DECLARE_CYCLE_STAT(FAsyncCompressionWorker, STATGROUP_ThreadPoolAsyncTasks);
}
bool GetCompressionResults(FCompressedImage2D& OutCompressedImage) const
{
OutCompressedImage = CompressedImage;
return bCompressionResults;
}
private:
/** Texture format interface with which to compress. */
const ITextureFormat& TextureFormat;
/** The image to compress. */
const FImage& SourceImage;
/** The resulting compressed image. */
FCompressedImage2D CompressedImage;
/** Build settings. */
FTextureBuildSettings BuildSettings;
/** true if the image has a non-white alpha channel. */
bool bImageHasAlphaChannel;
/** true if compression was successful. */
bool bCompressionResults;
};
typedef FAsyncTask<FAsyncCompressionWorker> FAsyncCompressionTask;
FTextureFormatCompressorCaps GetTextureFormatCaps(const FTextureBuildSettings& Settings)
{
ITargetPlatformManagerModule* TPM = GetTargetPlatformManager();
if (TPM)
{
const ITextureFormat* TextureFormat = TPM->FindTextureFormat(Settings.TextureFormatName);
if (TextureFormat != nullptr)
{
return TextureFormat->GetFormatCapabilities();
}
}
return FTextureFormatCompressorCaps();
}
// compress mip-maps in InMipChain and add mips to Texture, might alter the source content
static bool CompressMipChain(
const TArray<FImage>& MipChain,
const FTextureBuildSettings& Settings,
TArray<FCompressedImage2D>& OutMips
)
{
ITargetPlatformManagerModule* TPM = GetTargetPlatformManager();
if (TPM)
{
const ITextureFormat* TextureFormat = TPM->FindTextureFormat(Settings.TextureFormatName);
if (TextureFormat)
{
TIndirectArray<FAsyncCompressionTask> AsyncCompressionTasks;
const int32 MipCount = MipChain.Num();
const bool bImageHasAlphaChannel = DetectAlphaChannel(MipChain[0]);
const int32 MinAsyncCompressionSize = 128;
const bool bAllowParallelBuild = TextureFormat->AllowParallelBuild();
bool bCompressionSucceeded = true;
uint32 StartCycles = FPlatformTime::Cycles();
OutMips.Empty(MipCount);
for (int32 MipIndex = 0; MipIndex < MipCount; ++MipIndex)
{
const FImage& SrcMip = MipChain[MipIndex];
FCompressedImage2D& DestMip = *new(OutMips) FCompressedImage2D;
if (bAllowParallelBuild && FMath::Min(SrcMip.SizeX, SrcMip.SizeY) >= MinAsyncCompressionSize)
{
FAsyncCompressionTask* AsyncTask = new(AsyncCompressionTasks) FAsyncCompressionTask(
TextureFormat,
&SrcMip,
Settings,
bImageHasAlphaChannel
);
#if WITH_EDITOR
AsyncTask->StartBackgroundTask(GLargeThreadPool);
#else
AsyncTask->StartBackgroundTask();
#endif
}
else
{
bCompressionSucceeded = bCompressionSucceeded && TextureFormat->CompressImage(
SrcMip,
Settings,
bImageHasAlphaChannel,
DestMip
);
}
}
for (int32 TaskIndex = 0; TaskIndex < AsyncCompressionTasks.Num(); ++TaskIndex)
{
FAsyncCompressionTask& AsynTask = AsyncCompressionTasks[TaskIndex];
AsynTask.EnsureCompletion();
FCompressedImage2D& DestMip = OutMips[TaskIndex];
bCompressionSucceeded = bCompressionSucceeded && AsynTask.GetTask().GetCompressionResults(DestMip);
}
if (!bCompressionSucceeded)
{
OutMips.Empty();
}
uint32 EndCycles = FPlatformTime::Cycles();
UE_LOG(LogTextureCompressor,Verbose,TEXT("Compressed %dx%dx%d %s in %fms"),
MipChain[0].SizeX,
MipChain[0].SizeY,
MipChain[0].NumSlices,
*Settings.TextureFormatName.ToString(),
FPlatformTime::ToMilliseconds( EndCycles-StartCycles )
);
return bCompressionSucceeded;
}
else
{
UE_LOG(LogTextureCompressor, Warning,
TEXT("Failed to find compressor for texture format '%s'."),
*Settings.TextureFormatName.ToString()
);
return false;
}
}
UE_LOG(LogTextureCompressor, Warning,
TEXT("Failed to load target platform manager module. Unable to compress textures.")
);
return false;
}
// only useful for normal maps, fixed bad input (denormalized normals) and improved quality (quantization artifacts)
static void NormalizeMip(FImage& InOutMip)
{
const uint32 NumPixels = InOutMip.SizeX * InOutMip.SizeY * InOutMip.NumSlices;
FLinearColor* ImageColors = InOutMip.AsRGBA32F();
for(uint32 CurPixelIndex = 0; CurPixelIndex < NumPixels; ++CurPixelIndex)
{
FLinearColor& Color = ImageColors[CurPixelIndex];
FVector Normal = FVector(Color.R * 2.0f - 1.0f, Color.G * 2.0f - 1.0f, Color.B * 2.0f - 1.0f);
Normal = Normal.GetSafeNormal();
Color = FLinearColor(Normal.X * 0.5f + 0.5f, Normal.Y * 0.5f + 0.5f, Normal.Z * 0.5f + 0.5f, Color.A);
}
}
/**
* Texture compression module
*/
class FTextureCompressorModule : public ITextureCompressorModule
{
public:
FTextureCompressorModule()
#if PLATFORM_WINDOWS
: nvTextureToolsHandle(0)
#endif //PLATFORM_WINDOWS
{
}
virtual bool BuildTexture(
const TArray<FImage>& SourceMips,
const TArray<FImage>& AssociatedNormalSourceMips,
const FTextureBuildSettings& BuildSettings,
TArray<FCompressedImage2D>& OutTextureMips
)
{
TArray<FImage> IntermediateMipChain;
if(!BuildTextureMips(SourceMips, BuildSettings, IntermediateMipChain))
{
return false;
}
// apply roughness adjustment depending on normal map variation
if(AssociatedNormalSourceMips.Num())
{
// check AssociatedNormalSourceMips.Format;
TArray<FImage> IntermediateAssociatedNormalSourceMipChain;
FTextureBuildSettings DefaultSettings;
// helps to reduce aliasing further
DefaultSettings.MipSharpening = -4.0f;
DefaultSettings.SharpenMipKernelSize = 4;
DefaultSettings.bApplyKernelToTopMip = true;
// important to make accurate computation with normal length
DefaultSettings.bRenormalizeTopMip = true;
if(!BuildTextureMips(AssociatedNormalSourceMips, DefaultSettings, IntermediateAssociatedNormalSourceMipChain))
{
UE_LOG(LogTexture, Warning, TEXT("Failed to generate texture mips for composite texture"));
}
if(!ApplyCompositeTexture(IntermediateMipChain, IntermediateAssociatedNormalSourceMipChain, BuildSettings.CompositeTextureMode, BuildSettings.CompositePower))
{
UE_LOG(LogTexture, Warning, TEXT("Failed to apply composite texture"));
}
}
// Set the correct biased texture size so that the compressor understands the original source image size
// This is requires for platforms that may need to tile based on the original source texture size
BuildSettings.TopMipSize.X = IntermediateMipChain[0].SizeX;
BuildSettings.TopMipSize.Y = IntermediateMipChain[0].SizeY;
BuildSettings.VolumeSizeZ = BuildSettings.bVolume ? IntermediateMipChain[0].NumSlices : 1;
return CompressMipChain(IntermediateMipChain, BuildSettings, OutTextureMips);
}
// IModuleInterface implementation.
void StartupModule()
{
#if PLATFORM_WINDOWS
#if PLATFORM_64BITS
nvTextureToolsHandle = FPlatformProcess::GetDllHandle(TEXT("../../../Engine/Binaries/ThirdParty/nvTextureTools/Win64/nvtt_64.dll"));
#else //32-bit platform
nvTextureToolsHandle = FPlatformProcess::GetDllHandle(TEXT("../../../Engine/Binaries/ThirdParty/nvTextureTools/Win32/nvtt_.dll"));
#endif
#endif //PLATFORM_WINDOWS
}
void ShutdownModule()
{
#if PLATFORM_WINDOWS
FPlatformProcess::FreeDllHandle(nvTextureToolsHandle);
nvTextureToolsHandle = 0;
#endif
}
private:
#if PLATFORM_WINDOWS
// Handle to the nvtt dll
void* nvTextureToolsHandle;
#endif //PLATFORM_WINDOWS
bool BuildTextureMips(
const TArray<FImage>& InSourceMips,
const FTextureBuildSettings& BuildSettings,
TArray<FImage>& OutMipChain)
{
check(InSourceMips.Num());
check(InSourceMips[0].SizeX > 0 && InSourceMips[0].SizeY > 0 && InSourceMips[0].NumSlices > 0);
const FTextureFormatCompressorCaps CompressorCaps = GetTextureFormatCaps(BuildSettings);
// Identify long-lat cubemaps.
bool bLongLatCubemap = BuildSettings.bCubemap && InSourceMips[0].NumSlices == 1;
if (BuildSettings.bCubemap && InSourceMips[0].NumSlices != 6 && !bLongLatCubemap)
{
return false;
}
// Determine the maximum possible mip counts for source and dest.
const int32 MaxSourceMipCount = bLongLatCubemap ?
1 + FMath::CeilLogTwo(ComputeLongLatCubemapExtents(InSourceMips[0], BuildSettings.MaxTextureResolution)) :
1 + FMath::CeilLogTwo(FMath::Max3(InSourceMips[0].SizeX, InSourceMips[0].SizeY, BuildSettings.bVolume ? InSourceMips[0].NumSlices : 1));
const int32 MaxDestMipCount = 1 + FMath::CeilLogTwo(FMath::Min(CompressorCaps.MaxTextureDimension, BuildSettings.MaxTextureResolution));
// Determine the number of mips required by BuildSettings.
int32 NumOutputMips = (BuildSettings.MipGenSettings == TMGS_NoMipmaps) ? 1 : MaxSourceMipCount;
NumOutputMips = FMath::Min(NumOutputMips, MaxDestMipCount);
int32 NumSourceMips = InSourceMips.Num();
if (BuildSettings.MipGenSettings != TMGS_LeaveExistingMips || bLongLatCubemap)
{
NumSourceMips = 1;
}
TArray<FImage> PaddedSourceMips;
{
const FImage& FirstSourceMipImage = InSourceMips[0];
int32 TargetTextureSizeX = FirstSourceMipImage.SizeX;
int32 TargetTextureSizeY = FirstSourceMipImage.SizeY;
int32 TargetTextureSizeZ = BuildSettings.bVolume ? FirstSourceMipImage.NumSlices : 1; // Only used for volume texture.
bool bPadOrStretchTexture = false;
const int32 PowerOfTwoTextureSizeX = FMath::RoundUpToPowerOfTwo(TargetTextureSizeX);
const int32 PowerOfTwoTextureSizeY = FMath::RoundUpToPowerOfTwo(TargetTextureSizeY);
const int32 PowerOfTwoTextureSizeZ = FMath::RoundUpToPowerOfTwo(TargetTextureSizeZ);
switch (static_cast<const ETexturePowerOfTwoSetting::Type>(BuildSettings.PowerOfTwoMode))
{
case ETexturePowerOfTwoSetting::None:
break;
case ETexturePowerOfTwoSetting::PadToPowerOfTwo:
bPadOrStretchTexture = true;
TargetTextureSizeX = PowerOfTwoTextureSizeX;
TargetTextureSizeY = PowerOfTwoTextureSizeY;
TargetTextureSizeZ = PowerOfTwoTextureSizeZ;
break;
case ETexturePowerOfTwoSetting::PadToSquarePowerOfTwo:
bPadOrStretchTexture = true;
TargetTextureSizeX = TargetTextureSizeY = FMath::Max3<int32>(PowerOfTwoTextureSizeX, PowerOfTwoTextureSizeY, PowerOfTwoTextureSizeZ);
break;
default:
checkf(false, TEXT("Unknown entry in ETexturePowerOfTwoSetting::Type"));
break;
}
if (bPadOrStretchTexture)
{
// Want to stretch or pad the texture
bool bSuitableFormat = FirstSourceMipImage.Format == ERawImageFormat::RGBA32F;
FImage Temp;
if (!bSuitableFormat)
{
// convert to RGBA32F
FirstSourceMipImage.CopyTo(Temp, ERawImageFormat::RGBA32F, EGammaSpace::Linear);
}
// space for one source mip and one destination mip
const FImage& SourceImage = bSuitableFormat ? FirstSourceMipImage : Temp;
FImage& TargetImage = *new (PaddedSourceMips) FImage(TargetTextureSizeX, TargetTextureSizeY, BuildSettings.bVolume ? TargetTextureSizeZ : SourceImage.NumSlices, SourceImage.Format);
FLinearColor FillColor = BuildSettings.PaddingColor;
FLinearColor* TargetPtr = (FLinearColor*)TargetImage.RawData.GetData();
FLinearColor* SourcePtr = (FLinearColor*)SourceImage.RawData.GetData();
check(SourceImage.GetBytesPerPixel() == sizeof(FLinearColor));
check(TargetImage.GetBytesPerPixel() == sizeof(FLinearColor));
const int32 SourceBytesPerLine = SourceImage.SizeX * SourceImage.GetBytesPerPixel();
const int32 DestBytesPerLine = TargetImage.SizeX * TargetImage.GetBytesPerPixel();
for (int32 SliceIndex = 0; SliceIndex < SourceImage.NumSlices; ++SliceIndex)
{
for (int32 Y = 0; Y < TargetTextureSizeY; ++Y)
{
int32 XStart = 0;
if (Y < SourceImage.SizeY)
{
XStart = SourceImage.SizeX;
FMemory::Memcpy(TargetPtr, SourcePtr, SourceImage.SizeX * sizeof(FLinearColor));
SourcePtr += SourceImage.SizeX;
TargetPtr += SourceImage.SizeX;
}
for (int32 XPad = XStart; XPad < TargetImage.SizeX; ++XPad)
{
*TargetPtr++ = FillColor;
}
}
}
// Pad new slices for volume texture
for (int32 SliceIndex = SourceImage.NumSlices; SliceIndex < TargetImage.NumSlices; ++SliceIndex)
{
for (int32 Y = 0; Y < TargetImage.SizeY; ++Y)
{
for (int32 X = 0; X< TargetImage.SizeX; ++X)
{
*TargetPtr++ = FillColor;
}
}
}
}
}
const TArray<FImage>& PostOptionalUpscaleSourceMips = (PaddedSourceMips.Num() > 0) ? PaddedSourceMips : InSourceMips;
// See if the smallest provided mip image is still too large for the current compressor.
int32 LevelsToUsableSource = FMath::Max(0, MaxSourceMipCount - MaxDestMipCount);
int32 StartMip = FMath::Max(0, LevelsToUsableSource);
bool bBuildSourceImage = StartMip > (NumSourceMips - 1);
TArray<FImage> GeneratedSourceMips;
if (bBuildSourceImage)
{
// the source is larger than the compressor allows and no mip image exists to act as a smaller source.
// We must generate a suitable source image:
bool bSuitableFormat = PostOptionalUpscaleSourceMips.Last().Format == ERawImageFormat::RGBA32F;
const FImage& BaseImage = PostOptionalUpscaleSourceMips.Last();
if (BaseImage.SizeX != FMath::RoundUpToPowerOfTwo(BaseImage.SizeX) || BaseImage.SizeY != FMath::RoundUpToPowerOfTwo(BaseImage.SizeY))
{
UE_LOG(LogTextureCompressor, Warning,
TEXT("Source image %dx%d (npot) prevents resizing and is too large for compressors max dimension (%d)."),
BaseImage.SizeX,
BaseImage.SizeY,
CompressorCaps.MaxTextureDimension
);
return false;
}
FImage Temp;
if (!bSuitableFormat)
{
// convert to RGBA32F
BaseImage.CopyTo(Temp, ERawImageFormat::RGBA32F, EGammaSpace::Linear);
}
UE_LOG(LogTextureCompressor, Verbose,
TEXT("Source image %dx%d too large for compressors max dimension (%d). Resizing."),
BaseImage.SizeX,
BaseImage.SizeY,
CompressorCaps.MaxTextureDimension
);
GenerateMipChain(BuildSettings, bSuitableFormat ? BaseImage : Temp, GeneratedSourceMips, LevelsToUsableSource);
check(GeneratedSourceMips.Num() != 0);
// Note: The newly generated mip chain does not include the original top level mip.
StartMip--;
}
const TArray<FImage>& SourceMips = bBuildSourceImage ? GeneratedSourceMips : PostOptionalUpscaleSourceMips;
OutMipChain.Empty(NumOutputMips);
// Copy over base mips.
check(StartMip < SourceMips.Num());
int32 CopyCount = SourceMips.Num() - StartMip;
for (int32 MipIndex = StartMip; MipIndex < StartMip + CopyCount; ++MipIndex)
{
const FImage& Image = SourceMips[MipIndex];
ERawImageFormat::Type MipFormat = ERawImageFormat::RGBA32F;
// create base for the mip chain
FImage* Mip = new(OutMipChain) FImage();
if (bLongLatCubemap)
{
// Generate the base mip from the long-lat source image.
GenerateBaseCubeMipFromLongitudeLatitude2D(Mip, Image, BuildSettings.MaxTextureResolution);
}
else
{
// copy base source content to the base of the mip chain
if(BuildSettings.bApplyKernelToTopMip)
{
FImage Temp;
Image.CopyTo(Temp, MipFormat, EGammaSpace::Linear);
if(BuildSettings.bRenormalizeTopMip)
{
NormalizeMip(Temp);
}
GenerateTopMip(Temp, *Mip, BuildSettings);
}
else
{
Image.CopyTo(*Mip, MipFormat, EGammaSpace::Linear);
if(BuildSettings.bRenormalizeTopMip)
{
NormalizeMip(*Mip);
}
}
}
// Apply color adjustments
AdjustImageColors(*Mip, BuildSettings);
if (BuildSettings.bComputeBokehAlpha)
{
// To get the occlusion in the BokehDOF shader working for all Bokeh textures.
ComputeBokehAlpha(*Mip);
}
if (BuildSettings.bFlipGreenChannel)
{
FlipGreenChannel(*Mip);
}
}
// Generate any missing mips in the chain.
if (NumOutputMips > OutMipChain.Num())
{
// Do angular filtering of cubemaps if requested.
if (BuildSettings.bCubemap)
{
GenerateAngularFilteredMips(OutMipChain, NumOutputMips, BuildSettings.DiffuseConvolveMipLevel);
}
else
{
GenerateMipChain(BuildSettings, OutMipChain.Last(), OutMipChain);
}
}
check(OutMipChain.Num() == NumOutputMips);
// Apply post-mip generation adjustments.
if (BuildSettings.bReplicateRed)
{
ReplicateRedChannel(OutMipChain);
}
else if (BuildSettings.bReplicateAlpha)
{
ReplicateAlphaChannel(OutMipChain);
}
return true;
}
// @param CompositeTextureMode original type ECompositeTextureMode
// @return true on success, false on failure. Can fail due to bad mismatched dimensions of incomplete mip chains.
bool ApplyCompositeTexture(TArray<FImage>& RoughnessSourceMips, const TArray<FImage>& NormalSourceMips, uint8 CompositeTextureMode, float CompositePower)
{
uint32 MinLevel = FMath::Min(RoughnessSourceMips.Num(), NormalSourceMips.Num());
if( RoughnessSourceMips[RoughnessSourceMips.Num() - MinLevel].SizeX != NormalSourceMips[NormalSourceMips.Num() - MinLevel].SizeX ||
RoughnessSourceMips[RoughnessSourceMips.Num() - MinLevel].SizeY != NormalSourceMips[NormalSourceMips.Num() - MinLevel].SizeY )
{
//incomplete mip chain or mismatched dimensions so bail
return false;
}
for(uint32 Level = 0; Level < MinLevel; ++Level)
{
::ApplyCompositeTexture(RoughnessSourceMips[RoughnessSourceMips.Num() - 1 - Level], NormalSourceMips[NormalSourceMips.Num() - 1 - Level], CompositeTextureMode, CompositePower);
}
return true;
}
};
IMPLEMENT_MODULE(FTextureCompressorModule, TextureCompressor)