You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
#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. Change3934287by 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 Change3950202by 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 Change3966744by 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]
2239 lines
70 KiB
C++
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)
|