From 675af8692d711198ca327e147dbbdaa03316e771 Mon Sep 17 00:00:00 2001 From: santorac <55155825+santorac@users.noreply.github.com> Date: Tue, 31 Aug 2021 15:14:09 -0700 Subject: [PATCH 01/51] WIP switching from OpenImageIO to libpng. Signed-off-by: santorac <55155825+santorac@users.noreply.github.com> --- .../Common/Code/Source/FrameCaptureSystemComponent.cpp | 8 -------- .../Code/Source/Platform/Windows/platform_windows.cmake | 7 +------ .../Utils/Code/Platform/Windows/platform_windows.cmake | 1 + .../Platform/Android/BuiltInPackages_android.cmake | 1 + cmake/3rdParty/Platform/Linux/BuiltInPackages_linux.cmake | 1 + cmake/3rdParty/Platform/Mac/BuiltInPackages_mac.cmake | 1 + .../Platform/Windows/BuiltInPackages_windows.cmake | 1 + cmake/3rdParty/Platform/iOS/BuiltInPackages_ios.cmake | 1 + 8 files changed, 7 insertions(+), 14 deletions(-) diff --git a/Gems/Atom/Feature/Common/Code/Source/FrameCaptureSystemComponent.cpp b/Gems/Atom/Feature/Common/Code/Source/FrameCaptureSystemComponent.cpp index 6fbdf5b3f0..e3d3186f73 100644 --- a/Gems/Atom/Feature/Common/Code/Source/FrameCaptureSystemComponent.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/FrameCaptureSystemComponent.cpp @@ -32,17 +32,12 @@ #include #include -#if defined(OPEN_IMAGE_IO_ENABLED) -#include -#endif - namespace AZ { namespace Render { AZ_ENUM_DEFINE_REFLECT_UTILITIES(FrameCaptureResult); -#if defined(OPEN_IMAGE_IO_ENABLED) AZ_CVAR(unsigned int, r_pngCompressionLevel, 3, // A compression level of 3 seems like the best default in terms of file size and saving speeds @@ -112,7 +107,6 @@ namespace AZ return FrameCaptureOutputResult{FrameCaptureResult::InternalError, "Unable to save frame capture output to " + outputFilePath}; } -#endif FrameCaptureOutputResult DdsFrameCaptureOutput( const AZStd::string& outputFilePath, const AZ::RPI::AttachmentReadback::ReadbackResult& readbackResult) @@ -471,7 +465,6 @@ namespace AZ m_result = ddsFrameCapture.m_result; m_latestCaptureInfo = ddsFrameCapture.m_errorMessage.value_or(""); } -#if defined(OPEN_IMAGE_IO_ENABLED) else if (extension == "png") { if (readbackResult.m_imageDescriptor.m_format == RHI::Format::R8G8B8A8_UNORM || @@ -492,7 +485,6 @@ namespace AZ m_result = FrameCaptureResult::UnsupportedFormat; } } -#endif else { m_latestCaptureInfo = AZStd::string::format("Only supports saving image to ppm or dds files"); diff --git a/Gems/Atom/Feature/Common/Code/Source/Platform/Windows/platform_windows.cmake b/Gems/Atom/Feature/Common/Code/Source/Platform/Windows/platform_windows.cmake index b74260a5de..2932e891ce 100644 --- a/Gems/Atom/Feature/Common/Code/Source/Platform/Windows/platform_windows.cmake +++ b/Gems/Atom/Feature/Common/Code/Source/Platform/Windows/platform_windows.cmake @@ -10,10 +10,5 @@ set(LY_BUILD_DEPENDENCIES PRIVATE 3rdParty::OpenImageIO 3rdParty::ilmbase -) - -# [GFX-TODO] Add macro defintion in OpenImageIO 3rd party find cmake file -set(LY_COMPILE_DEFINITIONS - PRIVATE - OPEN_IMAGE_IO_ENABLED + 3rdParty::libpng ) diff --git a/Gems/Atom/Utils/Code/Platform/Windows/platform_windows.cmake b/Gems/Atom/Utils/Code/Platform/Windows/platform_windows.cmake index c1d40c6ad8..ead5aa05de 100644 --- a/Gems/Atom/Utils/Code/Platform/Windows/platform_windows.cmake +++ b/Gems/Atom/Utils/Code/Platform/Windows/platform_windows.cmake @@ -9,4 +9,5 @@ set(LY_BUILD_DEPENDENCIES PRIVATE 3rdParty::OpenImageIO + 3rdParty::libpng ) diff --git a/cmake/3rdParty/Platform/Android/BuiltInPackages_android.cmake b/cmake/3rdParty/Platform/Android/BuiltInPackages_android.cmake index a65e8b45e4..435678f29e 100644 --- a/cmake/3rdParty/Platform/Android/BuiltInPackages_android.cmake +++ b/cmake/3rdParty/Platform/Android/BuiltInPackages_android.cmake @@ -25,6 +25,7 @@ ly_associate_package(PACKAGE_NAME PhysX-4.1.2.29882248-rev3-android TARGETS Phy ly_associate_package(PACKAGE_NAME mikkelsen-1.0.0.4-android TARGETS mikkelsen PACKAGE_HASH 075e8e4940884971063b5a9963014e2e517246fa269c07c7dc55b8cf2cd99705) ly_associate_package(PACKAGE_NAME googletest-1.8.1-rev4-android TARGETS googletest PACKAGE_HASH 95671be75287a61c9533452835c3647e9c1b30f81b34b43bcb0ec1997cc23894) ly_associate_package(PACKAGE_NAME googlebenchmark-1.5.0-rev2-android TARGETS GoogleBenchmark PACKAGE_HASH 20b46e572211a69d7d94ddad1c89ec37bb958711d6ad4025368ac89ea83078fb) +ly_associate_package(PACKAGE_NAME libpng-1.6.37-mac TARGETS libpng PACKAGE_HASH 0000000000000000000000000000000000000000000000000000000000000000) ly_associate_package(PACKAGE_NAME libsamplerate-0.2.1-rev2-android TARGETS libsamplerate PACKAGE_HASH bf13662afe65d02bcfa16258a4caa9b875534978227d6f9f36c9cfa92b3fb12b) ly_associate_package(PACKAGE_NAME OpenSSL-1.1.1b-rev1-android TARGETS OpenSSL PACKAGE_HASH 4036d4019d722f0e1b7a1621bf60b5a17ca6a65c9c78fd8701cee1131eec8480) ly_associate_package(PACKAGE_NAME zlib-1.2.11-rev1-android TARGETS zlib PACKAGE_HASH 832b163cae0cccbe4fddc5988f5725fac56ef7dba5bfe95bf8c71281fba2e12c) diff --git a/cmake/3rdParty/Platform/Linux/BuiltInPackages_linux.cmake b/cmake/3rdParty/Platform/Linux/BuiltInPackages_linux.cmake index 7df364121b..56f00d351e 100644 --- a/cmake/3rdParty/Platform/Linux/BuiltInPackages_linux.cmake +++ b/cmake/3rdParty/Platform/Linux/BuiltInPackages_linux.cmake @@ -39,6 +39,7 @@ ly_associate_package(PACKAGE_NAME googletest-1.8.1-rev4-linux ly_associate_package(PACKAGE_NAME googlebenchmark-1.5.0-rev2-linux TARGETS GoogleBenchmark PACKAGE_HASH 4038878f337fc7e0274f0230f71851b385b2e0327c495fc3dd3d1c18a807928d) ly_associate_package(PACKAGE_NAME unwind-1.2.1-linux TARGETS unwind PACKAGE_HASH 3453265fb056e25432f611a61546a25f60388e315515ad39007b5925dd054a77) ly_associate_package(PACKAGE_NAME qt-5.15.2-rev5-linux TARGETS Qt PACKAGE_HASH 76b395897b941a173002845c7219a5f8a799e44b269ffefe8091acc048130f28) +ly_associate_package(PACKAGE_NAME libpng-1.6.37-mac TARGETS libpng PACKAGE_HASH 0000000000000000000000000000000000000000000000000000000000000000) ly_associate_package(PACKAGE_NAME libsamplerate-0.2.1-rev2-linux TARGETS libsamplerate PACKAGE_HASH 41643c31bc6b7d037f895f89d8d8d6369e906b92eff42b0fe05ee6a100f06261) ly_associate_package(PACKAGE_NAME OpenSSL-1.1.1b-rev2-linux TARGETS OpenSSL PACKAGE_HASH b779426d1e9c5ddf71160d5ae2e639c3b956e0fb5e9fcaf9ce97c4526024e3bc) ly_associate_package(PACKAGE_NAME DirectXShaderCompilerDxc-1.6.2104-o3de-rev3-linux TARGETS DirectXShaderCompilerDxc PACKAGE_HASH 88c4a359325d749bc34090b9ac466424847f3b71ba0de15045cf355c17c07099) diff --git a/cmake/3rdParty/Platform/Mac/BuiltInPackages_mac.cmake b/cmake/3rdParty/Platform/Mac/BuiltInPackages_mac.cmake index 353956a495..c0dbd13257 100644 --- a/cmake/3rdParty/Platform/Mac/BuiltInPackages_mac.cmake +++ b/cmake/3rdParty/Platform/Mac/BuiltInPackages_mac.cmake @@ -41,5 +41,6 @@ ly_associate_package(PACKAGE_NAME googletest-1.8.1-rev4-mac ly_associate_package(PACKAGE_NAME googlebenchmark-1.5.0-rev2-mac TARGETS GoogleBenchmark PACKAGE_HASH ad25de0146769c91e179953d845de2bec8ed4a691f973f47e3eb37639381f665) ly_associate_package(PACKAGE_NAME OpenSSL-1.1.1b-rev1-mac TARGETS OpenSSL PACKAGE_HASH 28adc1c0616ac0482b2a9d7b4a3a3635a1020e87b163f8aba687c501cf35f96c) ly_associate_package(PACKAGE_NAME qt-5.15.2-rev5-mac TARGETS Qt PACKAGE_HASH 9d25918351898b308ded3e9e571fff6f26311b2071aeafd00dd5b249fdf53f7e) +ly_associate_package(PACKAGE_NAME libpng-1.6.37-mac TARGETS libpng PACKAGE_HASH 0000000000000000000000000000000000000000000000000000000000000000) ly_associate_package(PACKAGE_NAME libsamplerate-0.2.1-rev2-mac TARGETS libsamplerate PACKAGE_HASH b912af40c0ac197af9c43d85004395ba92a6a859a24b7eacd920fed5854a97fe) ly_associate_package(PACKAGE_NAME zlib-1.2.11-rev1-mac TARGETS zlib PACKAGE_HASH 7fd8a77b3598423d9d6be5f8c60d52aecf346ab4224f563a5282db283aa0da02) diff --git a/cmake/3rdParty/Platform/Windows/BuiltInPackages_windows.cmake b/cmake/3rdParty/Platform/Windows/BuiltInPackages_windows.cmake index 2aded62bcd..fea4017db7 100644 --- a/cmake/3rdParty/Platform/Windows/BuiltInPackages_windows.cmake +++ b/cmake/3rdParty/Platform/Windows/BuiltInPackages_windows.cmake @@ -45,6 +45,7 @@ ly_associate_package(PACKAGE_NAME d3dx12-headers-rev1-windows ly_associate_package(PACKAGE_NAME pyside2-qt-5.15.1-rev2-windows TARGETS pyside2 PACKAGE_HASH c90f3efcc7c10e79b22a33467855ad861f9dbd2e909df27a5cba9db9fa3edd0f) ly_associate_package(PACKAGE_NAME openimageio-2.1.16.0-rev2-windows TARGETS OpenImageIO PACKAGE_HASH 85a2a6cf35cbc4c967c56ca8074babf0955c5b490c90c6e6fd23c78db99fc282) ly_associate_package(PACKAGE_NAME qt-5.15.2-rev4-windows TARGETS Qt PACKAGE_HASH a4634caaf48192cad5c5f408504746e53d338856148285057274f6a0ccdc071d) +ly_associate_package(PACKAGE_NAME libpng-1.6.37-windows TARGETS libpng PACKAGE_HASH 3240dbbccd4bf89a6676243c0e0301dafe6e7c8965d952098c1aa48a7ba60b8a) ly_associate_package(PACKAGE_NAME libsamplerate-0.2.1-rev2-windows TARGETS libsamplerate PACKAGE_HASH dcf3c11a96f212a52e2c9241abde5c364ee90b0f32fe6eeb6dcdca01d491829f) ly_associate_package(PACKAGE_NAME OpenMesh-8.1-rev1-windows TARGETS OpenMesh PACKAGE_HASH 1c1df639358526c368e790dfce40c45cbdfcfb1c9a041b9d7054a8949d88ee77) ly_associate_package(PACKAGE_NAME civetweb-1.8-rev1-windows TARGETS civetweb PACKAGE_HASH 36d0e58a59bcdb4dd70493fb1b177aa0354c945b06c30416348fd326cf323dd4) diff --git a/cmake/3rdParty/Platform/iOS/BuiltInPackages_ios.cmake b/cmake/3rdParty/Platform/iOS/BuiltInPackages_ios.cmake index c288460dd0..c392f365f0 100644 --- a/cmake/3rdParty/Platform/iOS/BuiltInPackages_ios.cmake +++ b/cmake/3rdParty/Platform/iOS/BuiltInPackages_ios.cmake @@ -26,6 +26,7 @@ ly_associate_package(PACKAGE_NAME PhysX-4.1.2.29882248-rev3-ios TARGETS PhysX ly_associate_package(PACKAGE_NAME mikkelsen-1.0.0.4-ios TARGETS mikkelsen PACKAGE_HASH 976aaa3ccd8582346132a10af253822ccc5d5bcc9ea5ba44d27848f65ee88a8a) ly_associate_package(PACKAGE_NAME googletest-1.8.1-rev4-ios TARGETS googletest PACKAGE_HASH 2f121ad9784c0ab73dfaa58e1fee05440a82a07cc556bec162eeb407688111a7) ly_associate_package(PACKAGE_NAME googlebenchmark-1.5.0-rev2-ios TARGETS GoogleBenchmark PACKAGE_HASH c2ffaed2b658892b1bcf81dee4b44cd1cb09fc78d55584ef5cb8ab87f2d8d1ae) +ly_associate_package(PACKAGE_NAME libpng-1.6.37-mac TARGETS libpng PACKAGE_HASH 0000000000000000000000000000000000000000000000000000000000000000) ly_associate_package(PACKAGE_NAME libsamplerate-0.2.1-rev2-ios TARGETS libsamplerate PACKAGE_HASH 7656b961697f490d4f9c35d2e61559f6fc38c32102e542a33c212cd618fc2119) ly_associate_package(PACKAGE_NAME OpenSSL-1.1.1b-rev1-ios TARGETS OpenSSL PACKAGE_HASH cd0dfce3086a7172777c63dadbaf0ac3695b676119ecb6d0614b5fb1da03462f) ly_associate_package(PACKAGE_NAME zlib-1.2.11-rev1-ios TARGETS zlib PACKAGE_HASH 20bfccf3b98bd9a7d3506cf344ac48135035eb517752bf9bede1e821f163608d) From 7a8eb8eda59e416db634fadcafc79bee080c5d6a Mon Sep 17 00:00:00 2001 From: santorac <55155825+santorac@users.noreply.github.com> Date: Wed, 8 Sep 2021 12:56:24 -0700 Subject: [PATCH 02/51] Added a new PngImage utility class that wraps libpng. This replaces the use of OpenImageIO in O3DE (although OpenImageIO is still a build dependency for now). Signed-off-by: santorac <55155825+santorac@users.noreply.github.com> --- .../Source/FrameCaptureSystemComponent.cpp | 30 +- .../Platform/Windows/platform_windows.cmake | 1 - Gems/Atom/Utils/Code/CMakeLists.txt | 1 + .../Utils/Code/Include/Atom/Utils/PngFile.h | 94 ++++++ Gems/Atom/Utils/Code/Source/PngFile.cpp | 295 ++++++++++++++++++ Gems/Atom/Utils/Code/atom_utils_files.cmake | 2 + .../Windows/BuiltInPackages_windows.cmake | 2 +- 7 files changed, 406 insertions(+), 19 deletions(-) create mode 100644 Gems/Atom/Utils/Code/Include/Atom/Utils/PngFile.h create mode 100644 Gems/Atom/Utils/Code/Source/PngFile.cpp diff --git a/Gems/Atom/Feature/Common/Code/Source/FrameCaptureSystemComponent.cpp b/Gems/Atom/Feature/Common/Code/Source/FrameCaptureSystemComponent.cpp index e3d3186f73..6201ff5e56 100644 --- a/Gems/Atom/Feature/Common/Code/Source/FrameCaptureSystemComponent.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/FrameCaptureSystemComponent.cpp @@ -16,6 +16,7 @@ #include #include +#include #include #include @@ -86,26 +87,21 @@ namespace AZ jobCompletion.StartAndWaitForCompletion(); } - using namespace OIIO; - AZStd::unique_ptr out = ImageOutput::create(outputFilePath.c_str()); - if (out) - { - ImageSpec spec( - readbackResult.m_imageDescriptor.m_size.m_width, - readbackResult.m_imageDescriptor.m_size.m_height, - numChannels - ); - spec.attribute("png:compressionLevel", r_pngCompressionLevel); + PngImage image = PngImage::Create(readbackResult.m_imageDescriptor.m_size, readbackResult.m_imageDescriptor.m_format, *buffer); - if (out->open(outputFilePath.c_str(), spec)) - { - out->write_image(TypeDesc::UINT8, buffer->data()); - out->close(); - return FrameCaptureOutputResult{FrameCaptureResult::Success, AZStd::nullopt}; - } + PngImage::SaveSettings saveSettings; + saveSettings.m_compressionLevel = r_pngCompressionLevel; + // We should probably strip alpha to save space, especially for automated test screenshots. Alpha is left in to maintain + // prior behavior, changing this is out of scope for the current task. Note, it would have bit of a cascade effect where + // AtomSampleViewer's ScriptReporter assumes an RGBA image. + saveSettings.m_stripAlpha = false; + + if(image && image.Save(outputFilePath.c_str(), saveSettings)) + { + return FrameCaptureOutputResult{FrameCaptureResult::Success, AZStd::nullopt}; } - return FrameCaptureOutputResult{FrameCaptureResult::InternalError, "Unable to save frame capture output to " + outputFilePath}; + return FrameCaptureOutputResult{FrameCaptureResult::InternalError, "Unable to save frame capture output to '" + outputFilePath + "'"}; } FrameCaptureOutputResult DdsFrameCaptureOutput( diff --git a/Gems/Atom/Feature/Common/Code/Source/Platform/Windows/platform_windows.cmake b/Gems/Atom/Feature/Common/Code/Source/Platform/Windows/platform_windows.cmake index 2932e891ce..8357d45a33 100644 --- a/Gems/Atom/Feature/Common/Code/Source/Platform/Windows/platform_windows.cmake +++ b/Gems/Atom/Feature/Common/Code/Source/Platform/Windows/platform_windows.cmake @@ -8,7 +8,6 @@ set(LY_BUILD_DEPENDENCIES PRIVATE - 3rdParty::OpenImageIO 3rdParty::ilmbase 3rdParty::libpng ) diff --git a/Gems/Atom/Utils/Code/CMakeLists.txt b/Gems/Atom/Utils/Code/CMakeLists.txt index 63a2e029f7..1beefc01f6 100644 --- a/Gems/Atom/Utils/Code/CMakeLists.txt +++ b/Gems/Atom/Utils/Code/CMakeLists.txt @@ -24,6 +24,7 @@ ly_add_target( Gem::Atom_RHI.Public PUBLIC Gem::Atom_RHI.Reflect + 3rdParty::libpng ) ################################################################################ diff --git a/Gems/Atom/Utils/Code/Include/Atom/Utils/PngFile.h b/Gems/Atom/Utils/Code/Include/Atom/Utils/PngFile.h new file mode 100644 index 0000000000..1f168df02a --- /dev/null +++ b/Gems/Atom/Utils/Code/Include/Atom/Utils/PngFile.h @@ -0,0 +1,94 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ +#pragma once + +#include +#include +#include +#include +#include + +#include +#include + +namespace AZ +{ + //! This is a light wrapper class for libpng, to load and save .png files. + //! Functionality is limited, feel free to add more features as needed. + class PngImage + { + public: + using ErrorHandler = AZStd::function; + + struct LoadSettings + { + ErrorHandler m_errorHandler = {}; //!< optional callback function describing any errors that are encountered + bool m_stripAlpha = false; //!< the alpha channel will be skipped, loading an RGBA image as RGB + }; + + struct SaveSettings + { + ErrorHandler m_errorHandler = {}; //!< optional callback function describing any errors that are encountered + bool m_stripAlpha = false; //!< the alpha channel will be skipped, saving an RGBA buffer as RGB + int m_compressionLevel = 6; //!< this is the zlib compression level. See png_set_compression_level in png.h + }; + + // To keep things simple for now we limit all images to RGB and RGBA, 8 bits per channel. + enum class Format + { + Unknown, + RGB, + RGBA + }; + + //! @return the loaded PngImage or an invalid PngImage if there was an error. + static PngImage Load(const char* path, LoadSettings loadSettings = {}); + + //! Create a PngImage from an RHI data buffer. + //! @param size the dimensions of the image (m_depth is not used, assumed to be 1) + //! @param format indicates the pixel format represented by @data. Only a limited set of formats are supported, see implementation. + //! @param data the buffer of image data. The size of the buffer must match the @size and @format parameters. + //! @return the created PngImage or an invalid PngImage if there was an error. + static PngImage Create(const RHI::Size& size, RHI::Format format, AZStd::array_view data); + static PngImage Create(const RHI::Size& size, RHI::Format format, AZStd::vector&& data); + + PngImage() = default; + AZ_DEFAULT_MOVE(PngImage) + + //! @return true if the save operation was successful + bool Save(const char* path, SaveSettings saveSettings = {}); + + bool IsValid() const; + operator bool() const { return IsValid(); } + + uint32_t GetWidth() const { return m_width; } + uint32_t GetHeight() const { return m_height; } + + Format GetBufferFormat() const { return m_bufferFormat; } + const AZStd::vector& GetBuffer() const { return m_buffer; } + + //! Returns a r-value reference that can be moved. This will invalidate the PngImage. + AZStd::vector&& TakeBuffer(); + + private: + AZ_DEFAULT_COPY(PngImage) + + static const int HeaderSize = 8; + + static void DefaultErrorHandler(const char* message); + + // See png_get_IHDR in http://www.libpng.org/pub/png/libpng-1.4.0-manual.pdf... + uint32_t m_width = 0; + uint32_t m_height = 0; + int32_t m_bitDepth = 0; + int32_t m_colorType = 0; + + Format m_bufferFormat = Format::Unknown; + AZStd::vector m_buffer; + }; +} // namespace AZ diff --git a/Gems/Atom/Utils/Code/Source/PngFile.cpp b/Gems/Atom/Utils/Code/Source/PngFile.cpp new file mode 100644 index 0000000000..273ea88358 --- /dev/null +++ b/Gems/Atom/Utils/Code/Source/PngFile.cpp @@ -0,0 +1,295 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include + +namespace AZ +{ + namespace + { + void PngImage_user_error_fn(png_structp png_ptr, png_const_charp error_msg) + { + PngImage::ErrorHandler* errorHandler = reinterpret_cast(png_get_error_ptr(png_ptr)); + (*errorHandler)(error_msg); + } + + void PngImage_user_warning_fn(png_structp /*png_ptr*/, png_const_charp warning_msg) + { + AZ_Warning("PngImage", false, "%s", warning_msg); + } + } + + PngImage PngImage::Create(const RHI::Size& size, RHI::Format format, AZStd::array_view data) + { + return Create(size, format, AZStd::vector{data.begin(), data.end()}); + } + + PngImage PngImage::Create(const RHI::Size& size, RHI::Format format, AZStd::vector&& data) + { + PngImage image; + + if (RHI::Format::R8G8B8A8_UNORM == format) + { + if (size.m_width * size.m_height * 4 == data.size()) + { + image.m_width = size.m_width; + image.m_height = size.m_height; + image.m_bitDepth = 8; + image.m_colorType = PNG_COLOR_TYPE_RGB_ALPHA; + image.m_bufferFormat = PngImage::Format::RGBA; + image.m_buffer = data; + } + else + { + AZ_Assert(false, "Invalid arguments. Buffer size does not match the image dimensions."); + } + } + + return image; + } + + PngImage PngImage::Load(const char* path, LoadSettings loadSettings) + { + if (!loadSettings.m_errorHandler) + { + loadSettings.m_errorHandler = [path](const char* message) { DefaultErrorHandler(AZStd::string::format("Could not load file '%s'. %s", path, message).c_str()); }; + } + + // For documentation of this code, see http://www.libpng.org/pub/png/libpng-1.4.0-manual.pdf chapter 3 + + FILE* fp = NULL; + if (fopen_s(&fp, path, "rb") || !fp) + { + loadSettings.m_errorHandler("Failed to open file."); + return {}; + } + + png_byte header[HeaderSize] = {}; + + if (fread(header, 1, HeaderSize, fp) != HeaderSize) + { + fclose(fp); + loadSettings.m_errorHandler("Invalid header."); + return {}; + } + + bool isPng = !png_sig_cmp(header, 0, HeaderSize); + if (!isPng) + { + fclose(fp); + loadSettings.m_errorHandler("Invalid header."); + return {}; + } + + png_voidp user_error_ptr = &loadSettings.m_errorHandler; + png_error_ptr user_error_fn = PngImage_user_error_fn; + png_error_ptr user_warning_fn = PngImage_user_warning_fn; + + png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, user_error_ptr, user_error_fn, user_warning_fn); + if (!png_ptr) + { + fclose(fp); + loadSettings.m_errorHandler("png_create_read_struct failed."); + return {}; + } + + png_infop info_ptr = png_create_info_struct(png_ptr); + if (!info_ptr) + { + png_destroy_read_struct(&png_ptr, (png_infopp)NULL, (png_infopp)NULL); + fclose(fp); + loadSettings.m_errorHandler("png_create_info_struct failed."); + return {}; + } + + png_infop end_info = png_create_info_struct(png_ptr); + if (!end_info) + { + png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL); + fclose(fp); + loadSettings.m_errorHandler("png_create_info_struct failed."); + return {}; + } + +#pragma warning(push) +#pragma warning(disable: 4611) // Disables "interaction between '_setjmp' and C++ object destruction is non-portable". See https://docs.microsoft.com/en-us/cpp/preprocessor/warning?view=msvc-160 + if (setjmp(png_jmpbuf(png_ptr))) + { + png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); + fclose(fp); + // We don't report an error message here because the user_error_fn should have done that already. + return {}; + } +#pragma warning(pop) + + png_init_io(png_ptr, fp); + + png_set_sig_bytes(png_ptr, HeaderSize); + + png_set_keep_unknown_chunks(png_ptr, PNG_HANDLE_CHUNK_NEVER, NULL, 0); + + // To keep things simple for now we limit all images to RGB and RGBA, 8 bits per channel + int png_transforms = PNG_TRANSFORM_PACKING | // Expand 1, 2 and 4-bit samples to bytes + PNG_TRANSFORM_STRIP_16 | // Reduce 16 bit samples to 8 bits + PNG_TRANSFORM_GRAY_TO_RGB; + + if (loadSettings.m_stripAlpha) + { + png_transforms |= PNG_TRANSFORM_STRIP_ALPHA; + } + + png_read_png(png_ptr, info_ptr, png_transforms, NULL); + + // Note that libpng will allocate row_pointers for us. If we want to manage the memory ourselves, we need to call png_set_rows. + // In that case we would have to use the low level interface: png_read_info, png_read_image, and png_read_end. + png_bytep* row_pointers = png_get_rows(png_ptr, info_ptr); + + PngImage pngImage; + + png_get_IHDR(png_ptr, info_ptr, &pngImage.m_width, &pngImage.m_height, &pngImage.m_bitDepth, &pngImage.m_colorType, NULL, NULL, NULL); + + uint32_t bytesPerPixel = 0; + + switch (pngImage.m_colorType) + { + case PNG_COLOR_TYPE_RGB: + pngImage.m_bufferFormat = PngImage::Format::RGB; + bytesPerPixel = 3; + break; + case PNG_COLOR_TYPE_RGBA: + pngImage.m_bufferFormat = PngImage::Format::RGBA; + bytesPerPixel = 4; + break; + default: + AZ_Assert(false, "The png transforms should have ensured a pixel format of RGB or RGBA, 8 bits per channel"); + png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL); + fclose(fp); + loadSettings.m_errorHandler("Unsupported pixel format."); + return {}; + } + + // In the future we could use the low-level interface to avoid copying the image (and provide progress callbacks) + pngImage.m_buffer.set_capacity(pngImage.m_width * pngImage.m_height * bytesPerPixel); + for (uint32_t rowIndex = 0; rowIndex < pngImage.m_height; ++rowIndex) + { + png_bytep row = row_pointers[rowIndex]; + pngImage.m_buffer.insert(pngImage.m_buffer.end(), row, row + (pngImage.m_width * bytesPerPixel)); + } + + png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); + fclose(fp); + return pngImage; + } + + bool PngImage::Save(const char* path, SaveSettings saveSettings) + { + if (!saveSettings.m_errorHandler) + { + saveSettings.m_errorHandler = [path](const char* message) { DefaultErrorHandler(AZStd::string::format("Could not save file '%s'. %s", path, message).c_str()); }; + } + + if (!IsValid()) + { + saveSettings.m_errorHandler("This PngImage is invalid."); + return false; + } + + // For documentation of this code, see http://www.libpng.org/pub/png/libpng-1.4.0-manual.pdf chapter 4 + + FILE* fp = NULL; + if (fopen_s(&fp, path, "wb") || !fp) + { + saveSettings.m_errorHandler("Failed to open file."); + return false; + } + + png_voidp user_error_ptr = &saveSettings.m_errorHandler; + png_error_ptr user_error_fn = PngImage_user_error_fn; + png_error_ptr user_warning_fn = PngImage_user_warning_fn; + + png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, user_error_ptr, user_error_fn, user_warning_fn); + if (!png_ptr) + { + fclose(fp); + saveSettings.m_errorHandler("png_create_write_struct failed."); + return false; + } + + png_infop info_ptr = png_create_info_struct(png_ptr); + if (!info_ptr) + { + png_destroy_write_struct(&png_ptr, (png_infopp)NULL); + fclose(fp); + saveSettings.m_errorHandler("png_destroy_write_struct failed."); + return false; + } + +#pragma warning(push) +#pragma warning(disable: 4611) // Disables "interaction between '_setjmp' and C++ object destruction is non-portable". See https://docs.microsoft.com/en-us/cpp/preprocessor/warning?view=msvc-160 + if (setjmp(png_jmpbuf(png_ptr))) + { + png_destroy_write_struct(&png_ptr, &info_ptr); + fclose(fp); + // We don't report an error message here because the user_error_fn should have done that already. + return false; + } +#pragma warning(pop) + + png_init_io(png_ptr, fp); + + png_set_IHDR(png_ptr, info_ptr, m_width, m_height, m_bitDepth, m_colorType, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); + + png_set_compression_level(png_ptr, saveSettings.m_compressionLevel); + + const uint32_t bytesPerPixel = (m_bufferFormat == PngImage::Format::RGB) ? 3 : 4; + + AZStd::vector rows; + rows.reserve(m_height); + for (uint32_t i = 0; i < m_height; ++i) + { + rows.push_back(m_buffer.begin() + m_width * bytesPerPixel * i); + } + + png_set_rows(png_ptr, info_ptr, rows.begin()); + + int transforms = PNG_TRANSFORM_IDENTITY; + if (saveSettings.m_stripAlpha && m_bufferFormat == PngImage::Format::RGBA) + { + transforms |= PNG_TRANSFORM_STRIP_FILLER_AFTER; + } + + png_write_png(png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, NULL); + + png_destroy_write_struct(&png_ptr, &info_ptr); + + fclose(fp); + + return true; + } + + void PngImage::DefaultErrorHandler(const char* message) + { + AZ_Error("PngImage", false, "%s", message); + } + + bool PngImage::IsValid() const + { + return + !m_buffer.empty() && + m_width > 0 && + m_height > 0 && + m_bitDepth > 0; + } + + AZStd::vector&& PngImage::TakeBuffer() + { + return AZStd::move(m_buffer); + } + +}// namespace AZ diff --git a/Gems/Atom/Utils/Code/atom_utils_files.cmake b/Gems/Atom/Utils/Code/atom_utils_files.cmake index 06b78a49c4..71b4c8879c 100644 --- a/Gems/Atom/Utils/Code/atom_utils_files.cmake +++ b/Gems/Atom/Utils/Code/atom_utils_files.cmake @@ -21,6 +21,7 @@ set(FILES Include/Atom/Utils/ImGuiFrameVisualizer.inl Include/Atom/Utils/ImGuiTransientAttachmentProfiler.h Include/Atom/Utils/ImGuiTransientAttachmentProfiler.inl + Include/Atom/Utils/PngFile.h Include/Atom/Utils/PpmFile.h Include/Atom/Utils/StableDynamicArray.h Include/Atom/Utils/StableDynamicArray.inl @@ -29,6 +30,7 @@ set(FILES Include/Atom/Utils/AssetCollectionAsyncLoader.h Source/DdsFile.cpp Source/ImageComparison.cpp + Source/PngFile.cpp Source/PpmFile.cpp Source/Utils.cpp Source/AssetCollectionAsyncLoader.cpp diff --git a/cmake/3rdParty/Platform/Windows/BuiltInPackages_windows.cmake b/cmake/3rdParty/Platform/Windows/BuiltInPackages_windows.cmake index fea4017db7..9538f8816a 100644 --- a/cmake/3rdParty/Platform/Windows/BuiltInPackages_windows.cmake +++ b/cmake/3rdParty/Platform/Windows/BuiltInPackages_windows.cmake @@ -45,7 +45,7 @@ ly_associate_package(PACKAGE_NAME d3dx12-headers-rev1-windows ly_associate_package(PACKAGE_NAME pyside2-qt-5.15.1-rev2-windows TARGETS pyside2 PACKAGE_HASH c90f3efcc7c10e79b22a33467855ad861f9dbd2e909df27a5cba9db9fa3edd0f) ly_associate_package(PACKAGE_NAME openimageio-2.1.16.0-rev2-windows TARGETS OpenImageIO PACKAGE_HASH 85a2a6cf35cbc4c967c56ca8074babf0955c5b490c90c6e6fd23c78db99fc282) ly_associate_package(PACKAGE_NAME qt-5.15.2-rev4-windows TARGETS Qt PACKAGE_HASH a4634caaf48192cad5c5f408504746e53d338856148285057274f6a0ccdc071d) -ly_associate_package(PACKAGE_NAME libpng-1.6.37-windows TARGETS libpng PACKAGE_HASH 3240dbbccd4bf89a6676243c0e0301dafe6e7c8965d952098c1aa48a7ba60b8a) +ly_associate_package(PACKAGE_NAME libpng-1.6.37-windows TARGETS libpng PACKAGE_HASH 011079ecbc09c22852eecd860c70dd89f8c2f923c09be87fec4e18ce1e55d4e7) ly_associate_package(PACKAGE_NAME libsamplerate-0.2.1-rev2-windows TARGETS libsamplerate PACKAGE_HASH dcf3c11a96f212a52e2c9241abde5c364ee90b0f32fe6eeb6dcdca01d491829f) ly_associate_package(PACKAGE_NAME OpenMesh-8.1-rev1-windows TARGETS OpenMesh PACKAGE_HASH 1c1df639358526c368e790dfce40c45cbdfcfb1c9a041b9d7054a8949d88ee77) ly_associate_package(PACKAGE_NAME civetweb-1.8-rev1-windows TARGETS civetweb PACKAGE_HASH 36d0e58a59bcdb4dd70493fb1b177aa0354c945b06c30416348fd326cf323dd4) From a9c6909a29070b3f26d58a281b8d815e9a4612cb Mon Sep 17 00:00:00 2001 From: santorac <55155825+santorac@users.noreply.github.com> Date: Wed, 8 Sep 2021 13:20:54 -0700 Subject: [PATCH 03/51] Renamed PngImage to PngFile and put it in a Utils namespace to match the other file utilities. Signed-off-by: santorac <55155825+santorac@users.noreply.github.com> --- .../Source/FrameCaptureSystemComponent.cpp | 4 +- .../Utils/Code/Include/Atom/Utils/PngFile.h | 143 +++--- Gems/Atom/Utils/Code/Source/PngFile.cpp | 469 +++++++++--------- 3 files changed, 311 insertions(+), 305 deletions(-) diff --git a/Gems/Atom/Feature/Common/Code/Source/FrameCaptureSystemComponent.cpp b/Gems/Atom/Feature/Common/Code/Source/FrameCaptureSystemComponent.cpp index 6201ff5e56..62113d3827 100644 --- a/Gems/Atom/Feature/Common/Code/Source/FrameCaptureSystemComponent.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/FrameCaptureSystemComponent.cpp @@ -87,9 +87,9 @@ namespace AZ jobCompletion.StartAndWaitForCompletion(); } - PngImage image = PngImage::Create(readbackResult.m_imageDescriptor.m_size, readbackResult.m_imageDescriptor.m_format, *buffer); + Utils::PngFile image = Utils::PngFile::Create(readbackResult.m_imageDescriptor.m_size, readbackResult.m_imageDescriptor.m_format, *buffer); - PngImage::SaveSettings saveSettings; + Utils::PngFile::SaveSettings saveSettings; saveSettings.m_compressionLevel = r_pngCompressionLevel; // We should probably strip alpha to save space, especially for automated test screenshots. Alpha is left in to maintain // prior behavior, changing this is out of scope for the current task. Note, it would have bit of a cascade effect where diff --git a/Gems/Atom/Utils/Code/Include/Atom/Utils/PngFile.h b/Gems/Atom/Utils/Code/Include/Atom/Utils/PngFile.h index 1f168df02a..5a12001122 100644 --- a/Gems/Atom/Utils/Code/Include/Atom/Utils/PngFile.h +++ b/Gems/Atom/Utils/Code/Include/Atom/Utils/PngFile.h @@ -18,77 +18,80 @@ namespace AZ { - //! This is a light wrapper class for libpng, to load and save .png files. - //! Functionality is limited, feel free to add more features as needed. - class PngImage + namespace Utils { - public: - using ErrorHandler = AZStd::function; - - struct LoadSettings + //! This is a light wrapper class for libpng, to load and save .png files. + //! Functionality is limited, feel free to add more features as needed. + class PngFile { - ErrorHandler m_errorHandler = {}; //!< optional callback function describing any errors that are encountered - bool m_stripAlpha = false; //!< the alpha channel will be skipped, loading an RGBA image as RGB + public: + using ErrorHandler = AZStd::function; + + struct LoadSettings + { + ErrorHandler m_errorHandler = {}; //!< optional callback function describing any errors that are encountered + bool m_stripAlpha = false; //!< the alpha channel will be skipped, loading an RGBA image as RGB + }; + + struct SaveSettings + { + ErrorHandler m_errorHandler = {}; //!< optional callback function describing any errors that are encountered + bool m_stripAlpha = false; //!< the alpha channel will be skipped, saving an RGBA buffer as RGB + int m_compressionLevel = 6; //!< this is the zlib compression level. See png_set_compression_level in png.h + }; + + // To keep things simple for now we limit all images to RGB and RGBA, 8 bits per channel. + enum class Format + { + Unknown, + RGB, + RGBA + }; + + //! @return the loaded PngFile or an invalid PngFile if there was an error. + static PngFile Load(const char* path, LoadSettings loadSettings = {}); + + //! Create a PngFile from an RHI data buffer. + //! @param size the dimensions of the image (m_depth is not used, assumed to be 1) + //! @param format indicates the pixel format represented by @data. Only a limited set of formats are supported, see implementation. + //! @param data the buffer of image data. The size of the buffer must match the @size and @format parameters. + //! @return the created PngFile or an invalid PngFile if there was an error. + static PngFile Create(const RHI::Size& size, RHI::Format format, AZStd::array_view data); + static PngFile Create(const RHI::Size& size, RHI::Format format, AZStd::vector&& data); + + PngFile() = default; + AZ_DEFAULT_MOVE(PngFile) + + //! @return true if the save operation was successful + bool Save(const char* path, SaveSettings saveSettings = {}); + + bool IsValid() const; + operator bool() const { return IsValid(); } + + uint32_t GetWidth() const { return m_width; } + uint32_t GetHeight() const { return m_height; } + + Format GetBufferFormat() const { return m_bufferFormat; } + const AZStd::vector& GetBuffer() const { return m_buffer; } + + //! Returns a r-value reference that can be moved. This will invalidate the PngFile. + AZStd::vector&& TakeBuffer(); + + private: + AZ_DEFAULT_COPY(PngFile) + + static const int HeaderSize = 8; + + static void DefaultErrorHandler(const char* message); + + // See png_get_IHDR in http://www.libpng.org/pub/png/libpng-1.4.0-manual.pdf... + uint32_t m_width = 0; + uint32_t m_height = 0; + int32_t m_bitDepth = 0; + int32_t m_colorType = 0; + + Format m_bufferFormat = Format::Unknown; + AZStd::vector m_buffer; }; - - struct SaveSettings - { - ErrorHandler m_errorHandler = {}; //!< optional callback function describing any errors that are encountered - bool m_stripAlpha = false; //!< the alpha channel will be skipped, saving an RGBA buffer as RGB - int m_compressionLevel = 6; //!< this is the zlib compression level. See png_set_compression_level in png.h - }; - - // To keep things simple for now we limit all images to RGB and RGBA, 8 bits per channel. - enum class Format - { - Unknown, - RGB, - RGBA - }; - - //! @return the loaded PngImage or an invalid PngImage if there was an error. - static PngImage Load(const char* path, LoadSettings loadSettings = {}); - - //! Create a PngImage from an RHI data buffer. - //! @param size the dimensions of the image (m_depth is not used, assumed to be 1) - //! @param format indicates the pixel format represented by @data. Only a limited set of formats are supported, see implementation. - //! @param data the buffer of image data. The size of the buffer must match the @size and @format parameters. - //! @return the created PngImage or an invalid PngImage if there was an error. - static PngImage Create(const RHI::Size& size, RHI::Format format, AZStd::array_view data); - static PngImage Create(const RHI::Size& size, RHI::Format format, AZStd::vector&& data); - - PngImage() = default; - AZ_DEFAULT_MOVE(PngImage) - - //! @return true if the save operation was successful - bool Save(const char* path, SaveSettings saveSettings = {}); - - bool IsValid() const; - operator bool() const { return IsValid(); } - - uint32_t GetWidth() const { return m_width; } - uint32_t GetHeight() const { return m_height; } - - Format GetBufferFormat() const { return m_bufferFormat; } - const AZStd::vector& GetBuffer() const { return m_buffer; } - - //! Returns a r-value reference that can be moved. This will invalidate the PngImage. - AZStd::vector&& TakeBuffer(); - - private: - AZ_DEFAULT_COPY(PngImage) - - static const int HeaderSize = 8; - - static void DefaultErrorHandler(const char* message); - - // See png_get_IHDR in http://www.libpng.org/pub/png/libpng-1.4.0-manual.pdf... - uint32_t m_width = 0; - uint32_t m_height = 0; - int32_t m_bitDepth = 0; - int32_t m_colorType = 0; - - Format m_bufferFormat = Format::Unknown; - AZStd::vector m_buffer; - }; + } } // namespace AZ diff --git a/Gems/Atom/Utils/Code/Source/PngFile.cpp b/Gems/Atom/Utils/Code/Source/PngFile.cpp index 273ea88358..5a3fdf7fdb 100644 --- a/Gems/Atom/Utils/Code/Source/PngFile.cpp +++ b/Gems/Atom/Utils/Code/Source/PngFile.cpp @@ -11,285 +11,288 @@ namespace AZ { - namespace + namespace Utils { - void PngImage_user_error_fn(png_structp png_ptr, png_const_charp error_msg) + namespace { - PngImage::ErrorHandler* errorHandler = reinterpret_cast(png_get_error_ptr(png_ptr)); - (*errorHandler)(error_msg); - } - - void PngImage_user_warning_fn(png_structp /*png_ptr*/, png_const_charp warning_msg) - { - AZ_Warning("PngImage", false, "%s", warning_msg); - } - } - - PngImage PngImage::Create(const RHI::Size& size, RHI::Format format, AZStd::array_view data) - { - return Create(size, format, AZStd::vector{data.begin(), data.end()}); - } - - PngImage PngImage::Create(const RHI::Size& size, RHI::Format format, AZStd::vector&& data) - { - PngImage image; - - if (RHI::Format::R8G8B8A8_UNORM == format) - { - if (size.m_width * size.m_height * 4 == data.size()) + void PngImage_user_error_fn(png_structp png_ptr, png_const_charp error_msg) { - image.m_width = size.m_width; - image.m_height = size.m_height; - image.m_bitDepth = 8; - image.m_colorType = PNG_COLOR_TYPE_RGB_ALPHA; - image.m_bufferFormat = PngImage::Format::RGBA; - image.m_buffer = data; + PngFile::ErrorHandler* errorHandler = reinterpret_cast(png_get_error_ptr(png_ptr)); + (*errorHandler)(error_msg); } - else + + void PngImage_user_warning_fn(png_structp /*png_ptr*/, png_const_charp warning_msg) { - AZ_Assert(false, "Invalid arguments. Buffer size does not match the image dimensions."); + AZ_Warning("PngFile", false, "%s", warning_msg); } } - return image; - } - - PngImage PngImage::Load(const char* path, LoadSettings loadSettings) - { - if (!loadSettings.m_errorHandler) + PngFile PngFile::Create(const RHI::Size& size, RHI::Format format, AZStd::array_view data) { - loadSettings.m_errorHandler = [path](const char* message) { DefaultErrorHandler(AZStd::string::format("Could not load file '%s'. %s", path, message).c_str()); }; - } - - // For documentation of this code, see http://www.libpng.org/pub/png/libpng-1.4.0-manual.pdf chapter 3 - - FILE* fp = NULL; - if (fopen_s(&fp, path, "rb") || !fp) - { - loadSettings.m_errorHandler("Failed to open file."); - return {}; + return Create(size, format, AZStd::vector{data.begin(), data.end()}); } - png_byte header[HeaderSize] = {}; - - if (fread(header, 1, HeaderSize, fp) != HeaderSize) + PngFile PngFile::Create(const RHI::Size& size, RHI::Format format, AZStd::vector&& data) { - fclose(fp); - loadSettings.m_errorHandler("Invalid header."); - return {}; + PngFile image; + + if (RHI::Format::R8G8B8A8_UNORM == format) + { + if (size.m_width * size.m_height * 4 == data.size()) + { + image.m_width = size.m_width; + image.m_height = size.m_height; + image.m_bitDepth = 8; + image.m_colorType = PNG_COLOR_TYPE_RGB_ALPHA; + image.m_bufferFormat = PngFile::Format::RGBA; + image.m_buffer = data; + } + else + { + AZ_Assert(false, "Invalid arguments. Buffer size does not match the image dimensions."); + } + } + + return image; } - bool isPng = !png_sig_cmp(header, 0, HeaderSize); - if (!isPng) + PngFile PngFile::Load(const char* path, LoadSettings loadSettings) { - fclose(fp); - loadSettings.m_errorHandler("Invalid header."); - return {}; - } + if (!loadSettings.m_errorHandler) + { + loadSettings.m_errorHandler = [path](const char* message) { DefaultErrorHandler(AZStd::string::format("Could not load file '%s'. %s", path, message).c_str()); }; + } - png_voidp user_error_ptr = &loadSettings.m_errorHandler; - png_error_ptr user_error_fn = PngImage_user_error_fn; - png_error_ptr user_warning_fn = PngImage_user_warning_fn; + // For documentation of this code, see http://www.libpng.org/pub/png/libpng-1.4.0-manual.pdf chapter 3 - png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, user_error_ptr, user_error_fn, user_warning_fn); - if (!png_ptr) - { - fclose(fp); - loadSettings.m_errorHandler("png_create_read_struct failed."); - return {}; - } + FILE* fp = NULL; + if (fopen_s(&fp, path, "rb") || !fp) + { + loadSettings.m_errorHandler("Failed to open file."); + return {}; + } - png_infop info_ptr = png_create_info_struct(png_ptr); - if (!info_ptr) - { - png_destroy_read_struct(&png_ptr, (png_infopp)NULL, (png_infopp)NULL); - fclose(fp); - loadSettings.m_errorHandler("png_create_info_struct failed."); - return {}; - } + png_byte header[HeaderSize] = {}; - png_infop end_info = png_create_info_struct(png_ptr); - if (!end_info) - { - png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL); - fclose(fp); - loadSettings.m_errorHandler("png_create_info_struct failed."); - return {}; - } + if (fread(header, 1, HeaderSize, fp) != HeaderSize) + { + fclose(fp); + loadSettings.m_errorHandler("Invalid header."); + return {}; + } + + bool isPng = !png_sig_cmp(header, 0, HeaderSize); + if (!isPng) + { + fclose(fp); + loadSettings.m_errorHandler("Invalid header."); + return {}; + } + + png_voidp user_error_ptr = &loadSettings.m_errorHandler; + png_error_ptr user_error_fn = PngImage_user_error_fn; + png_error_ptr user_warning_fn = PngImage_user_warning_fn; + + png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, user_error_ptr, user_error_fn, user_warning_fn); + if (!png_ptr) + { + fclose(fp); + loadSettings.m_errorHandler("png_create_read_struct failed."); + return {}; + } + + png_infop info_ptr = png_create_info_struct(png_ptr); + if (!info_ptr) + { + png_destroy_read_struct(&png_ptr, (png_infopp)NULL, (png_infopp)NULL); + fclose(fp); + loadSettings.m_errorHandler("png_create_info_struct failed."); + return {}; + } + + png_infop end_info = png_create_info_struct(png_ptr); + if (!end_info) + { + png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL); + fclose(fp); + loadSettings.m_errorHandler("png_create_info_struct failed."); + return {}; + } #pragma warning(push) #pragma warning(disable: 4611) // Disables "interaction between '_setjmp' and C++ object destruction is non-portable". See https://docs.microsoft.com/en-us/cpp/preprocessor/warning?view=msvc-160 - if (setjmp(png_jmpbuf(png_ptr))) - { + if (setjmp(png_jmpbuf(png_ptr))) + { + png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); + fclose(fp); + // We don't report an error message here because the user_error_fn should have done that already. + return {}; + } +#pragma warning(pop) + + png_init_io(png_ptr, fp); + + png_set_sig_bytes(png_ptr, HeaderSize); + + png_set_keep_unknown_chunks(png_ptr, PNG_HANDLE_CHUNK_NEVER, NULL, 0); + + // To keep things simple for now we limit all images to RGB and RGBA, 8 bits per channel + int png_transforms = PNG_TRANSFORM_PACKING | // Expand 1, 2 and 4-bit samples to bytes + PNG_TRANSFORM_STRIP_16 | // Reduce 16 bit samples to 8 bits + PNG_TRANSFORM_GRAY_TO_RGB; + + if (loadSettings.m_stripAlpha) + { + png_transforms |= PNG_TRANSFORM_STRIP_ALPHA; + } + + png_read_png(png_ptr, info_ptr, png_transforms, NULL); + + // Note that libpng will allocate row_pointers for us. If we want to manage the memory ourselves, we need to call png_set_rows. + // In that case we would have to use the low level interface: png_read_info, png_read_image, and png_read_end. + png_bytep* row_pointers = png_get_rows(png_ptr, info_ptr); + + PngFile pngFile; + + png_get_IHDR(png_ptr, info_ptr, &pngFile.m_width, &pngFile.m_height, &pngFile.m_bitDepth, &pngFile.m_colorType, NULL, NULL, NULL); + + uint32_t bytesPerPixel = 0; + + switch (pngFile.m_colorType) + { + case PNG_COLOR_TYPE_RGB: + pngFile.m_bufferFormat = PngFile::Format::RGB; + bytesPerPixel = 3; + break; + case PNG_COLOR_TYPE_RGBA: + pngFile.m_bufferFormat = PngFile::Format::RGBA; + bytesPerPixel = 4; + break; + default: + AZ_Assert(false, "The png transforms should have ensured a pixel format of RGB or RGBA, 8 bits per channel"); + png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL); + fclose(fp); + loadSettings.m_errorHandler("Unsupported pixel format."); + return {}; + } + + // In the future we could use the low-level interface to avoid copying the image (and provide progress callbacks) + pngFile.m_buffer.set_capacity(pngFile.m_width * pngFile.m_height * bytesPerPixel); + for (uint32_t rowIndex = 0; rowIndex < pngFile.m_height; ++rowIndex) + { + png_bytep row = row_pointers[rowIndex]; + pngFile.m_buffer.insert(pngFile.m_buffer.end(), row, row + (pngFile.m_width * bytesPerPixel)); + } + png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); fclose(fp); - // We don't report an error message here because the user_error_fn should have done that already. - return {}; + return pngFile; } -#pragma warning(pop) - png_init_io(png_ptr, fp); - - png_set_sig_bytes(png_ptr, HeaderSize); - - png_set_keep_unknown_chunks(png_ptr, PNG_HANDLE_CHUNK_NEVER, NULL, 0); - - // To keep things simple for now we limit all images to RGB and RGBA, 8 bits per channel - int png_transforms = PNG_TRANSFORM_PACKING | // Expand 1, 2 and 4-bit samples to bytes - PNG_TRANSFORM_STRIP_16 | // Reduce 16 bit samples to 8 bits - PNG_TRANSFORM_GRAY_TO_RGB; - - if (loadSettings.m_stripAlpha) + bool PngFile::Save(const char* path, SaveSettings saveSettings) { - png_transforms |= PNG_TRANSFORM_STRIP_ALPHA; - } + if (!saveSettings.m_errorHandler) + { + saveSettings.m_errorHandler = [path](const char* message) { DefaultErrorHandler(AZStd::string::format("Could not save file '%s'. %s", path, message).c_str()); }; + } - png_read_png(png_ptr, info_ptr, png_transforms, NULL); - - // Note that libpng will allocate row_pointers for us. If we want to manage the memory ourselves, we need to call png_set_rows. - // In that case we would have to use the low level interface: png_read_info, png_read_image, and png_read_end. - png_bytep* row_pointers = png_get_rows(png_ptr, info_ptr); + if (!IsValid()) + { + saveSettings.m_errorHandler("This PngFile is invalid."); + return false; + } - PngImage pngImage; + // For documentation of this code, see http://www.libpng.org/pub/png/libpng-1.4.0-manual.pdf chapter 4 - png_get_IHDR(png_ptr, info_ptr, &pngImage.m_width, &pngImage.m_height, &pngImage.m_bitDepth, &pngImage.m_colorType, NULL, NULL, NULL); - - uint32_t bytesPerPixel = 0; + FILE* fp = NULL; + if (fopen_s(&fp, path, "wb") || !fp) + { + saveSettings.m_errorHandler("Failed to open file."); + return false; + } - switch (pngImage.m_colorType) - { - case PNG_COLOR_TYPE_RGB: - pngImage.m_bufferFormat = PngImage::Format::RGB; - bytesPerPixel = 3; - break; - case PNG_COLOR_TYPE_RGBA: - pngImage.m_bufferFormat = PngImage::Format::RGBA; - bytesPerPixel = 4; - break; - default: - AZ_Assert(false, "The png transforms should have ensured a pixel format of RGB or RGBA, 8 bits per channel"); - png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL); - fclose(fp); - loadSettings.m_errorHandler("Unsupported pixel format."); - return {}; - } + png_voidp user_error_ptr = &saveSettings.m_errorHandler; + png_error_ptr user_error_fn = PngImage_user_error_fn; + png_error_ptr user_warning_fn = PngImage_user_warning_fn; - // In the future we could use the low-level interface to avoid copying the image (and provide progress callbacks) - pngImage.m_buffer.set_capacity(pngImage.m_width * pngImage.m_height * bytesPerPixel); - for (uint32_t rowIndex = 0; rowIndex < pngImage.m_height; ++rowIndex) - { - png_bytep row = row_pointers[rowIndex]; - pngImage.m_buffer.insert(pngImage.m_buffer.end(), row, row + (pngImage.m_width * bytesPerPixel)); - } - - png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); - fclose(fp); - return pngImage; - } + png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, user_error_ptr, user_error_fn, user_warning_fn); + if (!png_ptr) + { + fclose(fp); + saveSettings.m_errorHandler("png_create_write_struct failed."); + return false; + } - bool PngImage::Save(const char* path, SaveSettings saveSettings) - { - if (!saveSettings.m_errorHandler) - { - saveSettings.m_errorHandler = [path](const char* message) { DefaultErrorHandler(AZStd::string::format("Could not save file '%s'. %s", path, message).c_str()); }; - } - - if (!IsValid()) - { - saveSettings.m_errorHandler("This PngImage is invalid."); - return false; - } - - // For documentation of this code, see http://www.libpng.org/pub/png/libpng-1.4.0-manual.pdf chapter 4 - - FILE* fp = NULL; - if (fopen_s(&fp, path, "wb") || !fp) - { - saveSettings.m_errorHandler("Failed to open file."); - return false; - } - - png_voidp user_error_ptr = &saveSettings.m_errorHandler; - png_error_ptr user_error_fn = PngImage_user_error_fn; - png_error_ptr user_warning_fn = PngImage_user_warning_fn; - - png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, user_error_ptr, user_error_fn, user_warning_fn); - if (!png_ptr) - { - fclose(fp); - saveSettings.m_errorHandler("png_create_write_struct failed."); - return false; - } - - png_infop info_ptr = png_create_info_struct(png_ptr); - if (!info_ptr) - { - png_destroy_write_struct(&png_ptr, (png_infopp)NULL); - fclose(fp); - saveSettings.m_errorHandler("png_destroy_write_struct failed."); - return false; - } + png_infop info_ptr = png_create_info_struct(png_ptr); + if (!info_ptr) + { + png_destroy_write_struct(&png_ptr, (png_infopp)NULL); + fclose(fp); + saveSettings.m_errorHandler("png_destroy_write_struct failed."); + return false; + } #pragma warning(push) #pragma warning(disable: 4611) // Disables "interaction between '_setjmp' and C++ object destruction is non-portable". See https://docs.microsoft.com/en-us/cpp/preprocessor/warning?view=msvc-160 - if (setjmp(png_jmpbuf(png_ptr))) - { - png_destroy_write_struct(&png_ptr, &info_ptr); - fclose(fp); - // We don't report an error message here because the user_error_fn should have done that already. - return false; - } + if (setjmp(png_jmpbuf(png_ptr))) + { + png_destroy_write_struct(&png_ptr, &info_ptr); + fclose(fp); + // We don't report an error message here because the user_error_fn should have done that already. + return false; + } #pragma warning(pop) - png_init_io(png_ptr, fp); + png_init_io(png_ptr, fp); - png_set_IHDR(png_ptr, info_ptr, m_width, m_height, m_bitDepth, m_colorType, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); - - png_set_compression_level(png_ptr, saveSettings.m_compressionLevel); + png_set_IHDR(png_ptr, info_ptr, m_width, m_height, m_bitDepth, m_colorType, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); - const uint32_t bytesPerPixel = (m_bufferFormat == PngImage::Format::RGB) ? 3 : 4; + png_set_compression_level(png_ptr, saveSettings.m_compressionLevel); - AZStd::vector rows; - rows.reserve(m_height); - for (uint32_t i = 0; i < m_height; ++i) - { - rows.push_back(m_buffer.begin() + m_width * bytesPerPixel * i); + const uint32_t bytesPerPixel = (m_bufferFormat == PngFile::Format::RGB) ? 3 : 4; + + AZStd::vector rows; + rows.reserve(m_height); + for (uint32_t i = 0; i < m_height; ++i) + { + rows.push_back(m_buffer.begin() + m_width * bytesPerPixel * i); + } + + png_set_rows(png_ptr, info_ptr, rows.begin()); + + int transforms = PNG_TRANSFORM_IDENTITY; + if (saveSettings.m_stripAlpha && m_bufferFormat == PngFile::Format::RGBA) + { + transforms |= PNG_TRANSFORM_STRIP_FILLER_AFTER; + } + + png_write_png(png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, NULL); + + png_destroy_write_struct(&png_ptr, &info_ptr); + + fclose(fp); + + return true; } - png_set_rows(png_ptr, info_ptr, rows.begin()); - - int transforms = PNG_TRANSFORM_IDENTITY; - if (saveSettings.m_stripAlpha && m_bufferFormat == PngImage::Format::RGBA) + void PngFile::DefaultErrorHandler(const char* message) { - transforms |= PNG_TRANSFORM_STRIP_FILLER_AFTER; + AZ_Error("PngFile", false, "%s", message); } - png_write_png(png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, NULL); + bool PngFile::IsValid() const + { + return + !m_buffer.empty() && + m_width > 0 && + m_height > 0 && + m_bitDepth > 0; + } - png_destroy_write_struct(&png_ptr, &info_ptr); - - fclose(fp); + AZStd::vector&& PngFile::TakeBuffer() + { + return AZStd::move(m_buffer); + } - return true; - } - - void PngImage::DefaultErrorHandler(const char* message) - { - AZ_Error("PngImage", false, "%s", message); - } - - bool PngImage::IsValid() const - { - return - !m_buffer.empty() && - m_width > 0 && - m_height > 0 && - m_bitDepth > 0; - } - - AZStd::vector&& PngImage::TakeBuffer() - { - return AZStd::move(m_buffer); - } - + } // namespace Utils }// namespace AZ From 849a8da4d9fa6ae9aeb85802bae36cb47098b1da Mon Sep 17 00:00:00 2001 From: santorac <55155825+santorac@users.noreply.github.com> Date: Wed, 8 Sep 2021 23:45:16 -0700 Subject: [PATCH 04/51] Added unit tests for PngFile. Fixed a couple issue like palettized files would not load, and stripping alpha was not affecting the color type reported by libpng. Also cleaned up some error reporting. Added AzFramework to Atom/Utils tests to support PngFile testing. Signed-off-by: santorac <55155825+santorac@users.noreply.github.com> --- Gems/Atom/Utils/Code/CMakeLists.txt | 1 + .../Utils/Code/Include/Atom/Utils/PngFile.h | 7 +- Gems/Atom/Utils/Code/Source/PngFile.cpp | 68 ++-- Gems/Atom/Utils/Code/Tests/PngFileTests.cpp | 327 ++++++++++++++++++ .../Utils/Code/Tests/PngTestImages/.gitignore | 1 + .../Tests/PngTestImages/ColorChart_rgb.png | 3 + .../Tests/PngTestImages/ColorChart_rgba.jpg | 3 + .../Tests/PngTestImages/ColorChart_rgba.png | 3 + .../Tests/PngTestImages/ColorPalette_2bit.png | 3 + .../Code/Tests/PngTestImages/EmptyFile.png | 0 .../PngTestImages/Gradient_rgb_16bpc.png | 3 + .../Tests/PngTestImages/GrayPalette_1bit.png | 3 + .../Utils/Code/atom_utils_tests_files.cmake | 1 + 13 files changed, 398 insertions(+), 25 deletions(-) create mode 100644 Gems/Atom/Utils/Code/Tests/PngFileTests.cpp create mode 100644 Gems/Atom/Utils/Code/Tests/PngTestImages/.gitignore create mode 100644 Gems/Atom/Utils/Code/Tests/PngTestImages/ColorChart_rgb.png create mode 100644 Gems/Atom/Utils/Code/Tests/PngTestImages/ColorChart_rgba.jpg create mode 100644 Gems/Atom/Utils/Code/Tests/PngTestImages/ColorChart_rgba.png create mode 100644 Gems/Atom/Utils/Code/Tests/PngTestImages/ColorPalette_2bit.png create mode 100644 Gems/Atom/Utils/Code/Tests/PngTestImages/EmptyFile.png create mode 100644 Gems/Atom/Utils/Code/Tests/PngTestImages/Gradient_rgb_16bpc.png create mode 100644 Gems/Atom/Utils/Code/Tests/PngTestImages/GrayPalette_1bit.png diff --git a/Gems/Atom/Utils/Code/CMakeLists.txt b/Gems/Atom/Utils/Code/CMakeLists.txt index 1beefc01f6..89bc8dd0a5 100644 --- a/Gems/Atom/Utils/Code/CMakeLists.txt +++ b/Gems/Atom/Utils/Code/CMakeLists.txt @@ -44,6 +44,7 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED) BUILD_DEPENDENCIES PRIVATE AZ::AzTest + AZ::AzFramework Gem::Atom_Utils.Static ) ly_add_googletest( diff --git a/Gems/Atom/Utils/Code/Include/Atom/Utils/PngFile.h b/Gems/Atom/Utils/Code/Include/Atom/Utils/PngFile.h index 5a12001122..7fb2c768ad 100644 --- a/Gems/Atom/Utils/Code/Include/Atom/Utils/PngFile.h +++ b/Gems/Atom/Utils/Code/Include/Atom/Utils/PngFile.h @@ -55,9 +55,10 @@ namespace AZ //! @param size the dimensions of the image (m_depth is not used, assumed to be 1) //! @param format indicates the pixel format represented by @data. Only a limited set of formats are supported, see implementation. //! @param data the buffer of image data. The size of the buffer must match the @size and @format parameters. + //! @param errorHandler optional callback function describing any errors that are encountered //! @return the created PngFile or an invalid PngFile if there was an error. - static PngFile Create(const RHI::Size& size, RHI::Format format, AZStd::array_view data); - static PngFile Create(const RHI::Size& size, RHI::Format format, AZStd::vector&& data); + static PngFile Create(const RHI::Size& size, RHI::Format format, AZStd::array_view data, ErrorHandler errorHandler = {}); + static PngFile Create(const RHI::Size& size, RHI::Format format, AZStd::vector&& data, ErrorHandler errorHandler = {}); PngFile() = default; AZ_DEFAULT_MOVE(PngFile) @@ -84,11 +85,9 @@ namespace AZ static void DefaultErrorHandler(const char* message); - // See png_get_IHDR in http://www.libpng.org/pub/png/libpng-1.4.0-manual.pdf... uint32_t m_width = 0; uint32_t m_height = 0; int32_t m_bitDepth = 0; - int32_t m_colorType = 0; Format m_bufferFormat = Format::Unknown; AZStd::vector m_buffer; diff --git a/Gems/Atom/Utils/Code/Source/PngFile.cpp b/Gems/Atom/Utils/Code/Source/PngFile.cpp index 5a3fdf7fdb..056e199d40 100644 --- a/Gems/Atom/Utils/Code/Source/PngFile.cpp +++ b/Gems/Atom/Utils/Code/Source/PngFile.cpp @@ -27,13 +27,18 @@ namespace AZ } } - PngFile PngFile::Create(const RHI::Size& size, RHI::Format format, AZStd::array_view data) + PngFile PngFile::Create(const RHI::Size& size, RHI::Format format, AZStd::array_view data, ErrorHandler errorHandler) { - return Create(size, format, AZStd::vector{data.begin(), data.end()}); + return Create(size, format, AZStd::vector{data.begin(), data.end()}, errorHandler); } - PngFile PngFile::Create(const RHI::Size& size, RHI::Format format, AZStd::vector&& data) + PngFile PngFile::Create(const RHI::Size& size, RHI::Format format, AZStd::vector&& data, ErrorHandler errorHandler) { + if (!errorHandler) + { + errorHandler = [](const char* message) { DefaultErrorHandler(message); }; + } + PngFile image; if (RHI::Format::R8G8B8A8_UNORM == format) @@ -43,15 +48,18 @@ namespace AZ image.m_width = size.m_width; image.m_height = size.m_height; image.m_bitDepth = 8; - image.m_colorType = PNG_COLOR_TYPE_RGB_ALPHA; image.m_bufferFormat = PngFile::Format::RGBA; - image.m_buffer = data; + image.m_buffer = AZStd::move(data); } else { - AZ_Assert(false, "Invalid arguments. Buffer size does not match the image dimensions."); + errorHandler("Invalid arguments. Buffer size does not match the image dimensions."); } } + else + { + errorHandler(AZStd::string::format("Cannot create PngFile with unsupported format %s", AZ::RHI::ToString(format)).c_str()); + } return image; } @@ -68,7 +76,7 @@ namespace AZ FILE* fp = NULL; if (fopen_s(&fp, path, "rb") || !fp) { - loadSettings.m_errorHandler("Failed to open file."); + loadSettings.m_errorHandler("Cannot open file."); return {}; } @@ -77,7 +85,7 @@ namespace AZ if (fread(header, 1, HeaderSize, fp) != HeaderSize) { fclose(fp); - loadSettings.m_errorHandler("Invalid header."); + loadSettings.m_errorHandler("Invalid png header."); return {}; } @@ -85,7 +93,7 @@ namespace AZ if (!isPng) { fclose(fp); - loadSettings.m_errorHandler("Invalid header."); + loadSettings.m_errorHandler("Invalid png header."); return {}; } @@ -154,11 +162,13 @@ namespace AZ PngFile pngFile; - png_get_IHDR(png_ptr, info_ptr, &pngFile.m_width, &pngFile.m_height, &pngFile.m_bitDepth, &pngFile.m_colorType, NULL, NULL, NULL); + int colorType = 0; + + png_get_IHDR(png_ptr, info_ptr, &pngFile.m_width, &pngFile.m_height, &pngFile.m_bitDepth, &colorType, NULL, NULL, NULL); uint32_t bytesPerPixel = 0; - switch (pngFile.m_colorType) + switch (colorType) { case PNG_COLOR_TYPE_RGB: pngFile.m_bufferFormat = PngFile::Format::RGB; @@ -168,6 +178,12 @@ namespace AZ pngFile.m_bufferFormat = PngFile::Format::RGBA; bytesPerPixel = 4; break; + case PNG_COLOR_TYPE_PALETTE: + // Handles cases where the image uses 1, 2, or 4 bit samples. + // Note bytesPerPixel is 3 because we use PNG_TRANSFORM_PACKING + pngFile.m_bufferFormat = PngFile::Format::RGB; + bytesPerPixel = 3; + break; default: AZ_Assert(false, "The png transforms should have ensured a pixel format of RGB or RGBA, 8 bits per channel"); png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL); @@ -207,7 +223,7 @@ namespace AZ FILE* fp = NULL; if (fopen_s(&fp, path, "wb") || !fp) { - saveSettings.m_errorHandler("Failed to open file."); + saveSettings.m_errorHandler("Cannot open file."); return false; } @@ -245,7 +261,23 @@ namespace AZ png_init_io(png_ptr, fp); - png_set_IHDR(png_ptr, info_ptr, m_width, m_height, m_bitDepth, m_colorType, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); + int colorType = 0; + if (saveSettings.m_stripAlpha || m_bufferFormat == PngFile::Format::RGB) + { + colorType = PNG_COLOR_TYPE_RGB; + } + else + { + colorType = PNG_COLOR_TYPE_RGBA; + } + + int transforms = PNG_TRANSFORM_IDENTITY; + if (saveSettings.m_stripAlpha && m_bufferFormat == PngFile::Format::RGBA) + { + transforms |= PNG_TRANSFORM_STRIP_FILLER_AFTER; + } + + png_set_IHDR(png_ptr, info_ptr, m_width, m_height, m_bitDepth, colorType, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); png_set_compression_level(png_ptr, saveSettings.m_compressionLevel); @@ -259,14 +291,8 @@ namespace AZ } png_set_rows(png_ptr, info_ptr, rows.begin()); - - int transforms = PNG_TRANSFORM_IDENTITY; - if (saveSettings.m_stripAlpha && m_bufferFormat == PngFile::Format::RGBA) - { - transforms |= PNG_TRANSFORM_STRIP_FILLER_AFTER; - } - - png_write_png(png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, NULL); + + png_write_png(png_ptr, info_ptr, transforms, NULL); png_destroy_write_struct(&png_ptr, &info_ptr); diff --git a/Gems/Atom/Utils/Code/Tests/PngFileTests.cpp b/Gems/Atom/Utils/Code/Tests/PngFileTests.cpp new file mode 100644 index 0000000000..90181d8040 --- /dev/null +++ b/Gems/Atom/Utils/Code/Tests/PngFileTests.cpp @@ -0,0 +1,327 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include +#include +#include +#include +#include + +namespace UnitTest +{ + using namespace AZ::Utils; + + class PngFileTests + : public AllocatorsFixture + { + protected: + AZStd::string m_testImageFolder; + AZStd::string m_tempPngFilePath; + AZStd::vector m_primaryColors3x1; + AZStd::unique_ptr m_localFileIO; + + void SetUp() override + { + AllocatorsFixture::SetUp(); + + m_testImageFolder = AZ::Test::GetEngineRootPath() + "/Gems/Atom/Utils/Code/Tests/PngTestImages/"; + m_tempPngFilePath = m_testImageFolder + "temp.png"; + + m_localFileIO.reset(aznew AZ::IO::LocalFileIO()); + AZ::IO::FileIOBase::SetInstance(m_localFileIO.get()); + + AZ::IO::FileIOBase::GetInstance()->Remove(m_tempPngFilePath.c_str()); + + m_primaryColors3x1 = { + 255u, 0u, 0u, 255u, + 0u, 255u, 0u, 255u, + 0u, 0u, 255u, 255u + }; + } + + void TearDown() override + { + m_testImageFolder = AZStd::string{}; + m_tempPngFilePath = AZStd::string{}; + m_primaryColors3x1 = AZStd::vector{}; + + AZ::IO::FileIOBase::SetInstance(nullptr); + m_localFileIO.reset(); + + AllocatorsFixture::TearDown(); + } + + struct Color3 : public AZStd::array + { + using Base = AZStd::array; + Color3(uint8_t r, uint8_t g, uint8_t b) : Base({r, g, b}) {} + Color3(const uint8_t* raw) : Base({raw[0], raw[1], raw[2]}) {} + }; + + struct Color4 : public AZStd::array + { + using Base = AZStd::array; + Color4(uint8_t r, uint8_t g, uint8_t b, uint8_t a) : Base({r, g, b, a}) {} + Color4(const uint8_t* raw) : Base({raw[0], raw[1], raw[2], raw[3]}) {} + }; + }; + + TEST_F(PngFileTests, LoadRgb) + { + PngFile image = PngFile::Load((m_testImageFolder + "ColorChart_rgb.png").c_str()); + EXPECT_TRUE(image.IsValid()); + EXPECT_EQ(image.GetBufferFormat(), PngFile::Format::RGB); + EXPECT_EQ(image.GetWidth(), 3); + EXPECT_EQ(image.GetHeight(), 2); + EXPECT_EQ(image.GetBuffer().size(), 18); + EXPECT_EQ(Color3(image.GetBuffer().begin() + 0), Color3(255u, 0u, 0u)); + EXPECT_EQ(Color3(image.GetBuffer().begin() + 3), Color3(0u, 255u, 0u)); + EXPECT_EQ(Color3(image.GetBuffer().begin() + 6), Color3(0u, 0u, 255u)); + EXPECT_EQ(Color3(image.GetBuffer().begin() + 9), Color3(255u, 255u, 0u)); + EXPECT_EQ(Color3(image.GetBuffer().begin() + 12), Color3(0u, 255u, 255u)); + EXPECT_EQ(Color3(image.GetBuffer().begin() + 15), Color3(255u, 0u, 255u)); + } + + TEST_F(PngFileTests, LoadRgba) + { + PngFile image = PngFile::Load((m_testImageFolder + "ColorChart_rgba.png").c_str()); + EXPECT_TRUE(image.IsValid()); + EXPECT_EQ(image.GetBufferFormat(), PngFile::Format::RGBA); + EXPECT_EQ(image.GetWidth(), 3); + EXPECT_EQ(image.GetHeight(), 2); + EXPECT_EQ(image.GetBuffer().size(), 24); + EXPECT_EQ(Color4(image.GetBuffer().begin() + 0), Color4(255u, 0u, 0u, 200u)); + EXPECT_EQ(Color4(image.GetBuffer().begin() + 4), Color4(0u, 255u, 0u, 150u)); + EXPECT_EQ(Color4(image.GetBuffer().begin() + 8), Color4(0u, 0u, 255u, 100u)); + EXPECT_EQ(Color4(image.GetBuffer().begin() + 12), Color4(255u, 255u, 0u, 125u)); + EXPECT_EQ(Color4(image.GetBuffer().begin() + 16), Color4(0u, 255u, 255u, 175u)); + EXPECT_EQ(Color4(image.GetBuffer().begin() + 20), Color4(255u, 0u, 255u, 75u)); + } + + TEST_F(PngFileTests, LoadRgbaStripAlpha) + { + PngFile::LoadSettings loadSettings; + loadSettings.m_stripAlpha = true; + + PngFile image = PngFile::Load((m_testImageFolder + "ColorChart_rgba.png").c_str(), loadSettings); + // Note these checks are identical to the LoadRgb test. + EXPECT_TRUE(image.IsValid()); + EXPECT_EQ(image.GetBufferFormat(), PngFile::Format::RGB); + EXPECT_EQ(image.GetWidth(), 3); + EXPECT_EQ(image.GetHeight(), 2); + EXPECT_EQ(image.GetBuffer().size(), 18); + EXPECT_EQ(Color3(image.GetBuffer().begin() + 0), Color3(255u, 0u, 0u)); + EXPECT_EQ(Color3(image.GetBuffer().begin() + 3), Color3(0u, 255u, 0u)); + EXPECT_EQ(Color3(image.GetBuffer().begin() + 6), Color3(0u, 0u, 255u)); + EXPECT_EQ(Color3(image.GetBuffer().begin() + 9), Color3(255u, 255u, 0u)); + EXPECT_EQ(Color3(image.GetBuffer().begin() + 12), Color3(0u, 255u, 255u)); + EXPECT_EQ(Color3(image.GetBuffer().begin() + 15), Color3(255u, 0u, 255u)); + } + + TEST_F(PngFileTests, LoadColorPaletteTwoBits) + { + PngFile image = PngFile::Load((m_testImageFolder + "ColorPalette_2bit.png").c_str()); + EXPECT_TRUE(image.IsValid()); + EXPECT_EQ(image.GetBufferFormat(), PngFile::Format::RGB); + EXPECT_EQ(image.GetWidth(), 1); + EXPECT_EQ(image.GetHeight(), 3); + EXPECT_EQ(image.GetBuffer().size(), 9); + EXPECT_EQ(Color3(image.GetBuffer().begin() + 0), Color3(255u, 0u, 0u)); + EXPECT_EQ(Color3(image.GetBuffer().begin() + 3), Color3(0u, 255u, 0u)); + EXPECT_EQ(Color3(image.GetBuffer().begin() + 6), Color3(0u, 0u, 255u)); + } + + TEST_F(PngFileTests, LoadGrayscaleOneBit) + { + PngFile image = PngFile::Load((m_testImageFolder + "GrayPalette_1bit.png").c_str()); + EXPECT_TRUE(image.IsValid()); + EXPECT_EQ(image.GetBufferFormat(), PngFile::Format::RGB); + EXPECT_EQ(image.GetWidth(), 1); + EXPECT_EQ(image.GetHeight(), 2); + EXPECT_EQ(image.GetBuffer().size(), 6); + EXPECT_EQ(Color3(image.GetBuffer().begin() + 0), Color3(0u, 0u, 0u)); + EXPECT_EQ(Color3(image.GetBuffer().begin() + 3), Color3(255u, 255u, 255u)); + } + + TEST_F(PngFileTests, LoadRgba64Bits) + { + PngFile image = PngFile::Load((m_testImageFolder + "Gradient_rgb_16bpc.png").c_str()); + EXPECT_TRUE(image.IsValid()); + EXPECT_EQ(image.GetBufferFormat(), PngFile::Format::RGB); + EXPECT_EQ(image.GetWidth(), 5); + EXPECT_EQ(image.GetHeight(), 1); + EXPECT_EQ(image.GetBuffer().size(), 15); + // The values in this file are 30.0f, 30.1f, 30.2f, 30.3f, 30.4f. But we use PNG_TRANSFORM_STRIP_16 to reduce them to 8 bits per channel for simplicity. + EXPECT_EQ(Color3(image.GetBuffer().begin() + 0), Color3(76u, 0u, 0u)); + EXPECT_EQ(Color3(image.GetBuffer().begin() + 3), Color3(77u, 0u, 0u)); + EXPECT_EQ(Color3(image.GetBuffer().begin() + 6), Color3(77u, 0u, 0u)); + EXPECT_EQ(Color3(image.GetBuffer().begin() + 9), Color3(77u, 0u, 0u)); + EXPECT_EQ(Color3(image.GetBuffer().begin() + 12), Color3(77u, 0u, 0u)); + } + + TEST_F(PngFileTests, CreateCopy) + { + AZStd::vector data = m_primaryColors3x1; + + PngFile savedImage = PngFile::Create(AZ::RHI::Size{3, 1, 0}, AZ::RHI::Format::R8G8B8A8_UNORM, data); + EXPECT_TRUE(savedImage.IsValid()); + EXPECT_EQ(savedImage.GetWidth(), 3); + EXPECT_EQ(savedImage.GetHeight(), 1); + EXPECT_EQ(savedImage.GetBuffer(), data); + } + + TEST_F(PngFileTests, CreateMove) + { + AZStd::vector data = m_primaryColors3x1; + + PngFile savedImage = PngFile::Create(AZ::RHI::Size{3, 1, 0}, AZ::RHI::Format::R8G8B8A8_UNORM, AZStd::move(data)); + EXPECT_TRUE(savedImage.IsValid()); + EXPECT_EQ(savedImage.GetWidth(), 3); + EXPECT_EQ(savedImage.GetHeight(), 1); + EXPECT_EQ(savedImage.GetBuffer(), m_primaryColors3x1); + EXPECT_TRUE(data.empty()); // The data should have been moved + } + + TEST_F(PngFileTests, SaveRgba) + { + PngFile savedImage = PngFile::Create(AZ::RHI::Size{3, 1, 0}, AZ::RHI::Format::R8G8B8A8_UNORM, m_primaryColors3x1); + bool result = savedImage.Save(m_tempPngFilePath.c_str()); + EXPECT_TRUE(result); + + PngFile loadedImage = PngFile::Load(m_tempPngFilePath.c_str()); + EXPECT_TRUE(loadedImage.IsValid()); + EXPECT_EQ(loadedImage.GetBufferFormat(), savedImage.GetBufferFormat()); + EXPECT_EQ(loadedImage.GetWidth(), savedImage.GetWidth()); + EXPECT_EQ(loadedImage.GetHeight(), savedImage.GetHeight()); + EXPECT_EQ(loadedImage.GetBuffer(), savedImage.GetBuffer()); + } + + TEST_F(PngFileTests, SaveRgbaStripAlpha) + { + PngFile savedImage = PngFile::Create(AZ::RHI::Size{3, 1, 0}, AZ::RHI::Format::R8G8B8A8_UNORM, m_primaryColors3x1); + + PngFile::SaveSettings saveSettings; + saveSettings.m_stripAlpha = true; + + bool result = savedImage.Save(m_tempPngFilePath.c_str(), saveSettings); + EXPECT_TRUE(result); + + // The alpha was stripped when saving. Now we load the data without stripping anything and should find + // that there is no alpha channel. + + PngFile loadedImage = PngFile::Load(m_tempPngFilePath.c_str()); + + // The dimensions are the same... + EXPECT_TRUE(loadedImage.IsValid()); + EXPECT_EQ(loadedImage.GetWidth(), savedImage.GetWidth()); + EXPECT_EQ(loadedImage.GetHeight(), savedImage.GetHeight()); + + // ... but the format is different + EXPECT_NE(loadedImage.GetBufferFormat(), savedImage.GetBufferFormat()); + EXPECT_EQ(loadedImage.GetBufferFormat(), PngFile::Format::RGB); + + // ... and the loaded data is smaller + EXPECT_NE(loadedImage.GetBuffer(), savedImage.GetBuffer()); + EXPECT_EQ(Color3(loadedImage.GetBuffer().begin() + 0), Color3(255u, 0u, 0u)); + EXPECT_EQ(Color3(loadedImage.GetBuffer().begin() + 3), Color3(0u, 255u, 0u)); + EXPECT_EQ(Color3(loadedImage.GetBuffer().begin() + 6), Color3(0u, 0u, 255u)); + } + + TEST_F(PngFileTests, Error_CreateUnsupportedFormat) + { + AZStd::vector data = m_primaryColors3x1; + + AZStd::string gotErrorMessage; + + PngFile savedImage = PngFile::Create(AZ::RHI::Size{3, 1, 0}, AZ::RHI::Format::R32_UINT, data, + [&gotErrorMessage](const char* errorMessage) { gotErrorMessage = errorMessage; }); + + EXPECT_FALSE(savedImage.IsValid()); + EXPECT_TRUE(gotErrorMessage.find("unsupported format R32_UINT") != AZStd::string::npos); + } + + TEST_F(PngFileTests, Error_CreateIncorrectBufferSize) + { + AZStd::vector data = m_primaryColors3x1; + + AZStd::string gotErrorMessage; + + PngFile savedImage = PngFile::Create(AZ::RHI::Size{3, 2, 0}, AZ::RHI::Format::R8G8B8A8_UNORM, data, + [&gotErrorMessage](const char* errorMessage) { gotErrorMessage = errorMessage; }); + + EXPECT_FALSE(savedImage.IsValid()); + EXPECT_TRUE(gotErrorMessage.find("does not match") != AZStd::string::npos); + } + + TEST_F(PngFileTests, Error_LoadFileNotFound) + { + AZStd::string gotErrorMessage; + + PngFile::LoadSettings loadSettings; + loadSettings.m_errorHandler = [&gotErrorMessage](const char* errorMessage) { gotErrorMessage = errorMessage; }; + + PngFile image = PngFile::Load((m_testImageFolder + "DoesNotExist.png").c_str(), loadSettings); + EXPECT_FALSE(image.IsValid()); + EXPECT_TRUE(gotErrorMessage.find("not open file") != AZStd::string::npos); + } + + TEST_F(PngFileTests, Error_LoadEmptyFile) + { + AZStd::string gotErrorMessage; + + PngFile::LoadSettings loadSettings; + loadSettings.m_errorHandler = [&gotErrorMessage](const char* errorMessage) { gotErrorMessage = errorMessage; }; + + PngFile image = PngFile::Load((m_testImageFolder + "EmptyFile.png").c_str(), loadSettings); + EXPECT_FALSE(image.IsValid()); + EXPECT_TRUE(gotErrorMessage.find("Invalid png header") != AZStd::string::npos); + } + + TEST_F(PngFileTests, Error_LoadNotPngFile) + { + AZStd::string gotErrorMessage; + + PngFile::LoadSettings loadSettings; + loadSettings.m_errorHandler = [&gotErrorMessage](const char* errorMessage) { gotErrorMessage = errorMessage; }; + + PngFile image = PngFile::Load((m_testImageFolder + "ColorChart_rgba.jpg").c_str(), loadSettings); + EXPECT_FALSE(image.IsValid()); + EXPECT_TRUE(gotErrorMessage.find("Invalid png header") != AZStd::string::npos); + } + + TEST_F(PngFileTests, Error_SaveInvalidPngFile) + { + AZStd::string gotErrorMessage; + + PngFile::SaveSettings saveSettings; + saveSettings.m_errorHandler = [&gotErrorMessage](const char* errorMessage) { gotErrorMessage = errorMessage; }; + + PngFile savedImage; + bool result = savedImage.Save(m_tempPngFilePath.c_str(), saveSettings); + EXPECT_FALSE(result); + EXPECT_TRUE(gotErrorMessage.find("PngFile is invalid") != AZStd::string::npos); + EXPECT_FALSE(AZ::IO::FileIOBase::GetInstance()->Exists(m_tempPngFilePath.c_str())); + } + + TEST_F(PngFileTests, Error_SaveOverLockedFile) + { + AZStd::string gotErrorMessage; + + PngFile::SaveSettings saveSettings; + saveSettings.m_errorHandler = [&gotErrorMessage](const char* errorMessage) { gotErrorMessage = errorMessage; }; + + AZ::IO::FileIOStream tempFileStream; + tempFileStream.Open(m_tempPngFilePath.c_str(), AZ::IO::OpenMode::ModeWrite | AZ::IO::OpenMode::ModeCreatePath); + + PngFile savedImage = PngFile::Create(AZ::RHI::Size{3, 1, 0}, AZ::RHI::Format::R8G8B8A8_UNORM, m_primaryColors3x1); + bool result = savedImage.Save(m_tempPngFilePath.c_str(), saveSettings); + EXPECT_FALSE(result); + EXPECT_TRUE(gotErrorMessage.find("not open file") != AZStd::string::npos); + } +} diff --git a/Gems/Atom/Utils/Code/Tests/PngTestImages/.gitignore b/Gems/Atom/Utils/Code/Tests/PngTestImages/.gitignore new file mode 100644 index 0000000000..ea08b96982 --- /dev/null +++ b/Gems/Atom/Utils/Code/Tests/PngTestImages/.gitignore @@ -0,0 +1 @@ +temp.png \ No newline at end of file diff --git a/Gems/Atom/Utils/Code/Tests/PngTestImages/ColorChart_rgb.png b/Gems/Atom/Utils/Code/Tests/PngTestImages/ColorChart_rgb.png new file mode 100644 index 0000000000..4dc14cc8eb --- /dev/null +++ b/Gems/Atom/Utils/Code/Tests/PngTestImages/ColorChart_rgb.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:af0d0d079354495ff96aa266ecb4092d679e6c5a952e8b2d4ee743e538d54809 +size 126 diff --git a/Gems/Atom/Utils/Code/Tests/PngTestImages/ColorChart_rgba.jpg b/Gems/Atom/Utils/Code/Tests/PngTestImages/ColorChart_rgba.jpg new file mode 100644 index 0000000000..ea6f2283bd --- /dev/null +++ b/Gems/Atom/Utils/Code/Tests/PngTestImages/ColorChart_rgba.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:01cad8dd75c9a26169e858960817424362c16cbf2a8df8bc55857f6172ce2526 +size 834 diff --git a/Gems/Atom/Utils/Code/Tests/PngTestImages/ColorChart_rgba.png b/Gems/Atom/Utils/Code/Tests/PngTestImages/ColorChart_rgba.png new file mode 100644 index 0000000000..943208e2cf --- /dev/null +++ b/Gems/Atom/Utils/Code/Tests/PngTestImages/ColorChart_rgba.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6ddb5f70eaf72fe09add6cc7cdf7cbba2d327ab312bb9bde73d7f456f80b8d6e +size 141 diff --git a/Gems/Atom/Utils/Code/Tests/PngTestImages/ColorPalette_2bit.png b/Gems/Atom/Utils/Code/Tests/PngTestImages/ColorPalette_2bit.png new file mode 100644 index 0000000000..38bdbc9755 --- /dev/null +++ b/Gems/Atom/Utils/Code/Tests/PngTestImages/ColorPalette_2bit.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b50e93bbbd320d69752df2c25b4118c0198226a0d230781a6ed93f89b7eea053 +size 142 diff --git a/Gems/Atom/Utils/Code/Tests/PngTestImages/EmptyFile.png b/Gems/Atom/Utils/Code/Tests/PngTestImages/EmptyFile.png new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Gems/Atom/Utils/Code/Tests/PngTestImages/Gradient_rgb_16bpc.png b/Gems/Atom/Utils/Code/Tests/PngTestImages/Gradient_rgb_16bpc.png new file mode 100644 index 0000000000..c28c2baf36 --- /dev/null +++ b/Gems/Atom/Utils/Code/Tests/PngTestImages/Gradient_rgb_16bpc.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cb56e8bf15a4727bcebba579cb97a36e1f26e4a4870b2b4a38bdbd4b4bd076fd +size 127 diff --git a/Gems/Atom/Utils/Code/Tests/PngTestImages/GrayPalette_1bit.png b/Gems/Atom/Utils/Code/Tests/PngTestImages/GrayPalette_1bit.png new file mode 100644 index 0000000000..752e100691 --- /dev/null +++ b/Gems/Atom/Utils/Code/Tests/PngTestImages/GrayPalette_1bit.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:710fd5c80af49a42249a471ded6c9119be79d6748218073bb8297ffb4b4e62d1 +size 137 diff --git a/Gems/Atom/Utils/Code/atom_utils_tests_files.cmake b/Gems/Atom/Utils/Code/atom_utils_tests_files.cmake index 9069eeb682..5b43758a60 100644 --- a/Gems/Atom/Utils/Code/atom_utils_tests_files.cmake +++ b/Gems/Atom/Utils/Code/atom_utils_tests_files.cmake @@ -8,5 +8,6 @@ set(FILES Tests/ImageComparisonTests.cpp + Tests/PngFileTests.cpp Tests/StableDynamicArrayTests.cpp ) From e96793659a6c7926b054ba9d7f7b189dc3787c08 Mon Sep 17 00:00:00 2001 From: rgba16f <82187279+rgba16f@users.noreply.github.com> Date: Fri, 10 Sep 2021 10:15:40 -0500 Subject: [PATCH 05/51] Fixes to get Mac & iOS building with new PNG support Signed-off-by: rgba16f <82187279+rgba16f@users.noreply.github.com> --- .../Atom/Utils/Code/Include/Atom/Utils/PngFile.h | 6 ++++-- Gems/Atom/Utils/Code/Source/PngFile.cpp | 16 ++++++++-------- .../Platform/Mac/BuiltInPackages_mac.cmake | 2 +- .../Platform/iOS/BuiltInPackages_ios.cmake | 2 +- 4 files changed, 14 insertions(+), 12 deletions(-) diff --git a/Gems/Atom/Utils/Code/Include/Atom/Utils/PngFile.h b/Gems/Atom/Utils/Code/Include/Atom/Utils/PngFile.h index 7fb2c768ad..1de27e9b96 100644 --- a/Gems/Atom/Utils/Code/Include/Atom/Utils/PngFile.h +++ b/Gems/Atom/Utils/Code/Include/Atom/Utils/PngFile.h @@ -29,15 +29,17 @@ namespace AZ struct LoadSettings { - ErrorHandler m_errorHandler = {}; //!< optional callback function describing any errors that are encountered + ErrorHandler m_errorHandler{}; //!< optional callback function describing any errors that are encountered bool m_stripAlpha = false; //!< the alpha channel will be skipped, loading an RGBA image as RGB + LoadSettings() {}; // clang errors out if this is not provided. }; struct SaveSettings { - ErrorHandler m_errorHandler = {}; //!< optional callback function describing any errors that are encountered + ErrorHandler m_errorHandler{}; //!< optional callback function describing any errors that are encountered bool m_stripAlpha = false; //!< the alpha channel will be skipped, saving an RGBA buffer as RGB int m_compressionLevel = 6; //!< this is the zlib compression level. See png_set_compression_level in png.h + SaveSettings() {}; // clang errors out if this is not provided. }; // To keep things simple for now we limit all images to RGB and RGBA, 8 bits per channel. diff --git a/Gems/Atom/Utils/Code/Source/PngFile.cpp b/Gems/Atom/Utils/Code/Source/PngFile.cpp index 056e199d40..09a5d950e5 100644 --- a/Gems/Atom/Utils/Code/Source/PngFile.cpp +++ b/Gems/Atom/Utils/Code/Source/PngFile.cpp @@ -74,7 +74,8 @@ namespace AZ // For documentation of this code, see http://www.libpng.org/pub/png/libpng-1.4.0-manual.pdf chapter 3 FILE* fp = NULL; - if (fopen_s(&fp, path, "rb") || !fp) + azfopen(&fp, path, "rb"); // return type differs across platforms so can't do inside if + if (!fp) { loadSettings.m_errorHandler("Cannot open file."); return {}; @@ -127,8 +128,7 @@ namespace AZ return {}; } -#pragma warning(push) -#pragma warning(disable: 4611) // Disables "interaction between '_setjmp' and C++ object destruction is non-portable". See https://docs.microsoft.com/en-us/cpp/preprocessor/warning?view=msvc-160 +AZ_PUSH_DISABLE_WARNING(4611, "-Wunknown-warning-option") // Disables "interaction between '_setjmp' and C++ object destruction is non-portable". See https://docs.microsoft.com/en-us/cpp/preprocessor/warning?view=msvc-160 if (setjmp(png_jmpbuf(png_ptr))) { png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); @@ -136,7 +136,7 @@ namespace AZ // We don't report an error message here because the user_error_fn should have done that already. return {}; } -#pragma warning(pop) +AZ_POP_DISABLE_WARNING png_init_io(png_ptr, fp); @@ -221,7 +221,8 @@ namespace AZ // For documentation of this code, see http://www.libpng.org/pub/png/libpng-1.4.0-manual.pdf chapter 4 FILE* fp = NULL; - if (fopen_s(&fp, path, "wb") || !fp) + azfopen(&fp, path, "wb"); // return type differs across platforms so can't do inside if + if (!fp) { saveSettings.m_errorHandler("Cannot open file."); return false; @@ -248,8 +249,7 @@ namespace AZ return false; } -#pragma warning(push) -#pragma warning(disable: 4611) // Disables "interaction between '_setjmp' and C++ object destruction is non-portable". See https://docs.microsoft.com/en-us/cpp/preprocessor/warning?view=msvc-160 +AZ_PUSH_DISABLE_WARNING(4611, "-Wunknown-warning-option") // Disables "interaction between '_setjmp' and C++ object destruction is non-portable". See https://docs.microsoft.com/en-us/cpp/preprocessor/warning?view=msvc-160 if (setjmp(png_jmpbuf(png_ptr))) { png_destroy_write_struct(&png_ptr, &info_ptr); @@ -257,7 +257,7 @@ namespace AZ // We don't report an error message here because the user_error_fn should have done that already. return false; } -#pragma warning(pop) +AZ_POP_DISABLE_WARNING png_init_io(png_ptr, fp); diff --git a/cmake/3rdParty/Platform/Mac/BuiltInPackages_mac.cmake b/cmake/3rdParty/Platform/Mac/BuiltInPackages_mac.cmake index f64dbe82c7..f0ac0d6d54 100644 --- a/cmake/3rdParty/Platform/Mac/BuiltInPackages_mac.cmake +++ b/cmake/3rdParty/Platform/Mac/BuiltInPackages_mac.cmake @@ -39,7 +39,7 @@ ly_associate_package(PACKAGE_NAME googletest-1.8.1-rev4-mac ly_associate_package(PACKAGE_NAME googlebenchmark-1.5.0-rev2-mac TARGETS GoogleBenchmark PACKAGE_HASH ad25de0146769c91e179953d845de2bec8ed4a691f973f47e3eb37639381f665) ly_associate_package(PACKAGE_NAME OpenSSL-1.1.1b-rev1-mac TARGETS OpenSSL PACKAGE_HASH 28adc1c0616ac0482b2a9d7b4a3a3635a1020e87b163f8aba687c501cf35f96c) ly_associate_package(PACKAGE_NAME qt-5.15.2-rev5-mac TARGETS Qt PACKAGE_HASH 9d25918351898b308ded3e9e571fff6f26311b2071aeafd00dd5b249fdf53f7e) -ly_associate_package(PACKAGE_NAME libpng-1.6.37-mac TARGETS libpng PACKAGE_HASH 0000000000000000000000000000000000000000000000000000000000000000) +ly_associate_package(PACKAGE_NAME libpng-1.6.37-mac TARGETS libpng PACKAGE_HASH 1ad76cd038ccc1f288f83c5fe2859a0f35c5154e1fe7658e1230cc428d318a8b) ly_associate_package(PACKAGE_NAME libsamplerate-0.2.1-rev2-mac TARGETS libsamplerate PACKAGE_HASH b912af40c0ac197af9c43d85004395ba92a6a859a24b7eacd920fed5854a97fe) ly_associate_package(PACKAGE_NAME zlib-1.2.11-rev2-mac TARGETS zlib PACKAGE_HASH 21714e8a6de4f2523ee92a7f52d51fbee29c5f37ced334e00dc3c029115b472e) ly_associate_package(PACKAGE_NAME squish-ccr-deb557d-rev1-mac TARGETS squish-ccr PACKAGE_HASH 155bfbfa17c19a9cd2ef025de14c5db598f4290045d5b0d83ab58cb345089a77) diff --git a/cmake/3rdParty/Platform/iOS/BuiltInPackages_ios.cmake b/cmake/3rdParty/Platform/iOS/BuiltInPackages_ios.cmake index ec52936460..d152acfeae 100644 --- a/cmake/3rdParty/Platform/iOS/BuiltInPackages_ios.cmake +++ b/cmake/3rdParty/Platform/iOS/BuiltInPackages_ios.cmake @@ -25,7 +25,7 @@ ly_associate_package(PACKAGE_NAME PhysX-4.1.2.29882248-rev3-ios TARGETS PhysX ly_associate_package(PACKAGE_NAME mikkelsen-1.0.0.4-ios TARGETS mikkelsen PACKAGE_HASH 976aaa3ccd8582346132a10af253822ccc5d5bcc9ea5ba44d27848f65ee88a8a) ly_associate_package(PACKAGE_NAME googletest-1.8.1-rev4-ios TARGETS googletest PACKAGE_HASH 2f121ad9784c0ab73dfaa58e1fee05440a82a07cc556bec162eeb407688111a7) ly_associate_package(PACKAGE_NAME googlebenchmark-1.5.0-rev2-ios TARGETS GoogleBenchmark PACKAGE_HASH c2ffaed2b658892b1bcf81dee4b44cd1cb09fc78d55584ef5cb8ab87f2d8d1ae) -ly_associate_package(PACKAGE_NAME libpng-1.6.37-mac TARGETS libpng PACKAGE_HASH 0000000000000000000000000000000000000000000000000000000000000000) +ly_associate_package(PACKAGE_NAME libpng-1.6.37-ios TARGETS libpng PACKAGE_HASH 18a8217721083c4dc46514105be43ca764fa9c994a74aa0b57766ea6f8187e7b) ly_associate_package(PACKAGE_NAME libsamplerate-0.2.1-rev2-ios TARGETS libsamplerate PACKAGE_HASH 7656b961697f490d4f9c35d2e61559f6fc38c32102e542a33c212cd618fc2119) ly_associate_package(PACKAGE_NAME OpenSSL-1.1.1b-rev1-ios TARGETS OpenSSL PACKAGE_HASH cd0dfce3086a7172777c63dadbaf0ac3695b676119ecb6d0614b5fb1da03462f) ly_associate_package(PACKAGE_NAME zlib-1.2.11-rev2-ios TARGETS zlib PACKAGE_HASH a59fc0f83a02c616b679799310e9d86fde84514c6d2acefa12c6def0ae4a880c) From b44441b286c6d06b8489847bc1d41a50d2941f29 Mon Sep 17 00:00:00 2001 From: santorac <55155825+santorac@users.noreply.github.com> Date: Mon, 13 Sep 2021 17:19:22 -0700 Subject: [PATCH 06/51] Improved PngFile unit test a bit, added another check Signed-off-by: santorac <55155825+santorac@users.noreply.github.com> --- Gems/Atom/Utils/Code/Tests/PngFileTests.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Gems/Atom/Utils/Code/Tests/PngFileTests.cpp b/Gems/Atom/Utils/Code/Tests/PngFileTests.cpp index 90181d8040..2f3fb2c20c 100644 --- a/Gems/Atom/Utils/Code/Tests/PngFileTests.cpp +++ b/Gems/Atom/Utils/Code/Tests/PngFileTests.cpp @@ -318,6 +318,7 @@ namespace UnitTest AZ::IO::FileIOStream tempFileStream; tempFileStream.Open(m_tempPngFilePath.c_str(), AZ::IO::OpenMode::ModeWrite | AZ::IO::OpenMode::ModeCreatePath); + EXPECT_TRUE(tempFileStream.IsOpen()); PngFile savedImage = PngFile::Create(AZ::RHI::Size{3, 1, 0}, AZ::RHI::Format::R8G8B8A8_UNORM, m_primaryColors3x1); bool result = savedImage.Save(m_tempPngFilePath.c_str(), saveSettings); From f99766f556bd1df07646015079884040198193c4 Mon Sep 17 00:00:00 2001 From: santorac <55155825+santorac@users.noreply.github.com> Date: Mon, 13 Sep 2021 22:06:08 -0700 Subject: [PATCH 07/51] Update unit test to use AZ::IO::Path. Signed-off-by: santorac <55155825+santorac@users.noreply.github.com> --- Gems/Atom/Utils/Code/Tests/PngFileTests.cpp | 32 +++++++++++---------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/Gems/Atom/Utils/Code/Tests/PngFileTests.cpp b/Gems/Atom/Utils/Code/Tests/PngFileTests.cpp index 2f3fb2c20c..4418358cd2 100644 --- a/Gems/Atom/Utils/Code/Tests/PngFileTests.cpp +++ b/Gems/Atom/Utils/Code/Tests/PngFileTests.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include namespace UnitTest @@ -21,8 +22,8 @@ namespace UnitTest : public AllocatorsFixture { protected: - AZStd::string m_testImageFolder; - AZStd::string m_tempPngFilePath; + AZ::IO::Path m_testImageFolder; + AZ::IO::Path m_tempPngFilePath; AZStd::vector m_primaryColors3x1; AZStd::unique_ptr m_localFileIO; @@ -30,9 +31,10 @@ namespace UnitTest { AllocatorsFixture::SetUp(); - m_testImageFolder = AZ::Test::GetEngineRootPath() + "/Gems/Atom/Utils/Code/Tests/PngTestImages/"; - m_tempPngFilePath = m_testImageFolder + "temp.png"; + m_testImageFolder = AZ::IO::Path(AZ::Test::GetEngineRootPath()) / AZ::IO::Path("Gems/Atom/Utils/Code/Tests/PngTestImages", '/'); + m_tempPngFilePath = m_testImageFolder / "temp.png"; + m_localFileIO.reset(aznew AZ::IO::LocalFileIO()); AZ::IO::FileIOBase::SetInstance(m_localFileIO.get()); @@ -47,8 +49,8 @@ namespace UnitTest void TearDown() override { - m_testImageFolder = AZStd::string{}; - m_tempPngFilePath = AZStd::string{}; + m_testImageFolder = AZ::IO::Path{}; + m_tempPngFilePath = AZ::IO::Path{}; m_primaryColors3x1 = AZStd::vector{}; AZ::IO::FileIOBase::SetInstance(nullptr); @@ -74,7 +76,7 @@ namespace UnitTest TEST_F(PngFileTests, LoadRgb) { - PngFile image = PngFile::Load((m_testImageFolder + "ColorChart_rgb.png").c_str()); + PngFile image = PngFile::Load((m_testImageFolder / "ColorChart_rgb.png").c_str()); EXPECT_TRUE(image.IsValid()); EXPECT_EQ(image.GetBufferFormat(), PngFile::Format::RGB); EXPECT_EQ(image.GetWidth(), 3); @@ -90,7 +92,7 @@ namespace UnitTest TEST_F(PngFileTests, LoadRgba) { - PngFile image = PngFile::Load((m_testImageFolder + "ColorChart_rgba.png").c_str()); + PngFile image = PngFile::Load((m_testImageFolder / "ColorChart_rgba.png").c_str()); EXPECT_TRUE(image.IsValid()); EXPECT_EQ(image.GetBufferFormat(), PngFile::Format::RGBA); EXPECT_EQ(image.GetWidth(), 3); @@ -109,7 +111,7 @@ namespace UnitTest PngFile::LoadSettings loadSettings; loadSettings.m_stripAlpha = true; - PngFile image = PngFile::Load((m_testImageFolder + "ColorChart_rgba.png").c_str(), loadSettings); + PngFile image = PngFile::Load((m_testImageFolder / "ColorChart_rgba.png").c_str(), loadSettings); // Note these checks are identical to the LoadRgb test. EXPECT_TRUE(image.IsValid()); EXPECT_EQ(image.GetBufferFormat(), PngFile::Format::RGB); @@ -126,7 +128,7 @@ namespace UnitTest TEST_F(PngFileTests, LoadColorPaletteTwoBits) { - PngFile image = PngFile::Load((m_testImageFolder + "ColorPalette_2bit.png").c_str()); + PngFile image = PngFile::Load((m_testImageFolder / "ColorPalette_2bit.png").c_str()); EXPECT_TRUE(image.IsValid()); EXPECT_EQ(image.GetBufferFormat(), PngFile::Format::RGB); EXPECT_EQ(image.GetWidth(), 1); @@ -139,7 +141,7 @@ namespace UnitTest TEST_F(PngFileTests, LoadGrayscaleOneBit) { - PngFile image = PngFile::Load((m_testImageFolder + "GrayPalette_1bit.png").c_str()); + PngFile image = PngFile::Load((m_testImageFolder / "GrayPalette_1bit.png").c_str()); EXPECT_TRUE(image.IsValid()); EXPECT_EQ(image.GetBufferFormat(), PngFile::Format::RGB); EXPECT_EQ(image.GetWidth(), 1); @@ -151,7 +153,7 @@ namespace UnitTest TEST_F(PngFileTests, LoadRgba64Bits) { - PngFile image = PngFile::Load((m_testImageFolder + "Gradient_rgb_16bpc.png").c_str()); + PngFile image = PngFile::Load((m_testImageFolder / "Gradient_rgb_16bpc.png").c_str()); EXPECT_TRUE(image.IsValid()); EXPECT_EQ(image.GetBufferFormat(), PngFile::Format::RGB); EXPECT_EQ(image.GetWidth(), 5); @@ -266,7 +268,7 @@ namespace UnitTest PngFile::LoadSettings loadSettings; loadSettings.m_errorHandler = [&gotErrorMessage](const char* errorMessage) { gotErrorMessage = errorMessage; }; - PngFile image = PngFile::Load((m_testImageFolder + "DoesNotExist.png").c_str(), loadSettings); + PngFile image = PngFile::Load((m_testImageFolder / "DoesNotExist.png").c_str(), loadSettings); EXPECT_FALSE(image.IsValid()); EXPECT_TRUE(gotErrorMessage.find("not open file") != AZStd::string::npos); } @@ -278,7 +280,7 @@ namespace UnitTest PngFile::LoadSettings loadSettings; loadSettings.m_errorHandler = [&gotErrorMessage](const char* errorMessage) { gotErrorMessage = errorMessage; }; - PngFile image = PngFile::Load((m_testImageFolder + "EmptyFile.png").c_str(), loadSettings); + PngFile image = PngFile::Load((m_testImageFolder / "EmptyFile.png").c_str(), loadSettings); EXPECT_FALSE(image.IsValid()); EXPECT_TRUE(gotErrorMessage.find("Invalid png header") != AZStd::string::npos); } @@ -290,7 +292,7 @@ namespace UnitTest PngFile::LoadSettings loadSettings; loadSettings.m_errorHandler = [&gotErrorMessage](const char* errorMessage) { gotErrorMessage = errorMessage; }; - PngFile image = PngFile::Load((m_testImageFolder + "ColorChart_rgba.jpg").c_str(), loadSettings); + PngFile image = PngFile::Load((m_testImageFolder / "ColorChart_rgba.jpg").c_str(), loadSettings); EXPECT_FALSE(image.IsValid()); EXPECT_TRUE(gotErrorMessage.find("Invalid png header") != AZStd::string::npos); } From c61fc4f67c381dce718990dd9f42e69698f59bc2 Mon Sep 17 00:00:00 2001 From: santorac <55155825+santorac@users.noreply.github.com> Date: Mon, 13 Sep 2021 22:08:25 -0700 Subject: [PATCH 08/51] Removed Error_SaveOverLockedFile because it was failing on mac for some file system reason. This particular test case wasn't that important so it wasn't worth digging into, especially since I don't have a mac handy. Signed-off-by: santorac <55155825+santorac@users.noreply.github.com> --- Gems/Atom/Utils/Code/Tests/PngFileTests.cpp | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/Gems/Atom/Utils/Code/Tests/PngFileTests.cpp b/Gems/Atom/Utils/Code/Tests/PngFileTests.cpp index 4418358cd2..1c5b844204 100644 --- a/Gems/Atom/Utils/Code/Tests/PngFileTests.cpp +++ b/Gems/Atom/Utils/Code/Tests/PngFileTests.cpp @@ -310,21 +310,4 @@ namespace UnitTest EXPECT_TRUE(gotErrorMessage.find("PngFile is invalid") != AZStd::string::npos); EXPECT_FALSE(AZ::IO::FileIOBase::GetInstance()->Exists(m_tempPngFilePath.c_str())); } - - TEST_F(PngFileTests, Error_SaveOverLockedFile) - { - AZStd::string gotErrorMessage; - - PngFile::SaveSettings saveSettings; - saveSettings.m_errorHandler = [&gotErrorMessage](const char* errorMessage) { gotErrorMessage = errorMessage; }; - - AZ::IO::FileIOStream tempFileStream; - tempFileStream.Open(m_tempPngFilePath.c_str(), AZ::IO::OpenMode::ModeWrite | AZ::IO::OpenMode::ModeCreatePath); - EXPECT_TRUE(tempFileStream.IsOpen()); - - PngFile savedImage = PngFile::Create(AZ::RHI::Size{3, 1, 0}, AZ::RHI::Format::R8G8B8A8_UNORM, m_primaryColors3x1); - bool result = savedImage.Save(m_tempPngFilePath.c_str(), saveSettings); - EXPECT_FALSE(result); - EXPECT_TRUE(gotErrorMessage.find("not open file") != AZStd::string::npos); - } } From 860571f1e33c985b7a2e86618c61f5f86e7abd36 Mon Sep 17 00:00:00 2001 From: santorac <55155825+santorac@users.noreply.github.com> Date: Tue, 14 Sep 2021 18:45:22 -0700 Subject: [PATCH 09/51] WIP trying to get android package working. Signed-off-by: santorac <55155825+santorac@users.noreply.github.com> --- .../Common/Code/Source/Platform/Windows/platform_windows.cmake | 1 - Gems/Atom/Utils/Code/Platform/Windows/platform_windows.cmake | 1 - cmake/3rdParty/Platform/Android/BuiltInPackages_android.cmake | 2 +- cmake/3rdParty/Platform/Linux/BuiltInPackages_linux.cmake | 2 +- 4 files changed, 2 insertions(+), 4 deletions(-) diff --git a/Gems/Atom/Feature/Common/Code/Source/Platform/Windows/platform_windows.cmake b/Gems/Atom/Feature/Common/Code/Source/Platform/Windows/platform_windows.cmake index ef84846c0f..7c594fc945 100644 --- a/Gems/Atom/Feature/Common/Code/Source/Platform/Windows/platform_windows.cmake +++ b/Gems/Atom/Feature/Common/Code/Source/Platform/Windows/platform_windows.cmake @@ -13,5 +13,4 @@ endif() set(LY_BUILD_DEPENDENCIES PRIVATE 3rdParty::ilmbase - 3rdParty::libpng ) diff --git a/Gems/Atom/Utils/Code/Platform/Windows/platform_windows.cmake b/Gems/Atom/Utils/Code/Platform/Windows/platform_windows.cmake index ead5aa05de..c1d40c6ad8 100644 --- a/Gems/Atom/Utils/Code/Platform/Windows/platform_windows.cmake +++ b/Gems/Atom/Utils/Code/Platform/Windows/platform_windows.cmake @@ -9,5 +9,4 @@ set(LY_BUILD_DEPENDENCIES PRIVATE 3rdParty::OpenImageIO - 3rdParty::libpng ) diff --git a/cmake/3rdParty/Platform/Android/BuiltInPackages_android.cmake b/cmake/3rdParty/Platform/Android/BuiltInPackages_android.cmake index 4858b85670..e9160a329d 100644 --- a/cmake/3rdParty/Platform/Android/BuiltInPackages_android.cmake +++ b/cmake/3rdParty/Platform/Android/BuiltInPackages_android.cmake @@ -24,7 +24,7 @@ ly_associate_package(PACKAGE_NAME PhysX-4.1.2.29882248-rev3-android TARGETS Phy ly_associate_package(PACKAGE_NAME mikkelsen-1.0.0.4-android TARGETS mikkelsen PACKAGE_HASH 075e8e4940884971063b5a9963014e2e517246fa269c07c7dc55b8cf2cd99705) ly_associate_package(PACKAGE_NAME googletest-1.8.1-rev4-android TARGETS googletest PACKAGE_HASH 95671be75287a61c9533452835c3647e9c1b30f81b34b43bcb0ec1997cc23894) ly_associate_package(PACKAGE_NAME googlebenchmark-1.5.0-rev2-android TARGETS GoogleBenchmark PACKAGE_HASH 20b46e572211a69d7d94ddad1c89ec37bb958711d6ad4025368ac89ea83078fb) -ly_associate_package(PACKAGE_NAME libpng-1.6.37-mac TARGETS libpng PACKAGE_HASH 0000000000000000000000000000000000000000000000000000000000000000) +ly_associate_package(PACKAGE_NAME libpng-1.6.37-android TARGETS libpng PACKAGE_HASH d6adef1d1a90e0163ae2ab82dcc3f23f38b81438d0d8d4c6d219d600881e807e) ly_associate_package(PACKAGE_NAME libsamplerate-0.2.1-rev2-android TARGETS libsamplerate PACKAGE_HASH bf13662afe65d02bcfa16258a4caa9b875534978227d6f9f36c9cfa92b3fb12b) ly_associate_package(PACKAGE_NAME OpenSSL-1.1.1b-rev1-android TARGETS OpenSSL PACKAGE_HASH 4036d4019d722f0e1b7a1621bf60b5a17ca6a65c9c78fd8701cee1131eec8480) ly_associate_package(PACKAGE_NAME zlib-1.2.11-rev2-android TARGETS zlib PACKAGE_HASH 85b730b97176772538cfcacd6b6aaf4655fc2d368d134d6dd55e02f28f183826) diff --git a/cmake/3rdParty/Platform/Linux/BuiltInPackages_linux.cmake b/cmake/3rdParty/Platform/Linux/BuiltInPackages_linux.cmake index 44af91620f..d1bfcfb031 100644 --- a/cmake/3rdParty/Platform/Linux/BuiltInPackages_linux.cmake +++ b/cmake/3rdParty/Platform/Linux/BuiltInPackages_linux.cmake @@ -37,7 +37,7 @@ ly_associate_package(PACKAGE_NAME googletest-1.8.1-rev4-linux ly_associate_package(PACKAGE_NAME googlebenchmark-1.5.0-rev2-linux TARGETS GoogleBenchmark PACKAGE_HASH 4038878f337fc7e0274f0230f71851b385b2e0327c495fc3dd3d1c18a807928d) ly_associate_package(PACKAGE_NAME unwind-1.2.1-linux TARGETS unwind PACKAGE_HASH 3453265fb056e25432f611a61546a25f60388e315515ad39007b5925dd054a77) ly_associate_package(PACKAGE_NAME qt-5.15.2-rev5-linux TARGETS Qt PACKAGE_HASH 76b395897b941a173002845c7219a5f8a799e44b269ffefe8091acc048130f28) -ly_associate_package(PACKAGE_NAME libpng-1.6.37-mac TARGETS libpng PACKAGE_HASH 0000000000000000000000000000000000000000000000000000000000000000) +ly_associate_package(PACKAGE_NAME libpng-1.6.37-linux TARGETS libpng PACKAGE_HASH 0000000000000000000000000000000000000000000000000000000000000000) ly_associate_package(PACKAGE_NAME libsamplerate-0.2.1-rev2-linux TARGETS libsamplerate PACKAGE_HASH 41643c31bc6b7d037f895f89d8d8d6369e906b92eff42b0fe05ee6a100f06261) ly_associate_package(PACKAGE_NAME OpenSSL-1.1.1b-rev2-linux TARGETS OpenSSL PACKAGE_HASH b779426d1e9c5ddf71160d5ae2e639c3b956e0fb5e9fcaf9ce97c4526024e3bc) ly_associate_package(PACKAGE_NAME DirectXShaderCompilerDxc-1.6.2104-o3de-rev3-linux TARGETS DirectXShaderCompilerDxc PACKAGE_HASH 88c4a359325d749bc34090b9ac466424847f3b71ba0de15045cf355c17c07099) From bcef5e4952d76c3b0646f8fccb3bced345a1fb6d Mon Sep 17 00:00:00 2001 From: santorac <55155825+santorac@users.noreply.github.com> Date: Tue, 14 Sep 2021 22:09:46 -0700 Subject: [PATCH 10/51] I think I got the libpng android package working. Note there is a bit of a dependency hack in Gems\Atom\Feature\Common\Code\CMakeLists.txt to work around a mysterious issue; I'll reach out to some experts for investigation. Signed-off-by: santorac <55155825+santorac@users.noreply.github.com> --- Gems/Atom/Feature/Common/Code/CMakeLists.txt | 7 +++++++ Gems/Atom/Utils/Code/CMakeLists.txt | 1 + .../Platform/Android/BuiltInPackages_android.cmake | 2 +- 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/Gems/Atom/Feature/Common/Code/CMakeLists.txt b/Gems/Atom/Feature/Common/Code/CMakeLists.txt index b8c414dcc6..3a38aefe02 100644 --- a/Gems/Atom/Feature/Common/Code/CMakeLists.txt +++ b/Gems/Atom/Feature/Common/Code/CMakeLists.txt @@ -83,6 +83,13 @@ ly_add_target( Include BUILD_DEPENDENCIES PRIVATE + # For some reason zlib and libpng need to be declared here, otherwise libAtom_Feature_Common.so will fail to link + # when building for Android. These libraries are appropriately indicated in Atom/Utils's CCmakeLists.txt and those dependencies are + # supposed to be inherited here through the PUBLIC dependencies chaining ... but something is amiss. libpng and zlib were both + # appearing in the link command so it isn't clear why it was failing. Perhaps it was because libpng appeared before zlib + # in the link command? Somehow repeating them here works around this issue. + 3rdParty::libpng + 3rdParty::zlib AZ::AzCore AZ::AzFramework Gem::Atom_Feature_Common.Static diff --git a/Gems/Atom/Utils/Code/CMakeLists.txt b/Gems/Atom/Utils/Code/CMakeLists.txt index 89bc8dd0a5..0781100e9a 100644 --- a/Gems/Atom/Utils/Code/CMakeLists.txt +++ b/Gems/Atom/Utils/Code/CMakeLists.txt @@ -24,6 +24,7 @@ ly_add_target( Gem::Atom_RHI.Public PUBLIC Gem::Atom_RHI.Reflect + 3rdParty::zlib 3rdParty::libpng ) diff --git a/cmake/3rdParty/Platform/Android/BuiltInPackages_android.cmake b/cmake/3rdParty/Platform/Android/BuiltInPackages_android.cmake index e9160a329d..333b9a5444 100644 --- a/cmake/3rdParty/Platform/Android/BuiltInPackages_android.cmake +++ b/cmake/3rdParty/Platform/Android/BuiltInPackages_android.cmake @@ -24,7 +24,7 @@ ly_associate_package(PACKAGE_NAME PhysX-4.1.2.29882248-rev3-android TARGETS Phy ly_associate_package(PACKAGE_NAME mikkelsen-1.0.0.4-android TARGETS mikkelsen PACKAGE_HASH 075e8e4940884971063b5a9963014e2e517246fa269c07c7dc55b8cf2cd99705) ly_associate_package(PACKAGE_NAME googletest-1.8.1-rev4-android TARGETS googletest PACKAGE_HASH 95671be75287a61c9533452835c3647e9c1b30f81b34b43bcb0ec1997cc23894) ly_associate_package(PACKAGE_NAME googlebenchmark-1.5.0-rev2-android TARGETS GoogleBenchmark PACKAGE_HASH 20b46e572211a69d7d94ddad1c89ec37bb958711d6ad4025368ac89ea83078fb) -ly_associate_package(PACKAGE_NAME libpng-1.6.37-android TARGETS libpng PACKAGE_HASH d6adef1d1a90e0163ae2ab82dcc3f23f38b81438d0d8d4c6d219d600881e807e) +ly_associate_package(PACKAGE_NAME libpng-1.6.37-android TARGETS libpng PACKAGE_HASH 72be3859de38559ed45e4383bc480ad9cc1c080fea2c4d6dd3946c2fb22ad5bb) ly_associate_package(PACKAGE_NAME libsamplerate-0.2.1-rev2-android TARGETS libsamplerate PACKAGE_HASH bf13662afe65d02bcfa16258a4caa9b875534978227d6f9f36c9cfa92b3fb12b) ly_associate_package(PACKAGE_NAME OpenSSL-1.1.1b-rev1-android TARGETS OpenSSL PACKAGE_HASH 4036d4019d722f0e1b7a1621bf60b5a17ca6a65c9c78fd8701cee1131eec8480) ly_associate_package(PACKAGE_NAME zlib-1.2.11-rev2-android TARGETS zlib PACKAGE_HASH 85b730b97176772538cfcacd6b6aaf4655fc2d368d134d6dd55e02f28f183826) From 61618e66d3f459f771e3351b38923b33809031f8 Mon Sep 17 00:00:00 2001 From: santorac <55155825+santorac@users.noreply.github.com> Date: Wed, 15 Sep 2021 12:41:08 -0700 Subject: [PATCH 11/51] Fixed a build dependency issue between libpng and zlib on android, thanks to suggestion from @lumberyard-employee-dm Signed-off-by: santorac <55155825+santorac@users.noreply.github.com> --- Gems/Atom/Feature/Common/Code/CMakeLists.txt | 7 ------- Gems/Atom/Utils/Code/CMakeLists.txt | 1 - .../Platform/Android/BuiltInPackages_android.cmake | 2 +- .../Platform/Windows/BuiltInPackages_windows.cmake | 2 +- 4 files changed, 2 insertions(+), 10 deletions(-) diff --git a/Gems/Atom/Feature/Common/Code/CMakeLists.txt b/Gems/Atom/Feature/Common/Code/CMakeLists.txt index 3a38aefe02..b8c414dcc6 100644 --- a/Gems/Atom/Feature/Common/Code/CMakeLists.txt +++ b/Gems/Atom/Feature/Common/Code/CMakeLists.txt @@ -83,13 +83,6 @@ ly_add_target( Include BUILD_DEPENDENCIES PRIVATE - # For some reason zlib and libpng need to be declared here, otherwise libAtom_Feature_Common.so will fail to link - # when building for Android. These libraries are appropriately indicated in Atom/Utils's CCmakeLists.txt and those dependencies are - # supposed to be inherited here through the PUBLIC dependencies chaining ... but something is amiss. libpng and zlib were both - # appearing in the link command so it isn't clear why it was failing. Perhaps it was because libpng appeared before zlib - # in the link command? Somehow repeating them here works around this issue. - 3rdParty::libpng - 3rdParty::zlib AZ::AzCore AZ::AzFramework Gem::Atom_Feature_Common.Static diff --git a/Gems/Atom/Utils/Code/CMakeLists.txt b/Gems/Atom/Utils/Code/CMakeLists.txt index 0781100e9a..89bc8dd0a5 100644 --- a/Gems/Atom/Utils/Code/CMakeLists.txt +++ b/Gems/Atom/Utils/Code/CMakeLists.txt @@ -24,7 +24,6 @@ ly_add_target( Gem::Atom_RHI.Public PUBLIC Gem::Atom_RHI.Reflect - 3rdParty::zlib 3rdParty::libpng ) diff --git a/cmake/3rdParty/Platform/Android/BuiltInPackages_android.cmake b/cmake/3rdParty/Platform/Android/BuiltInPackages_android.cmake index 333b9a5444..297707adf1 100644 --- a/cmake/3rdParty/Platform/Android/BuiltInPackages_android.cmake +++ b/cmake/3rdParty/Platform/Android/BuiltInPackages_android.cmake @@ -24,7 +24,7 @@ ly_associate_package(PACKAGE_NAME PhysX-4.1.2.29882248-rev3-android TARGETS Phy ly_associate_package(PACKAGE_NAME mikkelsen-1.0.0.4-android TARGETS mikkelsen PACKAGE_HASH 075e8e4940884971063b5a9963014e2e517246fa269c07c7dc55b8cf2cd99705) ly_associate_package(PACKAGE_NAME googletest-1.8.1-rev4-android TARGETS googletest PACKAGE_HASH 95671be75287a61c9533452835c3647e9c1b30f81b34b43bcb0ec1997cc23894) ly_associate_package(PACKAGE_NAME googlebenchmark-1.5.0-rev2-android TARGETS GoogleBenchmark PACKAGE_HASH 20b46e572211a69d7d94ddad1c89ec37bb958711d6ad4025368ac89ea83078fb) -ly_associate_package(PACKAGE_NAME libpng-1.6.37-android TARGETS libpng PACKAGE_HASH 72be3859de38559ed45e4383bc480ad9cc1c080fea2c4d6dd3946c2fb22ad5bb) +ly_associate_package(PACKAGE_NAME libpng-1.6.37-rev1-android TARGETS libpng PACKAGE_HASH 51d3ec1559c5595196c11e11674cf5745989d3073bf33dabc6697e3eee77a1cc) ly_associate_package(PACKAGE_NAME libsamplerate-0.2.1-rev2-android TARGETS libsamplerate PACKAGE_HASH bf13662afe65d02bcfa16258a4caa9b875534978227d6f9f36c9cfa92b3fb12b) ly_associate_package(PACKAGE_NAME OpenSSL-1.1.1b-rev1-android TARGETS OpenSSL PACKAGE_HASH 4036d4019d722f0e1b7a1621bf60b5a17ca6a65c9c78fd8701cee1131eec8480) ly_associate_package(PACKAGE_NAME zlib-1.2.11-rev2-android TARGETS zlib PACKAGE_HASH 85b730b97176772538cfcacd6b6aaf4655fc2d368d134d6dd55e02f28f183826) diff --git a/cmake/3rdParty/Platform/Windows/BuiltInPackages_windows.cmake b/cmake/3rdParty/Platform/Windows/BuiltInPackages_windows.cmake index ba5658df0e..a6fb3fbddc 100644 --- a/cmake/3rdParty/Platform/Windows/BuiltInPackages_windows.cmake +++ b/cmake/3rdParty/Platform/Windows/BuiltInPackages_windows.cmake @@ -42,7 +42,7 @@ ly_associate_package(PACKAGE_NAME d3dx12-headers-rev1-windows ly_associate_package(PACKAGE_NAME pyside2-qt-5.15.1-rev2-windows TARGETS pyside2 PACKAGE_HASH c90f3efcc7c10e79b22a33467855ad861f9dbd2e909df27a5cba9db9fa3edd0f) ly_associate_package(PACKAGE_NAME openimageio-2.1.16.0-rev2-windows TARGETS OpenImageIO PACKAGE_HASH 85a2a6cf35cbc4c967c56ca8074babf0955c5b490c90c6e6fd23c78db99fc282) ly_associate_package(PACKAGE_NAME qt-5.15.2-rev4-windows TARGETS Qt PACKAGE_HASH a4634caaf48192cad5c5f408504746e53d338856148285057274f6a0ccdc071d) -ly_associate_package(PACKAGE_NAME libpng-1.6.37-windows TARGETS libpng PACKAGE_HASH 011079ecbc09c22852eecd860c70dd89f8c2f923c09be87fec4e18ce1e55d4e7) +ly_associate_package(PACKAGE_NAME libpng-1.6.37-rev1-windows TARGETS libpng PACKAGE_HASH aa20c894fbd7cdaea585a54e37620b3454a7e414a58128acd68ccf6fe76c47d6) ly_associate_package(PACKAGE_NAME libsamplerate-0.2.1-rev2-windows TARGETS libsamplerate PACKAGE_HASH dcf3c11a96f212a52e2c9241abde5c364ee90b0f32fe6eeb6dcdca01d491829f) ly_associate_package(PACKAGE_NAME OpenMesh-8.1-rev1-windows TARGETS OpenMesh PACKAGE_HASH 1c1df639358526c368e790dfce40c45cbdfcfb1c9a041b9d7054a8949d88ee77) ly_associate_package(PACKAGE_NAME civetweb-1.8-rev1-windows TARGETS civetweb PACKAGE_HASH 36d0e58a59bcdb4dd70493fb1b177aa0354c945b06c30416348fd326cf323dd4) From 619f31572e5b70dce5eea6abf7dfed5c19d34699 Mon Sep 17 00:00:00 2001 From: srikappa-amzn Date: Wed, 15 Sep 2021 13:26:55 -0700 Subject: [PATCH 12/51] Enable destroying game entities spawned with a spawnable Signed-off-by: srikappa-amzn --- .../AzFramework/Entity/GameEntityContextBus.h | 18 ----- .../Entity/GameEntityContextComponent.cpp | 50 ++++---------- .../Entity/GameEntityContextComponent.h | 5 -- .../Spawnable/SpawnableEntitiesInterface.h | 8 +++ .../Spawnable/SpawnableEntitiesManager.cpp | 67 +++++++++++++++---- .../Spawnable/SpawnableEntitiesManager.h | 14 +++- .../Spawnable/SpawnableSystemComponent.h | 2 + .../Spawnable/SpawnedEntityTicketMapper.cpp | 43 ++++++++++++ .../Spawnable/SpawnedEntityTicketMapper.h | 37 ++++++++++ .../SpawnedEntityTicketMapperInterface.h | 24 +++++++ .../AzFramework/azframework_files.cmake | 3 + 11 files changed, 194 insertions(+), 77 deletions(-) create mode 100644 Code/Framework/AzFramework/AzFramework/Spawnable/SpawnedEntityTicketMapper.cpp create mode 100644 Code/Framework/AzFramework/AzFramework/Spawnable/SpawnedEntityTicketMapper.h create mode 100644 Code/Framework/AzFramework/AzFramework/Spawnable/SpawnedEntityTicketMapperInterface.h diff --git a/Code/Framework/AzFramework/AzFramework/Entity/GameEntityContextBus.h b/Code/Framework/AzFramework/AzFramework/Entity/GameEntityContextBus.h index b5ca37df6e..f8e4dfa98b 100644 --- a/Code/Framework/AzFramework/AzFramework/Entity/GameEntityContextBus.h +++ b/Code/Framework/AzFramework/AzFramework/Entity/GameEntityContextBus.h @@ -91,15 +91,6 @@ namespace AzFramework */ virtual void DestroyGameEntity(const AZ::EntityId& /*id*/) = 0; - /** - * Destroys an entity only in slice mode (when prefabs are disabled). This request is only added as a stop-gap solution - * to prevent the editor from crashing when prefabs are enabled and must only be called through the BehaviorContext binding - * for 'DestroyGameEntity'. No code should be written to directly call this method. This will be removed soon. - * - * @param id The ID of the entity to destroy. - */ - virtual void DestroyGameEntityOnlyInSliceMode(const AZ::EntityId& /*id*/) = 0; - /** * Destroys an entity and all of its descendants. * The entity and its descendants are immediately deactivated and will be @@ -108,15 +99,6 @@ namespace AzFramework */ virtual void DestroyGameEntityAndDescendants(const AZ::EntityId& /*id*/) = 0; - /** - * Destroys an entity and its descendants only in slice mode (when prefabs are disabled). This request is only added as a stop-gap - * solution to prevent the editor from crashing when prefabs are enabled and must only be called through the BehaviorContext - * binding for 'DestroyGameEntityAndDescendants'.No code should be written to directly call this method. This will be removed soon. - * - * @param id The ID of the entity to destroy. - */ - virtual void DestroyGameEntityAndDescendantsOnlyInSliceMode(const AZ::EntityId& /*id*/) = 0; - /** * Activates the game entity. * @param id The ID of the entity to activate. diff --git a/Code/Framework/AzFramework/AzFramework/Entity/GameEntityContextComponent.cpp b/Code/Framework/AzFramework/AzFramework/Entity/GameEntityContextComponent.cpp index a97281b9c1..903bad91bf 100644 --- a/Code/Framework/AzFramework/AzFramework/Entity/GameEntityContextComponent.cpp +++ b/Code/Framework/AzFramework/AzFramework/Entity/GameEntityContextComponent.cpp @@ -11,9 +11,10 @@ #include #include #include +#include #include #include -#include +#include #include "GameEntityContextComponent.h" @@ -47,9 +48,9 @@ namespace AzFramework ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common) ->Event("CreateGameEntity", &GameEntityContextRequestBus::Events::CreateGameEntityForBehaviorContext) ->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All) - ->Event("DestroyGameEntity", &GameEntityContextRequestBus::Events::DestroyGameEntityOnlyInSliceMode) + ->Event("DestroyGameEntity", &GameEntityContextRequestBus::Events::DestroyGameEntity) ->Event( - "DestroyGameEntityAndDescendants", &GameEntityContextRequestBus::Events::DestroyGameEntityAndDescendantsOnlyInSliceMode) + "DestroyGameEntityAndDescendants", &GameEntityContextRequestBus::Events::DestroyGameEntityAndDescendants) ->Event("ActivateGameEntity", &GameEntityContextRequestBus::Events::ActivateGameEntity) ->Event("DeactivateGameEntity", &GameEntityContextRequestBus::Events::DeactivateGameEntity) ->Attribute(AZ::ScriptCanvasAttributes::DeactivatesInputEntity, true) @@ -249,23 +250,6 @@ namespace AzFramework DestroyGameEntityInternal(id, false); } - void GameEntityContextComponent::DestroyGameEntityOnlyInSliceMode(const AZ::EntityId& id) - { - bool isPrefabSystemEnabled = false; - AzFramework::ApplicationRequests::Bus::BroadcastResult( - isPrefabSystemEnabled, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled); - if (!isPrefabSystemEnabled) - { - DestroyGameEntityInternal(id, false); - } - else - { - AZ_Error( - "GameEntityContextComponent", false, - "Destroying a game entity is temporarily disabled until the Spawnable system can support this."); - } - } - //========================================================================= // GameEntityContextComponent::DestroyGameEntityAndDescendantsById //========================================================================= @@ -274,24 +258,6 @@ namespace AzFramework DestroyGameEntityInternal(id, true); } - - void GameEntityContextComponent::DestroyGameEntityAndDescendantsOnlyInSliceMode(const AZ::EntityId& id) - { - bool isPrefabSystemEnabled = false; - AzFramework::ApplicationRequests::Bus::BroadcastResult( - isPrefabSystemEnabled, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled); - if (!isPrefabSystemEnabled) - { - DestroyGameEntityInternal(id, true); - } - else - { - AZ_Error( - "GameEntityContextComponent", false, - "Destroying a game entity and its descendants is temporarily disabled until the Spawnable system can support this."); - } - } - //========================================================================= // GameEntityContextComponent::DestroyGameEntityInternal //========================================================================= @@ -319,6 +285,14 @@ namespace AzFramework EBUS_EVENT_RESULT(currentEntity, AZ::ComponentApplicationBus, FindEntity, *entityIdIter); if (currentEntity) { + bool isPrefabSystemEnabled = false; + AzFramework::ApplicationRequests::Bus::BroadcastResult( + isPrefabSystemEnabled, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled); + if (isPrefabSystemEnabled) + { + AZ::Interface::Get()->RemoveSpawnedEntity(currentEntity->GetId()); + } + if (currentEntity->GetState() == AZ::Entity::State::Active) { // Deactivate the entity, we'll destroy it as soon as it is safe. diff --git a/Code/Framework/AzFramework/AzFramework/Entity/GameEntityContextComponent.h b/Code/Framework/AzFramework/AzFramework/Entity/GameEntityContextComponent.h index 831067d728..56a53f3c50 100644 --- a/Code/Framework/AzFramework/AzFramework/Entity/GameEntityContextComponent.h +++ b/Code/Framework/AzFramework/AzFramework/Entity/GameEntityContextComponent.h @@ -90,11 +90,6 @@ namespace AzFramework } private: - ////////////////////////////////////////////////////////////////////////// - // GameEntityContextRequestBus - void DestroyGameEntityOnlyInSliceMode(const AZ::EntityId&) override; - void DestroyGameEntityAndDescendantsOnlyInSliceMode(const AZ::EntityId&) override; - ///////////////////////////////////////////////////////////////////////// AzFramework::EntityVisibilityBoundsUnionSystem m_entityVisibilityBoundsUnionSystem; }; diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.h b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.h index 4b09dcbc75..2ce4158229 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.h +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.h @@ -235,6 +235,12 @@ namespace AzFramework SpawnablePriority m_priority{ SpawnablePriority_Default }; }; + struct ClaimEntityOptionalArgs final + { + //! The priority at which this call will be executed. + SpawnablePriority m_priority{ SpawnablePriority_Default }; + }; + struct BarrierOptionalArgs final { //! The priority at which this call will be executed. @@ -314,6 +320,8 @@ namespace AzFramework virtual void ClaimEntities( EntitySpawnTicket& ticket, ClaimEntitiesCallback listCallback, ClaimEntitiesOptionalArgs optionalArgs = {}) = 0; + virtual void ClaimEntity(AZ::EntityId entityId, void* ticket, ClaimEntityOptionalArgs optionalArgs = {}) = 0; + //! Blocks until all operations made on the provided ticket before the barrier call have completed. //! @param ticket The ticket to monitor. //! @param completionCallback Required callback that will be called as soon as the barrier has been reached. diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp index 5fa072a451..eae71b5ea0 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp @@ -17,17 +17,18 @@ #include #include #include +#include namespace AzFramework { template - void SpawnableEntitiesManager::QueueRequest(EntitySpawnTicket& ticket, SpawnablePriority priority, T&& request) + void SpawnableEntitiesManager::QueueRequest(Ticket* ticket, SpawnablePriority priority, T&& request) { - request.m_ticket = &GetTicketPayload(ticket); + request.m_ticket = ticket; Queue& queue = priority <= m_highPriorityThreshold ? m_highPriorityQueue : m_regularPriorityQueue; { AZStd::scoped_lock queueLock(queue.m_pendingRequestMutex); - request.m_requestId = GetTicketPayload(ticket).m_nextRequestId++; + request.m_requestId = ticket->m_nextRequestId++; queue.m_pendingRequest.push(AZStd::move(request)); } } @@ -56,7 +57,7 @@ namespace AzFramework optionalArgs.m_serializeContext == nullptr ? m_defaultSerializeContext : optionalArgs.m_serializeContext; queueEntry.m_completionCallback = AZStd::move(optionalArgs.m_completionCallback); queueEntry.m_preInsertionCallback = AZStd::move(optionalArgs.m_preInsertionCallback); - QueueRequest(ticket, optionalArgs.m_priority, AZStd::move(queueEntry)); + QueueRequest(&GetTicketPayload(ticket), optionalArgs.m_priority, AZStd::move(queueEntry)); } void SpawnableEntitiesManager::SpawnEntities( @@ -72,7 +73,7 @@ namespace AzFramework queueEntry.m_completionCallback = AZStd::move(optionalArgs.m_completionCallback); queueEntry.m_preInsertionCallback = AZStd::move(optionalArgs.m_preInsertionCallback); queueEntry.m_referencePreviouslySpawnedEntities = optionalArgs.m_referencePreviouslySpawnedEntities; - QueueRequest(ticket, optionalArgs.m_priority, AZStd::move(queueEntry)); + QueueRequest(&GetTicketPayload(ticket), optionalArgs.m_priority, AZStd::move(queueEntry)); } void SpawnableEntitiesManager::DespawnAllEntities(EntitySpawnTicket& ticket, DespawnAllEntitiesOptionalArgs optionalArgs) @@ -82,7 +83,7 @@ namespace AzFramework DespawnAllEntitiesCommand queueEntry; queueEntry.m_ticketId = ticket.GetId(); queueEntry.m_completionCallback = AZStd::move(optionalArgs.m_completionCallback); - QueueRequest(ticket, optionalArgs.m_priority, AZStd::move(queueEntry)); + QueueRequest(&GetTicketPayload(ticket), optionalArgs.m_priority, AZStd::move(queueEntry)); } void SpawnableEntitiesManager::ReloadSpawnable( @@ -96,7 +97,7 @@ namespace AzFramework queueEntry.m_serializeContext = optionalArgs.m_serializeContext == nullptr ? m_defaultSerializeContext : optionalArgs.m_serializeContext; queueEntry.m_completionCallback = AZStd::move(optionalArgs.m_completionCallback); - QueueRequest(ticket, optionalArgs.m_priority, AZStd::move(queueEntry)); + QueueRequest(&GetTicketPayload(ticket), optionalArgs.m_priority, AZStd::move(queueEntry)); } void SpawnableEntitiesManager::ListEntities( @@ -108,7 +109,7 @@ namespace AzFramework ListEntitiesCommand queueEntry; queueEntry.m_ticketId = ticket.GetId(); queueEntry.m_listCallback = AZStd::move(listCallback); - QueueRequest(ticket, optionalArgs.m_priority, AZStd::move(queueEntry)); + QueueRequest(&GetTicketPayload(ticket), optionalArgs.m_priority, AZStd::move(queueEntry)); } void SpawnableEntitiesManager::ListIndicesAndEntities( @@ -120,7 +121,7 @@ namespace AzFramework ListIndicesEntitiesCommand queueEntry; queueEntry.m_ticketId = ticket.GetId(); queueEntry.m_listCallback = AZStd::move(listCallback); - QueueRequest(ticket, optionalArgs.m_priority, AZStd::move(queueEntry)); + QueueRequest(&GetTicketPayload(ticket), optionalArgs.m_priority, AZStd::move(queueEntry)); } void SpawnableEntitiesManager::ClaimEntities( @@ -132,7 +133,18 @@ namespace AzFramework ClaimEntitiesCommand queueEntry; queueEntry.m_ticketId = ticket.GetId(); queueEntry.m_listCallback = AZStd::move(listCallback); - QueueRequest(ticket, optionalArgs.m_priority, AZStd::move(queueEntry)); + QueueRequest(&GetTicketPayload(ticket), optionalArgs.m_priority, AZStd::move(queueEntry)); + } + + void SpawnableEntitiesManager::ClaimEntity( + AZ::EntityId entityId, void* ticket, + ClaimEntityOptionalArgs optionalArgs) + { + AZ_Assert(ticket == nullptr, "Ticket provided to ClaimEntity is invalid."); + + ClaimEntityCommand queueEntry; + queueEntry.m_entityId = entityId; + QueueRequest(reinterpret_cast(ticket), optionalArgs.m_priority, AZStd::move(queueEntry)); } void SpawnableEntitiesManager::Barrier(EntitySpawnTicket& ticket, BarrierCallback completionCallback, BarrierOptionalArgs optionalArgs) @@ -143,7 +155,7 @@ namespace AzFramework BarrierCommand queueEntry; queueEntry.m_ticketId = ticket.GetId(); queueEntry.m_completionCallback = AZStd::move(completionCallback); - QueueRequest(ticket, optionalArgs.m_priority, AZStd::move(queueEntry)); + QueueRequest(&GetTicketPayload(ticket), optionalArgs.m_priority, AZStd::move(queueEntry)); } auto SpawnableEntitiesManager::ProcessQueue(CommandQueuePriority priority) -> CommandQueueStatus @@ -340,6 +352,7 @@ namespace AzFramework for (auto it = ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount; it != ticket.m_spawnedEntities.end(); ++it) { GameEntityContextRequestBus::Broadcast(&GameEntityContextRequestBus::Events::AddGameEntity, *it); + AZ::Interface::Get()->AddSpawnedEntity((*it)->GetId(), &ticket); } // Let other systems know about newly spawned entities for any post-processing after adding to the scene/game context. @@ -421,6 +434,7 @@ namespace AzFramework for (auto it = ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount; it != ticket.m_spawnedEntities.end(); ++it) { GameEntityContextRequestBus::Broadcast(&GameEntityContextRequestBus::Events::AddGameEntity, *it); + AZ::Interface::Get()->AddSpawnedEntity((*it)->GetId(), &ticket); } if (request.m_completionCallback) @@ -448,7 +462,7 @@ namespace AzFramework if (entity != nullptr) { GameEntityContextRequestBus::Broadcast( - &GameEntityContextRequestBus::Events::DestroyGameEntityAndDescendants, entity->GetId()); + &GameEntityContextRequestBus::Events::DestroyGameEntity, entity->GetId()); } } @@ -483,7 +497,7 @@ namespace AzFramework if (entity != nullptr) { GameEntityContextRequestBus::Broadcast( - &GameEntityContextRequestBus::Events::DestroyGameEntityAndDescendants, entity->GetId()); + &GameEntityContextRequestBus::Events::DestroyGameEntity, entity->GetId()); } } @@ -609,6 +623,31 @@ namespace AzFramework } } + bool SpawnableEntitiesManager::ProcessRequest(ClaimEntityCommand& request) + { + Ticket& ticket = *request.m_ticket; + if (request.m_requestId == ticket.m_currentRequestId) + { + AZStd::vector& spawnedEntities = ticket.m_spawnedEntities; + AZStd::vector::size_type entityIndex = 0; + for (entityIndex = 0; entityIndex < spawnedEntities.size(); entityIndex++) + { + if (spawnedEntities[entityIndex]->GetId() == request.m_entityId) + { + spawnedEntities.erase(spawnedEntities.begin() + entityIndex); + ticket.m_spawnedEntityIndices.erase(ticket.m_spawnedEntityIndices.begin() + entityIndex); + ticket.m_currentRequestId++; + return true; + } + } + return false; + } + else + { + return false; + } + } + bool SpawnableEntitiesManager::ProcessRequest(BarrierCommand& request) { Ticket& ticket = *request.m_ticket; @@ -637,7 +676,7 @@ namespace AzFramework if (entity != nullptr) { GameEntityContextRequestBus::Broadcast( - &GameEntityContextRequestBus::Events::DestroyGameEntityAndDescendants, entity->GetId()); + &GameEntityContextRequestBus::Events::DestroyGameEntity, entity->GetId()); } } delete request.m_ticket; diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h index 69985f8aa9..89181ea179 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h @@ -66,6 +66,8 @@ namespace AzFramework EntitySpawnTicket& ticket, ListIndicesEntitiesCallback listCallback, ListEntitiesOptionalArgs optionalArgs = {}) override; void ClaimEntities( EntitySpawnTicket& ticket, ClaimEntitiesCallback listCallback, ClaimEntitiesOptionalArgs optionalArgs = {}) override; + void ClaimEntity( + AZ::EntityId entityId, void* ticket, ClaimEntityOptionalArgs optionalArgs = {}) override; void Barrier(EntitySpawnTicket& spawnInfo, BarrierCallback completionCallback, BarrierOptionalArgs optionalArgs = {}) override; @@ -162,6 +164,13 @@ namespace AzFramework EntitySpawnTicket::Id m_ticketId; uint32_t m_requestId; }; + struct ClaimEntityCommand + { + Ticket* m_ticket; + EntitySpawnTicket::Id m_ticketId; + uint32_t m_requestId; + AZ::EntityId m_entityId; + }; struct BarrierCommand { BarrierCallback m_completionCallback; @@ -177,7 +186,7 @@ namespace AzFramework using Requests = AZStd::variant< SpawnAllEntitiesCommand, SpawnEntitiesCommand, DespawnAllEntitiesCommand, ReloadSpawnableCommand, ListEntitiesCommand, - ListIndicesEntitiesCommand, ClaimEntitiesCommand, BarrierCommand, DestroyTicketCommand>; + ListIndicesEntitiesCommand, ClaimEntitiesCommand, ClaimEntityCommand, BarrierCommand, DestroyTicketCommand>; struct Queue { @@ -187,7 +196,7 @@ namespace AzFramework }; template - void QueueRequest(EntitySpawnTicket& ticket, SpawnablePriority priority, T&& request); + void QueueRequest(Ticket* ticket, SpawnablePriority priority, T&& request); AZStd::pair CreateTicket(AZ::Data::Asset&& spawnable) override; void DestroyTicket(void* ticket) override; @@ -203,6 +212,7 @@ namespace AzFramework bool ProcessRequest(ListEntitiesCommand& request); bool ProcessRequest(ListIndicesEntitiesCommand& request); bool ProcessRequest(ClaimEntitiesCommand& request); + bool ProcessRequest(ClaimEntityCommand& request); bool ProcessRequest(BarrierCommand& request); bool ProcessRequest(DestroyTicketCommand& request); diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableSystemComponent.h b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableSystemComponent.h index 5b5fb1b7ee..33e0a29704 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableSystemComponent.h +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableSystemComponent.h @@ -18,6 +18,7 @@ #include #include #include +#include namespace AzFramework { @@ -90,6 +91,7 @@ namespace AzFramework void LoadRootSpawnableFromSettingsRegistry(); SpawnableAssetHandler m_assetHandler; + SpawnedEntityTicketMapper m_spawnedEntityTicketMapper; SpawnableEntitiesManager m_entitiesManager; SpawnableEntitiesContainer m_rootSpawnableContainer; AZ::SettingsRegistryInterface::NotifyEventHandler m_registryChangeHandler; diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnedEntityTicketMapper.cpp b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnedEntityTicketMapper.cpp new file mode 100644 index 0000000000..4179f3c111 --- /dev/null +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnedEntityTicketMapper.cpp @@ -0,0 +1,43 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include + +namespace AzFramework +{ + SpawnedEntityTicketMapper::SpawnedEntityTicketMapper() + { + AZ::Interface::Register(this); + } + + SpawnedEntityTicketMapper::~SpawnedEntityTicketMapper() + { + AZ::Interface::Unregister(this); + } + + void SpawnedEntityTicketMapper::RemoveSpawnedEntity(AZ::EntityId entityId) + { + auto spawnedGameEntitiesIterator = m_spawnedEntities.find(entityId); + if (spawnedGameEntitiesIterator != m_spawnedEntities.end()) + { + SpawnableEntitiesDefinition* spawnableEntitiesInterface = SpawnableEntitiesInterface::Get(); + AZ_Assert(spawnableEntitiesInterface == nullptr, "SpawnableEntitiesInterface is not found."); + spawnableEntitiesInterface->ClaimEntity( + spawnedGameEntitiesIterator->first, spawnedGameEntitiesIterator->second); + m_spawnedEntities.erase(entityId); + } + } + + void SpawnedEntityTicketMapper::AddSpawnedEntity(AZ::EntityId entityId, void* ticket) + { + m_spawnedEntities.emplace(entityId, ticket); + } + +} + diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnedEntityTicketMapper.h b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnedEntityTicketMapper.h new file mode 100644 index 0000000000..36582a05b2 --- /dev/null +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnedEntityTicketMapper.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include +#include +#include + +namespace AzFramework +{ + class SpawnedEntityTicketMapper + : public SpawnedEntityTicketMapperInterface + { + public: + AZ_RTTI(SpawnedEntityTicketMapper, "{5C803604-E949-44F4-94A4-F835E5794C77}", SpawnedEntityTicketMapperInterface); + + SpawnedEntityTicketMapper(); + ~SpawnedEntityTicketMapper(); + + //! Removes the entityId from the spawned entities map. + //! @param entityId The id of the entity to remove. + void RemoveSpawnedEntity(AZ::EntityId entityId) override; + + //! Adds the entityId,ticket pair to the spawned entities map. + //! @param entityId The id of the entity to add. + //! @param ticket The ticket pointer to add. + void AddSpawnedEntity(AZ::EntityId entityId, void* ticket) override; + private: + AZStd::unordered_map m_spawnedEntities; + }; +} // namespace AzFramework diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnedEntityTicketMapperInterface.h b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnedEntityTicketMapperInterface.h new file mode 100644 index 0000000000..8f0bd96dbe --- /dev/null +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnedEntityTicketMapperInterface.h @@ -0,0 +1,24 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include +#include + +namespace AzFramework +{ + class SpawnedEntityTicketMapperInterface + { + public: + AZ_RTTI(SpawnedEntityTicketMapperInterface, "{D407E96B-635C-44F2-B089-084EBAB8B036}"); + + virtual void RemoveSpawnedEntity(AZ::EntityId entityId) = 0; + virtual void AddSpawnedEntity(AZ::EntityId entityId, void* ticket) = 0; + }; +} diff --git a/Code/Framework/AzFramework/AzFramework/azframework_files.cmake b/Code/Framework/AzFramework/AzFramework/azframework_files.cmake index 87cf29ffec..b486596e2a 100644 --- a/Code/Framework/AzFramework/AzFramework/azframework_files.cmake +++ b/Code/Framework/AzFramework/AzFramework/azframework_files.cmake @@ -294,6 +294,9 @@ set(FILES Spawnable/SpawnableMonitor.cpp Spawnable/SpawnableSystemComponent.h Spawnable/SpawnableSystemComponent.cpp + Spawnable/SpawnedEntityTicketMapper.h + Spawnable/SpawnedEntityTicketMapper.cpp + Spawnable/SpawnedEntityTicketMapperInterface.h Terrain/TerrainDataRequestBus.h Terrain/TerrainDataRequestBus.cpp Thermal/ThermalInfo.h From 2cbd72e80132c5b0c8efce567a3317592ef0138c Mon Sep 17 00:00:00 2001 From: srikappa-amzn Date: Wed, 15 Sep 2021 14:57:54 -0700 Subject: [PATCH 13/51] Improved comments and minor changes to accessing interfaces Signed-off-by: srikappa-amzn --- .../Entity/GameEntityContextComponent.cpp | 5 ++++- .../Spawnable/SpawnableEntitiesManager.cpp | 15 +++++++-------- .../Spawnable/SpawnableEntitiesManager.h | 4 +++- .../Spawnable/SpawnableSystemComponent.h | 2 -- .../Spawnable/SpawnedEntityTicketMapper.cpp | 5 +++-- .../Spawnable/SpawnedEntityTicketMapper.h | 3 ++- 6 files changed, 19 insertions(+), 15 deletions(-) diff --git a/Code/Framework/AzFramework/AzFramework/Entity/GameEntityContextComponent.cpp b/Code/Framework/AzFramework/AzFramework/Entity/GameEntityContextComponent.cpp index 903bad91bf..694ed83765 100644 --- a/Code/Framework/AzFramework/AzFramework/Entity/GameEntityContextComponent.cpp +++ b/Code/Framework/AzFramework/AzFramework/Entity/GameEntityContextComponent.cpp @@ -290,7 +290,10 @@ namespace AzFramework isPrefabSystemEnabled, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled); if (isPrefabSystemEnabled) { - AZ::Interface::Get()->RemoveSpawnedEntity(currentEntity->GetId()); + SpawnedEntityTicketMapperInterface* spawnedEntityTicketMapperInterface = + AZ::Interface::Get(); + AZ_Assert(spawnedEntityTicketMapperInterface != nullptr, "SpawnedEntityTicketMapperInterface is not found."); + spawnedEntityTicketMapperInterface->RemoveSpawnedEntity(currentEntity->GetId()); } if (currentEntity->GetState() == AZ::Entity::State::Active) diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp index eae71b5ea0..2c317bc33f 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp @@ -17,7 +17,6 @@ #include #include #include -#include namespace AzFramework { @@ -352,7 +351,7 @@ namespace AzFramework for (auto it = ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount; it != ticket.m_spawnedEntities.end(); ++it) { GameEntityContextRequestBus::Broadcast(&GameEntityContextRequestBus::Events::AddGameEntity, *it); - AZ::Interface::Get()->AddSpawnedEntity((*it)->GetId(), &ticket); + m_spawnedEntityTicketMapper.AddSpawnedEntity((*it)->GetId(), &ticket); } // Let other systems know about newly spawned entities for any post-processing after adding to the scene/game context. @@ -434,7 +433,7 @@ namespace AzFramework for (auto it = ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount; it != ticket.m_spawnedEntities.end(); ++it) { GameEntityContextRequestBus::Broadcast(&GameEntityContextRequestBus::Events::AddGameEntity, *it); - AZ::Interface::Get()->AddSpawnedEntity((*it)->GetId(), &ticket); + m_spawnedEntityTicketMapper.AddSpawnedEntity((*it)->GetId(), &ticket); } if (request.m_completionCallback) @@ -625,18 +624,18 @@ namespace AzFramework bool SpawnableEntitiesManager::ProcessRequest(ClaimEntityCommand& request) { - Ticket& ticket = *request.m_ticket; - if (request.m_requestId == ticket.m_currentRequestId) + Ticket* ticket = request.m_ticket; + if (request.m_requestId == ticket->m_currentRequestId) { - AZStd::vector& spawnedEntities = ticket.m_spawnedEntities; + AZStd::vector& spawnedEntities = ticket->m_spawnedEntities; AZStd::vector::size_type entityIndex = 0; for (entityIndex = 0; entityIndex < spawnedEntities.size(); entityIndex++) { if (spawnedEntities[entityIndex]->GetId() == request.m_entityId) { spawnedEntities.erase(spawnedEntities.begin() + entityIndex); - ticket.m_spawnedEntityIndices.erase(ticket.m_spawnedEntityIndices.begin() + entityIndex); - ticket.m_currentRequestId++; + ticket->m_spawnedEntityIndices.erase(ticket->m_spawnedEntityIndices.begin() + entityIndex); + ticket->m_currentRequestId++; return true; } } diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h index 89181ea179..e5a0760010 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h @@ -16,6 +16,7 @@ #include #include #include +#include namespace AZ { @@ -167,7 +168,6 @@ namespace AzFramework struct ClaimEntityCommand { Ticket* m_ticket; - EntitySpawnTicket::Id m_ticketId; uint32_t m_requestId; AZ::EntityId m_entityId; }; @@ -234,6 +234,8 @@ namespace AzFramework //! SpawnablePriority_Default which gives users a bit of room to fine tune the priorities as this value can be configured //! through the Settings Registry under the key "/O3DE/AzFramework/Spawnables/HighPriorityThreshold". SpawnablePriority m_highPriorityThreshold { 64 }; + private: + SpawnedEntityTicketMapper m_spawnedEntityTicketMapper; }; AZ_DEFINE_ENUM_BITWISE_OPERATORS(AzFramework::SpawnableEntitiesManager::CommandQueuePriority); diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableSystemComponent.h b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableSystemComponent.h index 33e0a29704..5b5fb1b7ee 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableSystemComponent.h +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableSystemComponent.h @@ -18,7 +18,6 @@ #include #include #include -#include namespace AzFramework { @@ -91,7 +90,6 @@ namespace AzFramework void LoadRootSpawnableFromSettingsRegistry(); SpawnableAssetHandler m_assetHandler; - SpawnedEntityTicketMapper m_spawnedEntityTicketMapper; SpawnableEntitiesManager m_entitiesManager; SpawnableEntitiesContainer m_rootSpawnableContainer; AZ::SettingsRegistryInterface::NotifyEventHandler m_registryChangeHandler; diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnedEntityTicketMapper.cpp b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnedEntityTicketMapper.cpp index 4179f3c111..3a6b40f5a9 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnedEntityTicketMapper.cpp +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnedEntityTicketMapper.cpp @@ -13,6 +13,9 @@ namespace AzFramework { SpawnedEntityTicketMapper::SpawnedEntityTicketMapper() { + spawnableEntitiesInterface = SpawnableEntitiesInterface::Get(); + AZ_Assert(spawnableEntitiesInterface != nullptr, "SpawnableEntitiesInterface is not found."); + AZ::Interface::Register(this); } @@ -26,8 +29,6 @@ namespace AzFramework auto spawnedGameEntitiesIterator = m_spawnedEntities.find(entityId); if (spawnedGameEntitiesIterator != m_spawnedEntities.end()) { - SpawnableEntitiesDefinition* spawnableEntitiesInterface = SpawnableEntitiesInterface::Get(); - AZ_Assert(spawnableEntitiesInterface == nullptr, "SpawnableEntitiesInterface is not found."); spawnableEntitiesInterface->ClaimEntity( spawnedGameEntitiesIterator->first, spawnedGameEntitiesIterator->second); m_spawnedEntities.erase(entityId); diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnedEntityTicketMapper.h b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnedEntityTicketMapper.h index 36582a05b2..45e701db4e 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnedEntityTicketMapper.h +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnedEntityTicketMapper.h @@ -23,7 +23,7 @@ namespace AzFramework SpawnedEntityTicketMapper(); ~SpawnedEntityTicketMapper(); - //! Removes the entityId from the spawned entities map. + //! Removes the entityId from the spawned entities map if present. //! @param entityId The id of the entity to remove. void RemoveSpawnedEntity(AZ::EntityId entityId) override; @@ -32,6 +32,7 @@ namespace AzFramework //! @param ticket The ticket pointer to add. void AddSpawnedEntity(AZ::EntityId entityId, void* ticket) override; private: + SpawnableEntitiesDefinition* spawnableEntitiesInterface = nullptr; AZStd::unordered_map m_spawnedEntities; }; } // namespace AzFramework From f95986f4432dd40a0473f8dfeb3d34f94538a8a4 Mon Sep 17 00:00:00 2001 From: santorac <55155825+santorac@users.noreply.github.com> Date: Wed, 15 Sep 2021 23:33:48 -0700 Subject: [PATCH 14/51] Updated linux build to use new libpng package. Signed-off-by: santorac <55155825+santorac@users.noreply.github.com> --- cmake/3rdParty/Platform/Linux/BuiltInPackages_linux.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/3rdParty/Platform/Linux/BuiltInPackages_linux.cmake b/cmake/3rdParty/Platform/Linux/BuiltInPackages_linux.cmake index d1bfcfb031..92030a39e0 100644 --- a/cmake/3rdParty/Platform/Linux/BuiltInPackages_linux.cmake +++ b/cmake/3rdParty/Platform/Linux/BuiltInPackages_linux.cmake @@ -37,7 +37,7 @@ ly_associate_package(PACKAGE_NAME googletest-1.8.1-rev4-linux ly_associate_package(PACKAGE_NAME googlebenchmark-1.5.0-rev2-linux TARGETS GoogleBenchmark PACKAGE_HASH 4038878f337fc7e0274f0230f71851b385b2e0327c495fc3dd3d1c18a807928d) ly_associate_package(PACKAGE_NAME unwind-1.2.1-linux TARGETS unwind PACKAGE_HASH 3453265fb056e25432f611a61546a25f60388e315515ad39007b5925dd054a77) ly_associate_package(PACKAGE_NAME qt-5.15.2-rev5-linux TARGETS Qt PACKAGE_HASH 76b395897b941a173002845c7219a5f8a799e44b269ffefe8091acc048130f28) -ly_associate_package(PACKAGE_NAME libpng-1.6.37-linux TARGETS libpng PACKAGE_HASH 0000000000000000000000000000000000000000000000000000000000000000) +ly_associate_package(PACKAGE_NAME libpng-1.6.37-rev1-linux TARGETS libpng PACKAGE_HASH 896451999f1de76375599aec4b34ae0573d8d34620d9ab29cc30b8739c265ba6) ly_associate_package(PACKAGE_NAME libsamplerate-0.2.1-rev2-linux TARGETS libsamplerate PACKAGE_HASH 41643c31bc6b7d037f895f89d8d8d6369e906b92eff42b0fe05ee6a100f06261) ly_associate_package(PACKAGE_NAME OpenSSL-1.1.1b-rev2-linux TARGETS OpenSSL PACKAGE_HASH b779426d1e9c5ddf71160d5ae2e639c3b956e0fb5e9fcaf9ce97c4526024e3bc) ly_associate_package(PACKAGE_NAME DirectXShaderCompilerDxc-1.6.2104-o3de-rev3-linux TARGETS DirectXShaderCompilerDxc PACKAGE_HASH 88c4a359325d749bc34090b9ac466424847f3b71ba0de15045cf355c17c07099) From a4af34423a4b0e85ac4a6fc80408e9e06f120ba2 Mon Sep 17 00:00:00 2001 From: srikappa-amzn Date: Thu, 16 Sep 2021 00:03:00 -0700 Subject: [PATCH 15/51] Use iter_swap instead of erase Signed-off-by: srikappa-amzn --- .../Spawnable/SpawnableEntitiesManager.cpp | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp index 2c317bc33f..92d461819b 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp @@ -628,18 +628,16 @@ namespace AzFramework if (request.m_requestId == ticket->m_currentRequestId) { AZStd::vector& spawnedEntities = ticket->m_spawnedEntities; - AZStd::vector::size_type entityIndex = 0; - for (entityIndex = 0; entityIndex < spawnedEntities.size(); entityIndex++) + for (auto entityIterator = spawnedEntities.begin(); entityIterator != spawnedEntities.end(); ++entityIterator) { - if (spawnedEntities[entityIndex]->GetId() == request.m_entityId) + if ((*entityIterator)->GetId() == request.m_entityId) { - spawnedEntities.erase(spawnedEntities.begin() + entityIndex); - ticket->m_spawnedEntityIndices.erase(ticket->m_spawnedEntityIndices.begin() + entityIndex); - ticket->m_currentRequestId++; + AZStd::iter_swap(entityIterator, spawnedEntities.rbegin()); + spawnedEntities.pop_back(); return true; } } - return false; + return true; } else { From d11dbd37320c6121385ceee45e4fd5b7e60efd77 Mon Sep 17 00:00:00 2001 From: scspaldi Date: Tue, 21 Sep 2021 11:50:04 -0700 Subject: [PATCH 16/51] Updated Linux and Mac configs to show test results. Signed-off-by: scspaldi --- scripts/build/Platform/Linux/build_config.json | 12 ++++++++---- scripts/build/Platform/Mac/build_config.json | 6 ++++-- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/scripts/build/Platform/Linux/build_config.json b/scripts/build/Platform/Linux/build_config.json index e70fa32878..ee4c27b0a9 100644 --- a/scripts/build/Platform/Linux/build_config.json +++ b/scripts/build/Platform/Linux/build_config.json @@ -83,7 +83,8 @@ "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_UNITY_BUILD=TRUE -DLY_PARALLEL_LINK_JOBS=4", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "all", - "CTEST_OPTIONS": "-E Gem::EMotionFX.Editor.Tests -LE (SUITE_sandbox|SUITE_awsi) -L FRAMEWORK_googletest" + "CTEST_OPTIONS": "-E Gem::EMotionFX.Editor.Tests -LE (SUITE_sandbox|SUITE_awsi) -L FRAMEWORK_googletest", + "TEST_RESULTS": "True" } }, "test_profile_nounity": { @@ -95,7 +96,8 @@ "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_UNITY_BUILD=FALSE -DLY_PARALLEL_LINK_JOBS=4", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "all", - "CTEST_OPTIONS": "-E Gem::EMotionFX.Editor.Tests -LE (SUITE_sandbox|SUITE_awsi) -L FRAMEWORK_googletest" + "CTEST_OPTIONS": "-E Gem::EMotionFX.Editor.Tests -LE (SUITE_sandbox|SUITE_awsi) -L FRAMEWORK_googletest", + "TEST_RESULTS": "True" } }, "asset_profile": { @@ -143,7 +145,8 @@ "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_UNITY_BUILD=TRUE -DLY_PARALLEL_LINK_JOBS=4", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "TEST_SUITE_periodic", - "CTEST_OPTIONS": "-L (SUITE_periodic)" + "CTEST_OPTIONS": "-L (SUITE_periodic)", + "TEST_RESULTS": "True" } }, "sandbox_test_profile": { @@ -178,7 +181,8 @@ "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_UNITY_BUILD=TRUE -DLY_PARALLEL_LINK_JOBS=4", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "TEST_SUITE_benchmark", - "CTEST_OPTIONS": "-L (SUITE_benchmark)" + "CTEST_OPTIONS": "-L (SUITE_benchmark)", + "TEST_RESULTS": "True" } }, "release": { diff --git a/scripts/build/Platform/Mac/build_config.json b/scripts/build/Platform/Mac/build_config.json index 2cf445a836..7eb3cb5699 100644 --- a/scripts/build/Platform/Mac/build_config.json +++ b/scripts/build/Platform/Mac/build_config.json @@ -102,7 +102,8 @@ "CMAKE_OPTIONS": "-G Xcode -DLY_UNITY_BUILD=TRUE", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "TEST_SUITE_periodic", - "CTEST_OPTIONS": "-L \"(SUITE_periodic)\"" + "CTEST_OPTIONS": "-L \"(SUITE_periodic)\"", + "TEST_RESULTS": "True" } }, "benchmark_test_profile": { @@ -118,7 +119,8 @@ "CMAKE_OPTIONS": "-G Xcode -DLY_UNITY_BUILD=TRUE", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "TEST_SUITE_benchmark", - "CTEST_OPTIONS": "-L \"(SUITE_benchmark)\"" + "CTEST_OPTIONS": "-L \"(SUITE_benchmark)\"", + "TEST_RESULTS": "True" } }, "release": { From 368c325613ad4cabf5b335f34937392c7b773dfa Mon Sep 17 00:00:00 2001 From: Danilo Aimini <82231674+AMZN-daimini@users.noreply.github.com> Date: Mon, 20 Sep 2021 17:07:39 -0700 Subject: [PATCH 17/51] Disable selection of "out of focus" entities in both Outliner and Viewport. Refactor hover/selection rect painting for branches in the Outliner to fix disabled hover state. Signed-off-by: Danilo Aimini <82231674+AMZN-daimini@users.noreply.github.com> --- .../UI/Outliner/EntityOutliner.qss | 4 +- .../UI/Outliner/EntityOutlinerListModel.cpp | 27 ++++++++---- .../UI/Outliner/EntityOutlinerListModel.hxx | 8 +++- .../UI/Outliner/EntityOutlinerTreeView.cpp | 43 +++++++++++++++++++ .../UI/Outliner/EntityOutlinerTreeView.hxx | 3 ++ .../ViewportSelection/EditorHelpers.cpp | 18 ++++++++ .../ViewportSelection/EditorHelpers.h | 11 +++-- 7 files changed, 99 insertions(+), 15 deletions(-) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutliner.qss b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutliner.qss index 15ff8b1b67..26e9506c9d 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutliner.qss +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutliner.qss @@ -21,7 +21,7 @@ AzToolsFramework--EntityOutlinerWidget QTreeView AzToolsFramework--EntityOutlinerWidget QTreeView::branch:hover , AzToolsFramework--EntityOutlinerWidget QTreeView::item:hover { - background: rgba(255, 255, 255, 30); + background: transparent; } AzToolsFramework--EntityOutlinerWidget QTreeView::branch:selected @@ -29,7 +29,7 @@ AzToolsFramework--EntityOutlinerWidget QTreeView::branch:selected , AzToolsFramework--EntityOutlinerWidget QTreeView::branch:selected:active , AzToolsFramework--EntityOutlinerWidget QTreeView::item:selected:active { - background: rgba(255, 255, 255, 45); + background: transparent; } diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerListModel.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerListModel.cpp index d9f3ff8bb8..f9521cb77f 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerListModel.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerListModel.cpp @@ -44,6 +44,7 @@ #include #include #include +#include #include #include #include @@ -102,10 +103,14 @@ namespace AzToolsFramework EntityCompositionNotificationBus::Handler::BusConnect(); AZ::EntitySystemBus::Handler::BusConnect(); - m_editorEntityFrameworkInterface = AZ::Interface::Get(); + m_editorEntityUiInterface = AZ::Interface::Get(); + AZ_Assert(m_editorEntityUiInterface != nullptr, + "EntityOutlinerListModel requires a EditorEntityUiInterface instance on Initialize."); - AZ_Assert(m_editorEntityFrameworkInterface != nullptr, - "EntityOutlinerListModel requires a EditorEntityFrameworkInterface instance on Initialize."); + m_focusModeInterface = AZ::Interface::Get(); + AZ_Assert( + m_focusModeInterface != nullptr, + "EntityOutlinerListModel requires a FocusModeInterface instance on Initialize."); } int EntityOutlinerListModel::rowCount(const QModelIndex& parent) const @@ -279,7 +284,7 @@ namespace AzToolsFramework QVariant EntityOutlinerListModel::GetEntityIcon(const AZ::EntityId& id) const { - auto entityUiHandler = m_editorEntityFrameworkInterface->GetHandler(id); + auto entityUiHandler = m_editorEntityUiInterface->GetHandler(id); QIcon icon; // Retrieve the icon from the handler @@ -316,7 +321,7 @@ namespace AzToolsFramework QVariant EntityOutlinerListModel::GetEntityTooltip(const AZ::EntityId& id) const { - auto entityUiHandler = m_editorEntityFrameworkInterface->GetHandler(id); + auto entityUiHandler = m_editorEntityUiInterface->GetHandler(id); QString tooltip; // Retrieve the tooltip from the handler @@ -349,7 +354,7 @@ namespace AzToolsFramework QVariant EntityOutlinerListModel::dataForVisibility(const QModelIndex& index, int role) const { auto entityId = GetEntityFromIndex(index); - auto entityUiHandler = m_editorEntityFrameworkInterface->GetHandler(entityId); + auto entityUiHandler = m_editorEntityUiInterface->GetHandler(entityId); if (!entityUiHandler || entityUiHandler->CanToggleLockVisibility(entityId)) { @@ -377,7 +382,7 @@ namespace AzToolsFramework QVariant EntityOutlinerListModel::dataForLock(const QModelIndex& index, int role) const { auto entityId = GetEntityFromIndex(index); - auto entityUiHandler = m_editorEntityFrameworkInterface->GetHandler(entityId); + auto entityUiHandler = m_editorEntityUiInterface->GetHandler(entityId); if (!entityUiHandler || entityUiHandler->CanToggleLockVisibility(entityId)) { @@ -436,7 +441,7 @@ namespace AzToolsFramework if (value.canConvert()) { const auto entityId = GetEntityFromIndex(index); - auto entityUiHandler = m_editorEntityFrameworkInterface->GetHandler(entityId); + auto entityUiHandler = m_editorEntityUiInterface->GetHandler(entityId); if (!entityUiHandler || entityUiHandler->CanToggleLockVisibility(entityId)) { @@ -532,6 +537,12 @@ namespace AzToolsFramework break; } + AZ::EntityId entityId = GetEntityFromIndex(index); + if (!m_focusModeInterface->IsInFocusSubTree(entityId)) + { + itemFlags &= !Qt::ItemIsEnabled; + } + return itemFlags; } diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerListModel.hxx b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerListModel.hxx index 25e4276dd4..1cc20ae1b0 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerListModel.hxx +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerListModel.hxx @@ -35,6 +35,11 @@ namespace AzToolsFramework { + namespace FocusModeFramework + { + class FocusModeInterface; + } + class EditorEntityUiInterface; namespace EntityOutliner @@ -273,7 +278,8 @@ namespace AzToolsFramework QVariant GetEntityIcon(const AZ::EntityId& id) const; QVariant GetEntityTooltip(const AZ::EntityId& id) const; - EditorEntityUiInterface* m_editorEntityFrameworkInterface = nullptr; + EditorEntityUiInterface* m_editorEntityUiInterface = nullptr; + FocusModeFramework::FocusModeInterface* m_focusModeInterface = nullptr; }; class EntityOutlinerCheckBox diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerTreeView.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerTreeView.cpp index 0549c600d3..842b48abdc 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerTreeView.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerTreeView.cpp @@ -38,6 +38,8 @@ namespace AzToolsFramework AZ_Assert((m_editorEntityFrameworkInterface != nullptr), "EntityOutlinerTreeView requires a EditorEntityFrameworkInterface instance on Construction."); + + viewport()->setMouseTracking(true); } EntityOutlinerTreeView::~EntityOutlinerTreeView() @@ -59,6 +61,11 @@ namespace AzToolsFramework } } + void EntityOutlinerTreeView::leaveEvent(QEvent* /*event*/) + { + m_mousePosition = QPoint(); + } + void EntityOutlinerTreeView::mousePressEvent(QMouseEvent* event) { //postponing normal mouse pressed logic until mouse is released or dragged @@ -112,6 +119,8 @@ namespace AzToolsFramework setSelectionMode(selectionModeBefore); } + m_mousePosition = event->pos(); + //process mouse movement as normal, potentially triggering drag and drop QTreeView::mouseMoveEvent(event); } @@ -172,12 +181,46 @@ namespace AzToolsFramework void EntityOutlinerTreeView::drawBranches(QPainter* painter, const QRect& rect, const QModelIndex& index) const { + const bool isEnabled = (this->model()->flags(index) & Qt::ItemIsEnabled); + + const bool isSelected = selectionModel()->isSelected(index); + const bool isHovered = (index == indexAt(m_mousePosition)) && isEnabled; + + + // Paint the branch Selection/Hover Rect + PaintBranchSelectionHoverRect(painter, rect, isSelected, isHovered); + // Paint the branch background as defined by the entity's handler, or its closes ancestor's. PaintBranchBackground(painter, rect, index); QTreeView::drawBranches(painter, rect, index); } + void EntityOutlinerTreeView::PaintBranchSelectionHoverRect( + QPainter* painter, const QRect& rect, bool isSelected, bool isHovered) const + { + painter->save(); + painter->setRenderHint(QPainter::Antialiasing, false); + + if (isSelected || isHovered) + { + QPainterPath backgroundPath; + QRect backgroundRect(rect); + + backgroundPath.addRect(backgroundRect); + + QColor backgroundColor = m_hoverColor; + if (isSelected) + { + backgroundColor = m_selectedColor; + } + + painter->fillPath(backgroundPath, backgroundColor); + } + + painter->restore(); + } + void EntityOutlinerTreeView::PaintBranchBackground(QPainter* painter, const QRect& rect, const QModelIndex& index) const { // Go through ancestors and add them to the stack diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerTreeView.hxx b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerTreeView.hxx index 66cd082407..5b0134366b 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerTreeView.hxx +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerTreeView.hxx @@ -59,6 +59,7 @@ namespace AzToolsFramework void startDrag(Qt::DropActions supportedActions) override; void dragMoveEvent(QDragMoveEvent* event) override; void dropEvent(QDropEvent* event) override; + void leaveEvent(QEvent* event) override; //! Renders the left side of the item: appropriate background, branch lines, icons. void drawBranches(QPainter* painter, const QRect& rect, const QModelIndex& index) const override; @@ -72,8 +73,10 @@ namespace AzToolsFramework void StartCustomDrag(const QModelIndexList& indexList, Qt::DropActions supportedActions) override; void PaintBranchBackground(QPainter* painter, const QRect& rect, const QModelIndex& index) const; + void PaintBranchSelectionHoverRect(QPainter* painter, const QRect& rect, bool isSelected, bool isHovered) const; QMouseEvent* m_queuedMouseEvent; + QPoint m_mousePosition; bool m_draggingUnselectedItem; // This is set when an item is dragged outside its bounding box. int m_expandOnlyDelay = -1; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorHelpers.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorHelpers.cpp index 25b10463dc..f50bd0d654 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorHelpers.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorHelpers.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -112,6 +113,17 @@ namespace AzToolsFramework } } + EditorHelpers::EditorHelpers(const EditorVisibleEntityDataCache* entityDataCache) + : m_entityDataCache(entityDataCache) + { + m_focusModeInterface = AZ::Interface::Get(); + AZ_Assert( + m_focusModeInterface, + "EditorHelpers - " + "Focus Mode Interface could not be found. " + "Check that it is being correctly initialized."); + } + AZ::EntityId EditorHelpers::HandleMouseInteraction( const AzFramework::CameraState& cameraState, const ViewportInteraction::MouseInteractionEvent& mouseInteraction) { @@ -173,6 +185,12 @@ namespace AzToolsFramework } } + // Verify if the entity Id corresponds to an entity that is focused; if not, halt selection. + if (!m_focusModeInterface->IsInFocusSubTree(entityIdUnderCursor)) + { + return AZ::EntityId(); + } + return entityIdUnderCursor; } diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorHelpers.h b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorHelpers.h index 0dbb1d0f6c..bec50bb067 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorHelpers.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorHelpers.h @@ -23,6 +23,11 @@ namespace AzToolsFramework { class EditorVisibleEntityDataCache; + namespace FocusModeFramework + { + class FocusModeInterface; + } + namespace ViewportInteraction { struct MouseInteractionEvent; @@ -38,10 +43,7 @@ namespace AzToolsFramework //! An EditorVisibleEntityDataCache must be passed to EditorHelpers to allow it to //! efficiently read entity data without resorting to EBus calls. - explicit EditorHelpers(const EditorVisibleEntityDataCache* entityDataCache) - : m_entityDataCache(entityDataCache) - { - } + explicit EditorHelpers(const EditorVisibleEntityDataCache* entityDataCache); EditorHelpers(const EditorHelpers&) = delete; EditorHelpers& operator=(const EditorHelpers&) = delete; ~EditorHelpers() = default; @@ -62,5 +64,6 @@ namespace AzToolsFramework private: const EditorVisibleEntityDataCache* m_entityDataCache = nullptr; //!< Entity Data queried by the EditorHelpers. + FocusModeFramework::FocusModeInterface* m_focusModeInterface = nullptr; }; } // namespace AzToolsFramework From 1dd58511c19d84f6c3f0ddcb7e575ec163c5345f Mon Sep 17 00:00:00 2001 From: Danilo Aimini <82231674+AMZN-daimini@users.noreply.github.com> Date: Tue, 21 Sep 2021 10:45:18 -0700 Subject: [PATCH 18/51] Remove FocusModeFramework nested namespace Signed-off-by: Danilo Aimini <82231674+AMZN-daimini@users.noreply.github.com> --- .../UI/Outliner/EntityOutlinerListModel.cpp | 2 +- .../UI/Outliner/EntityOutlinerListModel.hxx | 8 ++------ .../AzToolsFramework/ViewportSelection/EditorHelpers.cpp | 2 +- .../AzToolsFramework/ViewportSelection/EditorHelpers.h | 8 ++------ 4 files changed, 6 insertions(+), 14 deletions(-) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerListModel.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerListModel.cpp index f9521cb77f..d6d5c75304 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerListModel.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerListModel.cpp @@ -107,7 +107,7 @@ namespace AzToolsFramework AZ_Assert(m_editorEntityUiInterface != nullptr, "EntityOutlinerListModel requires a EditorEntityUiInterface instance on Initialize."); - m_focusModeInterface = AZ::Interface::Get(); + m_focusModeInterface = AZ::Interface::Get(); AZ_Assert( m_focusModeInterface != nullptr, "EntityOutlinerListModel requires a FocusModeInterface instance on Initialize."); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerListModel.hxx b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerListModel.hxx index 1cc20ae1b0..4b13aa6d27 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerListModel.hxx +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerListModel.hxx @@ -35,12 +35,8 @@ namespace AzToolsFramework { - namespace FocusModeFramework - { - class FocusModeInterface; - } - class EditorEntityUiInterface; + class FocusModeInterface; namespace EntityOutliner { @@ -279,7 +275,7 @@ namespace AzToolsFramework QVariant GetEntityTooltip(const AZ::EntityId& id) const; EditorEntityUiInterface* m_editorEntityUiInterface = nullptr; - FocusModeFramework::FocusModeInterface* m_focusModeInterface = nullptr; + FocusModeInterface* m_focusModeInterface = nullptr; }; class EntityOutlinerCheckBox diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorHelpers.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorHelpers.cpp index f50bd0d654..57e6c9ff7a 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorHelpers.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorHelpers.cpp @@ -116,7 +116,7 @@ namespace AzToolsFramework EditorHelpers::EditorHelpers(const EditorVisibleEntityDataCache* entityDataCache) : m_entityDataCache(entityDataCache) { - m_focusModeInterface = AZ::Interface::Get(); + m_focusModeInterface = AZ::Interface::Get(); AZ_Assert( m_focusModeInterface, "EditorHelpers - " diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorHelpers.h b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorHelpers.h index bec50bb067..87865202e0 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorHelpers.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorHelpers.h @@ -22,11 +22,7 @@ namespace AzFramework namespace AzToolsFramework { class EditorVisibleEntityDataCache; - - namespace FocusModeFramework - { - class FocusModeInterface; - } + class FocusModeInterface; namespace ViewportInteraction { @@ -64,6 +60,6 @@ namespace AzToolsFramework private: const EditorVisibleEntityDataCache* m_entityDataCache = nullptr; //!< Entity Data queried by the EditorHelpers. - FocusModeFramework::FocusModeInterface* m_focusModeInterface = nullptr; + FocusModeInterface* m_focusModeInterface = nullptr; }; } // namespace AzToolsFramework From 3fdf1f052aacb593453b269edd3ec1e15f327c8f Mon Sep 17 00:00:00 2001 From: Danilo Aimini <82231674+AMZN-daimini@users.noreply.github.com> Date: Wed, 22 Sep 2021 16:46:21 -0700 Subject: [PATCH 19/51] Minor comments and whitespace fixes. Signed-off-by: Danilo Aimini <82231674+AMZN-daimini@users.noreply.github.com> --- .../AzToolsFramework/UI/Outliner/EntityOutliner.qss | 11 ++++++----- .../UI/Outliner/EntityOutlinerTreeView.cpp | 1 - 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutliner.qss b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutliner.qss index 26e9506c9d..b3c5882334 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutliner.qss +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutliner.qss @@ -18,13 +18,14 @@ AzToolsFramework--EntityOutlinerWidget QTreeView selection-background-color: transparent; } + +/* + * Entity Outliner handles hover and selected state of items via code, + * so we need to override the AzQtComponents::Treeview style. + */ AzToolsFramework--EntityOutlinerWidget QTreeView::branch:hover , AzToolsFramework--EntityOutlinerWidget QTreeView::item:hover -{ - background: transparent; -} - -AzToolsFramework--EntityOutlinerWidget QTreeView::branch:selected +, AzToolsFramework--EntityOutlinerWidget QTreeView::branch:selected , AzToolsFramework--EntityOutlinerWidget QTreeView::item:selected , AzToolsFramework--EntityOutlinerWidget QTreeView::branch:selected:active , AzToolsFramework--EntityOutlinerWidget QTreeView::item:selected:active diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerTreeView.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerTreeView.cpp index 842b48abdc..e32a719a15 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerTreeView.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerTreeView.cpp @@ -186,7 +186,6 @@ namespace AzToolsFramework const bool isSelected = selectionModel()->isSelected(index); const bool isHovered = (index == indexAt(m_mousePosition)) && isEnabled; - // Paint the branch Selection/Hover Rect PaintBranchSelectionHoverRect(painter, rect, isSelected, isHovered); From 03372d2ea91e77113bc1b08a1972070a4122fb69 Mon Sep 17 00:00:00 2001 From: Danilo Aimini <82231674+AMZN-daimini@users.noreply.github.com> Date: Thu, 23 Sep 2021 12:50:49 -0700 Subject: [PATCH 20/51] Made IsInFocusSubTree const, and other minor refactorings. Signed-off-by: Danilo Aimini <82231674+AMZN-daimini@users.noreply.github.com> --- .../AzToolsFramework/FocusMode/FocusModeInterface.h | 2 +- .../AzToolsFramework/FocusMode/FocusModeSystemComponent.cpp | 2 +- .../AzToolsFramework/FocusMode/FocusModeSystemComponent.h | 2 +- .../AzToolsFramework/UI/Outliner/EntityOutlinerListModel.cpp | 3 +-- .../AzToolsFramework/UI/Outliner/EntityOutlinerTreeView.cpp | 2 +- .../AzToolsFramework/ViewportSelection/EditorHelpers.h | 2 +- 6 files changed, 6 insertions(+), 7 deletions(-) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/FocusMode/FocusModeInterface.h b/Code/Framework/AzToolsFramework/AzToolsFramework/FocusMode/FocusModeInterface.h index 6d3a7599de..a4b90f95b7 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/FocusMode/FocusModeInterface.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/FocusMode/FocusModeInterface.h @@ -33,7 +33,7 @@ namespace AzToolsFramework virtual AZ::EntityId GetFocusRoot() = 0; //! Returns whether the entity id provided is part of the focused sub-tree. - virtual bool IsInFocusSubTree(AZ::EntityId entityId) = 0; + virtual bool IsInFocusSubTree(AZ::EntityId entityId) const = 0; }; } // namespace AzToolsFramework diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/FocusMode/FocusModeSystemComponent.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/FocusMode/FocusModeSystemComponent.cpp index e08191032d..e1c04fcb93 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/FocusMode/FocusModeSystemComponent.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/FocusMode/FocusModeSystemComponent.cpp @@ -79,7 +79,7 @@ namespace AzToolsFramework return m_focusRoot; } - bool FocusModeSystemComponent::IsInFocusSubTree(AZ::EntityId entityId) + bool FocusModeSystemComponent::IsInFocusSubTree(AZ::EntityId entityId) const { if (m_focusRoot == AZ::EntityId()) { diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/FocusMode/FocusModeSystemComponent.h b/Code/Framework/AzToolsFramework/AzToolsFramework/FocusMode/FocusModeSystemComponent.h index 697a4ff998..27ddebe1ed 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/FocusMode/FocusModeSystemComponent.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/FocusMode/FocusModeSystemComponent.h @@ -42,7 +42,7 @@ namespace AzToolsFramework void SetFocusRoot(AZ::EntityId entityId) override; void ClearFocusRoot() override; AZ::EntityId GetFocusRoot() override; - bool IsInFocusSubTree(AZ::EntityId entityId) override; + bool IsInFocusSubTree(AZ::EntityId entityId) const override; private: AZ::EntityId m_focusRoot; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerListModel.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerListModel.cpp index d6d5c75304..90f8e1d121 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerListModel.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerListModel.cpp @@ -537,8 +537,7 @@ namespace AzToolsFramework break; } - AZ::EntityId entityId = GetEntityFromIndex(index); - if (!m_focusModeInterface->IsInFocusSubTree(entityId)) + if (AZ::EntityId entityId = GetEntityFromIndex(index); !m_focusModeInterface->IsInFocusSubTree(entityId)) { itemFlags &= !Qt::ItemIsEnabled; } diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerTreeView.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerTreeView.cpp index e32a719a15..2d20ac7364 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerTreeView.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerTreeView.cpp @@ -61,7 +61,7 @@ namespace AzToolsFramework } } - void EntityOutlinerTreeView::leaveEvent(QEvent* /*event*/) + void EntityOutlinerTreeView::leaveEvent([[maybe_unused]] QEvent* event) { m_mousePosition = QPoint(); } diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorHelpers.h b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorHelpers.h index 87865202e0..a6a78a4e61 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorHelpers.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorHelpers.h @@ -60,6 +60,6 @@ namespace AzToolsFramework private: const EditorVisibleEntityDataCache* m_entityDataCache = nullptr; //!< Entity Data queried by the EditorHelpers. - FocusModeInterface* m_focusModeInterface = nullptr; + const FocusModeInterface* m_focusModeInterface = nullptr; }; } // namespace AzToolsFramework From 3c882230edd196e2803806cff201c94b3c5ae147 Mon Sep 17 00:00:00 2001 From: srikappa-amzn Date: Thu, 23 Sep 2021 15:25:15 -0700 Subject: [PATCH 21/51] Store the spawn ticket id inside entity instead of maintaining a map of entity to ticket Signed-off-by: srikappa-amzn --- .../AzCore/AzCore/Component/Entity.cpp | 10 ++ .../AzCore/AzCore/Component/Entity.h | 5 + .../Entity/GameEntityContextComponent.cpp | 21 ++- .../Spawnable/SpawnableEntitiesInterface.cpp | 22 ++- .../Spawnable/SpawnableEntitiesInterface.h | 36 +++-- .../Spawnable/SpawnableEntitiesManager.cpp | 130 +++++++++++------- .../Spawnable/SpawnableEntitiesManager.h | 38 +++-- .../Spawnable/SpawnedEntityTicketMapper.cpp | 44 ------ .../Spawnable/SpawnedEntityTicketMapper.h | 38 ----- .../SpawnedEntityTicketMapperInterface.h | 24 ---- .../AzFramework/azframework_files.cmake | 3 - 11 files changed, 179 insertions(+), 192 deletions(-) delete mode 100644 Code/Framework/AzFramework/AzFramework/Spawnable/SpawnedEntityTicketMapper.cpp delete mode 100644 Code/Framework/AzFramework/AzFramework/Spawnable/SpawnedEntityTicketMapper.h delete mode 100644 Code/Framework/AzFramework/AzFramework/Spawnable/SpawnedEntityTicketMapperInterface.h diff --git a/Code/Framework/AzCore/AzCore/Component/Entity.cpp b/Code/Framework/AzCore/AzCore/Component/Entity.cpp index 00c1895261..4f55c360cd 100644 --- a/Code/Framework/AzCore/AzCore/Component/Entity.cpp +++ b/Code/Framework/AzCore/AzCore/Component/Entity.cpp @@ -655,6 +655,16 @@ namespace AZ m_stateEvent.Signal(oldState, m_state); } + void Entity::SetSpawnTicketId(u32 spawnTicketId) + { + m_spawnTicketId = AZStd::move(spawnTicketId); + } + + u32 Entity::GetSpawnTicketId() const + { + return m_spawnTicketId; + } + void Entity::OnNameChanged() const { EBUS_EVENT_ID(GetId(), EntityBus, OnEntityNameChanged, m_name); diff --git a/Code/Framework/AzCore/AzCore/Component/Entity.h b/Code/Framework/AzCore/AzCore/Component/Entity.h index 7ec63a56ac..97cec50946 100644 --- a/Code/Framework/AzCore/AzCore/Component/Entity.h +++ b/Code/Framework/AzCore/AzCore/Component/Entity.h @@ -133,6 +133,9 @@ namespace AZ //! @return The state of the entity. For example, the entity has been initialized, the entity is active, and so on. State GetState() const { return m_state; } + u32 GetSpawnTicketId() const; + void SetSpawnTicketId(u32); + //! Connects an entity state event handler to the entity. //! All state changes will be signaled through this event. //! @param handler reference to the EntityStateEvent handler to attach to the entities state event. @@ -411,6 +414,8 @@ namespace AZ //! A user-friendly name for the entity. This makes error messages easier to read. AZStd::string m_name; + u32 m_spawnTicketId = 0; + //! The state of the entity. State m_state; diff --git a/Code/Framework/AzFramework/AzFramework/Entity/GameEntityContextComponent.cpp b/Code/Framework/AzFramework/AzFramework/Entity/GameEntityContextComponent.cpp index 694ed83765..f2571c7549 100644 --- a/Code/Framework/AzFramework/AzFramework/Entity/GameEntityContextComponent.cpp +++ b/Code/Framework/AzFramework/AzFramework/Entity/GameEntityContextComponent.cpp @@ -14,7 +14,7 @@ #include #include #include -#include +#include #include "GameEntityContextComponent.h" @@ -290,10 +290,21 @@ namespace AzFramework isPrefabSystemEnabled, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled); if (isPrefabSystemEnabled) { - SpawnedEntityTicketMapperInterface* spawnedEntityTicketMapperInterface = - AZ::Interface::Get(); - AZ_Assert(spawnedEntityTicketMapperInterface != nullptr, "SpawnedEntityTicketMapperInterface is not found."); - spawnedEntityTicketMapperInterface->RemoveSpawnedEntity(currentEntity->GetId()); + if (currentEntity->GetSpawnTicketId() > 0) + { + SpawnableEntitiesDefinition* spawnableEntitiesInterface = SpawnableEntitiesInterface::Get(); + AZ_Assert(spawnableEntitiesInterface != nullptr, "SpawnableEntitiesInterface is not found."); + spawnableEntitiesInterface->GetEntitySpawnTicket( + currentEntity->GetSpawnTicketId(), + [spawnableEntitiesInterface, currentEntity](EntitySpawnTicket* entitySpawnTicket) + { + if (entitySpawnTicket != nullptr) + { + spawnableEntitiesInterface->DespawnEntity(currentEntity->GetId(), *entitySpawnTicket); + } + }); + return; + } } if (currentEntity->GetState() == AZ::Entity::State::Active) diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.cpp b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.cpp index 617eb3a0b9..53bffd0d26 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.cpp +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.cpp @@ -227,10 +227,16 @@ namespace AzFramework EntitySpawnTicket::EntitySpawnTicket(EntitySpawnTicket&& rhs) : m_payload(rhs.m_payload) - , m_id(rhs.m_id) { + auto manager = SpawnableEntitiesInterface::Get(); + AZ_Assert(manager, "SpawnableEntitiesInterface has no implementation."); rhs.m_payload = nullptr; + Id previousId = m_id; + m_id = rhs.m_id; rhs.m_id = 0; + AZStd::scoped_lock lock(manager->m_entitySpawnTicketMapMutex); + manager->m_entitySpawnTicketMap.erase(previousId); + manager->m_entitySpawnTicketMap.insert_or_assign(rhs.m_id, this); } EntitySpawnTicket::EntitySpawnTicket(AZ::Data::Asset spawnable) @@ -240,6 +246,8 @@ namespace AzFramework AZStd::pair result = manager->CreateTicket(AZStd::move(spawnable)); m_id = result.first; m_payload = result.second; + AZStd::scoped_lock lock(manager->m_entitySpawnTicketMapMutex); + manager->m_entitySpawnTicketMap.insert_or_assign(m_id, this); } EntitySpawnTicket::~EntitySpawnTicket() @@ -250,6 +258,8 @@ namespace AzFramework AZ_Assert(manager, "Attempting to destroy an entity spawn ticket while the SpawnableEntitiesInterface has no implementation."); manager->DestroyTicket(m_payload); m_payload = nullptr; + AZStd::scoped_lock lock(manager->m_entitySpawnTicketMapMutex); + manager->m_entitySpawnTicketMap.erase(m_id); m_id = 0; } } @@ -258,17 +268,23 @@ namespace AzFramework { if (this != &rhs) { + auto manager = SpawnableEntitiesInterface::Get(); + AZ_Assert(manager, "Attempting to destroy an entity spawn ticket while the SpawnableEntitiesInterface has no implementation."); if (m_payload) { - auto manager = SpawnableEntitiesInterface::Get(); - AZ_Assert(manager, "Attempting to destroy an entity spawn ticket while the SpawnableEntitiesInterface has no implementation."); manager->DestroyTicket(m_payload); } + + Id previousId = m_id; m_id = rhs.m_id; rhs.m_id = 0; m_payload = rhs.m_payload; rhs.m_payload = nullptr; + + AZStd::scoped_lock lock(manager->m_entitySpawnTicketMapMutex); + manager->m_entitySpawnTicketMap.erase(previousId); + manager->m_entitySpawnTicketMap.insert_or_assign(m_id, this); } return *this; } diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.h b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.h index 2ce4158229..91b2eafc54 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.h +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.h @@ -140,7 +140,7 @@ namespace AzFramework public: friend class SpawnableEntitiesDefinition; - using Id = uint64_t; + using Id = uint32_t; EntitySpawnTicket() = default; EntitySpawnTicket(const EntitySpawnTicket&) = delete; @@ -162,6 +162,7 @@ namespace AzFramework using EntitySpawnCallback = AZStd::function; using EntityPreInsertionCallback = AZStd::function; using EntityDespawnCallback = AZStd::function; + using GetEntitySpawnTicketCallback = AZStd::function; using ReloadSpawnableCallback = AZStd::function; using ListEntitiesCallback = AZStd::function; using ListIndicesEntitiesCallback = AZStd::function; @@ -206,12 +207,21 @@ namespace AzFramework struct DespawnAllEntitiesOptionalArgs final { //! Callback that's called when despawning entities has completed. This can be triggered from a different thread than the one that - //! made the function call to despawn. The returned list of entities contains all the newly created entities. + //! made the function call to despawn. EntityDespawnCallback m_completionCallback; //! The priority at which this call will be executed. SpawnablePriority m_priority { SpawnablePriority_Default }; }; + struct DespawnEntityOptionalArgs final + { + //! Callback that's called when despawning entity has completed. This can be triggered from a different thread than the one that + //! made the function call to despawn. + EntityDespawnCallback m_completionCallback; + //! The priority at which this call will be executed. + SpawnablePriority m_priority{ SpawnablePriority_Default }; + }; + struct ReloadSpawnableOptionalArgs final { //! Callback that's called when respawning entities has completed. This can be triggered from a different thread than the one that @@ -235,12 +245,6 @@ namespace AzFramework SpawnablePriority m_priority{ SpawnablePriority_Default }; }; - struct ClaimEntityOptionalArgs final - { - //! The priority at which this call will be executed. - SpawnablePriority m_priority{ SpawnablePriority_Default }; - }; - struct BarrierOptionalArgs final { //! The priority at which this call will be executed. @@ -283,9 +287,18 @@ namespace AzFramework EntitySpawnTicket& ticket, AZStd::vector entityIndices, SpawnEntitiesOptionalArgs optionalArgs = {}) = 0; //! Removes all entities in the provided list from the environment. //! @param ticket The ticket previously used to spawn entities with. - //! @param priority The priority at which this call will be executed. //! @param optionalArgs Optional additional arguments, see DespawnAllEntitiesOptionalArgs. virtual void DespawnAllEntities(EntitySpawnTicket& ticket, DespawnAllEntitiesOptionalArgs optionalArgs = {}) = 0; + //! Removes the entity with the provided id from the spawned list of entities. + //! @param entityId the id of entity to despawn. + //! @param ticket The ticket previously used to spawn entities with. + //! @param optionalArgs Optional additional arguments, see DespawnEntityOptionalArgs. + virtual void DespawnEntity(AZ::EntityId entityId, EntitySpawnTicket& ticket, DespawnEntityOptionalArgs optionalArgs = {}) = 0; + //! Gets the EntitySpawnTicket associated with the entitySpawnTicketId. + //! @param entitySpawnTicketId the id of EntitySpawnTicket to get. + //! @param getEntitySpawnTicketCallback The callback to execute upon fetching the ticket. + virtual void GetEntitySpawnTicket( + EntitySpawnTicket::Id entitySpawnTicketId, GetEntitySpawnTicketCallback getEntitySpawnTicketCallback) = 0; //! Removes all entities in the provided list from the environment and reconstructs the entities from the provided spawnable. //! @param ticket Holds the information on the entities to reload. //! @param priority The priority at which this call will be executed. @@ -320,8 +333,6 @@ namespace AzFramework virtual void ClaimEntities( EntitySpawnTicket& ticket, ClaimEntitiesCallback listCallback, ClaimEntitiesOptionalArgs optionalArgs = {}) = 0; - virtual void ClaimEntity(AZ::EntityId entityId, void* ticket, ClaimEntityOptionalArgs optionalArgs = {}) = 0; - //! Blocks until all operations made on the provided ticket before the barrier call have completed. //! @param ticket The ticket to monitor. //! @param completionCallback Required callback that will be called as soon as the barrier has been reached. @@ -355,6 +366,9 @@ namespace AzFramework { return reinterpret_cast(ticket->m_payload); } + + AZStd::unordered_map m_entitySpawnTicketMap; + AZStd::mutex m_entitySpawnTicketMapMutex; }; using SpawnableEntitiesInterface = AZ::Interface; diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp index 92d461819b..c75a5fe834 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp @@ -21,13 +21,13 @@ namespace AzFramework { template - void SpawnableEntitiesManager::QueueRequest(Ticket* ticket, SpawnablePriority priority, T&& request) + void SpawnableEntitiesManager::QueueRequest(EntitySpawnTicket& ticket, SpawnablePriority priority, T&& request) { - request.m_ticket = ticket; + request.m_ticket = &GetTicketPayload(ticket); Queue& queue = priority <= m_highPriorityThreshold ? m_highPriorityQueue : m_regularPriorityQueue; { AZStd::scoped_lock queueLock(queue.m_pendingRequestMutex); - request.m_requestId = ticket->m_nextRequestId++; + request.m_requestId = GetTicketPayload(ticket).m_nextRequestId++; queue.m_pendingRequest.push(AZStd::move(request)); } } @@ -56,7 +56,7 @@ namespace AzFramework optionalArgs.m_serializeContext == nullptr ? m_defaultSerializeContext : optionalArgs.m_serializeContext; queueEntry.m_completionCallback = AZStd::move(optionalArgs.m_completionCallback); queueEntry.m_preInsertionCallback = AZStd::move(optionalArgs.m_preInsertionCallback); - QueueRequest(&GetTicketPayload(ticket), optionalArgs.m_priority, AZStd::move(queueEntry)); + QueueRequest(ticket, optionalArgs.m_priority, AZStd::move(queueEntry)); } void SpawnableEntitiesManager::SpawnEntities( @@ -72,7 +72,7 @@ namespace AzFramework queueEntry.m_completionCallback = AZStd::move(optionalArgs.m_completionCallback); queueEntry.m_preInsertionCallback = AZStd::move(optionalArgs.m_preInsertionCallback); queueEntry.m_referencePreviouslySpawnedEntities = optionalArgs.m_referencePreviouslySpawnedEntities; - QueueRequest(&GetTicketPayload(ticket), optionalArgs.m_priority, AZStd::move(queueEntry)); + QueueRequest(ticket, optionalArgs.m_priority, AZStd::move(queueEntry)); } void SpawnableEntitiesManager::DespawnAllEntities(EntitySpawnTicket& ticket, DespawnAllEntitiesOptionalArgs optionalArgs) @@ -82,7 +82,36 @@ namespace AzFramework DespawnAllEntitiesCommand queueEntry; queueEntry.m_ticketId = ticket.GetId(); queueEntry.m_completionCallback = AZStd::move(optionalArgs.m_completionCallback); - QueueRequest(&GetTicketPayload(ticket), optionalArgs.m_priority, AZStd::move(queueEntry)); + QueueRequest(ticket, optionalArgs.m_priority, AZStd::move(queueEntry)); + } + + void SpawnableEntitiesManager::DespawnEntity(AZ::EntityId entityId, EntitySpawnTicket& ticket, DespawnEntityOptionalArgs optionalArgs) + { + AZ_Assert(ticket.IsValid(), "Ticket provided to DespawnEntity hasn't been initialized."); + + DespawnEntityCommand queueEntry; + queueEntry.m_ticketId = ticket.GetId(); + queueEntry.m_entityId = entityId; + queueEntry.m_completionCallback = AZStd::move(optionalArgs.m_completionCallback); + QueueRequest(ticket, optionalArgs.m_priority, AZStd::move(queueEntry)); + } + + void SpawnableEntitiesManager::GetEntitySpawnTicket( + EntitySpawnTicket::Id entitySpawnTicketId, GetEntitySpawnTicketCallback getEntitySpawnTicketCallback) + { + if (entitySpawnTicketId == 0) + { + AZ_Error("Spawnable", false, "Ticket id provided to GetEntitySpawnTicket is invalid."); + return; + } + + auto entitySpawnTicketIterator = m_entitySpawnTicketMap.find(entitySpawnTicketId); + if (entitySpawnTicketIterator == m_entitySpawnTicketMap.end()) + { + AZ_Error("Spawnable", false, "The EntitySpawnTicket corresponding to id '%lu' cannot be found", entitySpawnTicketId); + return; + } + getEntitySpawnTicketCallback(entitySpawnTicketIterator->second); } void SpawnableEntitiesManager::ReloadSpawnable( @@ -96,7 +125,7 @@ namespace AzFramework queueEntry.m_serializeContext = optionalArgs.m_serializeContext == nullptr ? m_defaultSerializeContext : optionalArgs.m_serializeContext; queueEntry.m_completionCallback = AZStd::move(optionalArgs.m_completionCallback); - QueueRequest(&GetTicketPayload(ticket), optionalArgs.m_priority, AZStd::move(queueEntry)); + QueueRequest(ticket, optionalArgs.m_priority, AZStd::move(queueEntry)); } void SpawnableEntitiesManager::ListEntities( @@ -108,7 +137,7 @@ namespace AzFramework ListEntitiesCommand queueEntry; queueEntry.m_ticketId = ticket.GetId(); queueEntry.m_listCallback = AZStd::move(listCallback); - QueueRequest(&GetTicketPayload(ticket), optionalArgs.m_priority, AZStd::move(queueEntry)); + QueueRequest(ticket, optionalArgs.m_priority, AZStd::move(queueEntry)); } void SpawnableEntitiesManager::ListIndicesAndEntities( @@ -120,7 +149,7 @@ namespace AzFramework ListIndicesEntitiesCommand queueEntry; queueEntry.m_ticketId = ticket.GetId(); queueEntry.m_listCallback = AZStd::move(listCallback); - QueueRequest(&GetTicketPayload(ticket), optionalArgs.m_priority, AZStd::move(queueEntry)); + QueueRequest(ticket, optionalArgs.m_priority, AZStd::move(queueEntry)); } void SpawnableEntitiesManager::ClaimEntities( @@ -132,18 +161,7 @@ namespace AzFramework ClaimEntitiesCommand queueEntry; queueEntry.m_ticketId = ticket.GetId(); queueEntry.m_listCallback = AZStd::move(listCallback); - QueueRequest(&GetTicketPayload(ticket), optionalArgs.m_priority, AZStd::move(queueEntry)); - } - - void SpawnableEntitiesManager::ClaimEntity( - AZ::EntityId entityId, void* ticket, - ClaimEntityOptionalArgs optionalArgs) - { - AZ_Assert(ticket == nullptr, "Ticket provided to ClaimEntity is invalid."); - - ClaimEntityCommand queueEntry; - queueEntry.m_entityId = entityId; - QueueRequest(reinterpret_cast(ticket), optionalArgs.m_priority, AZStd::move(queueEntry)); + QueueRequest(ticket, optionalArgs.m_priority, AZStd::move(queueEntry)); } void SpawnableEntitiesManager::Barrier(EntitySpawnTicket& ticket, BarrierCallback completionCallback, BarrierOptionalArgs optionalArgs) @@ -154,7 +172,7 @@ namespace AzFramework BarrierCommand queueEntry; queueEntry.m_ticketId = ticket.GetId(); queueEntry.m_completionCallback = AZStd::move(completionCallback); - QueueRequest(&GetTicketPayload(ticket), optionalArgs.m_priority, AZStd::move(queueEntry)); + QueueRequest(ticket, optionalArgs.m_priority, AZStd::move(queueEntry)); } auto SpawnableEntitiesManager::ProcessQueue(CommandQueuePriority priority) -> CommandQueueStatus @@ -234,12 +252,13 @@ namespace AzFramework return queue.m_delayed.empty() ? CommandQueueStatus::NoCommandsLeft : CommandQueueStatus::HasCommandsLeft; } - AZStd::pair SpawnableEntitiesManager::CreateTicket(AZ::Data::Asset&& spawnable) + AZStd::pair SpawnableEntitiesManager::CreateTicket(AZ::Data::Asset&& spawnable) { - static AZStd::atomic_uint64_t idCounter { 1 }; + static AZStd::atomic_uint32_t idCounter { 1 }; auto result = aznew Ticket(); result->m_spawnable = AZStd::move(spawnable); + return AZStd::make_pair(idCounter++, result); } @@ -351,7 +370,7 @@ namespace AzFramework for (auto it = ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount; it != ticket.m_spawnedEntities.end(); ++it) { GameEntityContextRequestBus::Broadcast(&GameEntityContextRequestBus::Events::AddGameEntity, *it); - m_spawnedEntityTicketMapper.AddSpawnedEntity((*it)->GetId(), &ticket); + (*it)->SetSpawnTicketId(request.m_ticketId); } // Let other systems know about newly spawned entities for any post-processing after adding to the scene/game context. @@ -433,7 +452,7 @@ namespace AzFramework for (auto it = ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount; it != ticket.m_spawnedEntities.end(); ++it) { GameEntityContextRequestBus::Broadcast(&GameEntityContextRequestBus::Events::AddGameEntity, *it); - m_spawnedEntityTicketMapper.AddSpawnedEntity((*it)->GetId(), &ticket); + (*it)->SetSpawnTicketId(request.m_ticketId); } if (request.m_completionCallback) @@ -460,6 +479,7 @@ namespace AzFramework { if (entity != nullptr) { + entity->SetSpawnTicketId(0); GameEntityContextRequestBus::Broadcast( &GameEntityContextRequestBus::Events::DestroyGameEntity, entity->GetId()); } @@ -482,6 +502,39 @@ namespace AzFramework } } + bool SpawnableEntitiesManager::ProcessRequest(DespawnEntityCommand& request) + { + Ticket& ticket = *request.m_ticket; + if (request.m_requestId == ticket.m_currentRequestId) + { + AZStd::vector& spawnedEntities = request.m_ticket->m_spawnedEntities; + for (auto entityIterator = spawnedEntities.begin(); entityIterator != spawnedEntities.end(); ++entityIterator) + { + if (*entityIterator != nullptr && (*entityIterator)->GetId() == request.m_entityId) + { + (*entityIterator)->SetSpawnTicketId(0); + GameEntityContextRequestBus::Broadcast( + &GameEntityContextRequestBus::Events::DestroyGameEntity, (*entityIterator)->GetId()); + AZStd::iter_swap(entityIterator, spawnedEntities.rbegin()); + spawnedEntities.pop_back(); + break; + } + } + + if (request.m_completionCallback) + { + request.m_completionCallback(request.m_ticketId); + } + + ticket.m_currentRequestId++; + return true; + } + else + { + return false; + } + } + bool SpawnableEntitiesManager::ProcessRequest(ReloadSpawnableCommand& request) { Ticket& ticket = *request.m_ticket; @@ -495,6 +548,7 @@ namespace AzFramework { if (entity != nullptr) { + entity->SetSpawnTicketId(0); GameEntityContextRequestBus::Broadcast( &GameEntityContextRequestBus::Events::DestroyGameEntity, entity->GetId()); } @@ -622,29 +676,6 @@ namespace AzFramework } } - bool SpawnableEntitiesManager::ProcessRequest(ClaimEntityCommand& request) - { - Ticket* ticket = request.m_ticket; - if (request.m_requestId == ticket->m_currentRequestId) - { - AZStd::vector& spawnedEntities = ticket->m_spawnedEntities; - for (auto entityIterator = spawnedEntities.begin(); entityIterator != spawnedEntities.end(); ++entityIterator) - { - if ((*entityIterator)->GetId() == request.m_entityId) - { - AZStd::iter_swap(entityIterator, spawnedEntities.rbegin()); - spawnedEntities.pop_back(); - return true; - } - } - return true; - } - else - { - return false; - } - } - bool SpawnableEntitiesManager::ProcessRequest(BarrierCommand& request) { Ticket& ticket = *request.m_ticket; @@ -672,6 +703,7 @@ namespace AzFramework { if (entity != nullptr) { + entity->SetSpawnTicketId(0); GameEntityContextRequestBus::Broadcast( &GameEntityContextRequestBus::Events::DestroyGameEntity, entity->GetId()); } diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h index e5a0760010..5445360d64 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h @@ -16,7 +16,6 @@ #include #include #include -#include namespace AZ { @@ -58,6 +57,9 @@ namespace AzFramework void SpawnEntities( EntitySpawnTicket& ticket, AZStd::vector entityIndices, SpawnEntitiesOptionalArgs optionalArgs = {}) override; void DespawnAllEntities(EntitySpawnTicket& ticket, DespawnAllEntitiesOptionalArgs optionalArgs = {}) override; + void DespawnEntity(AZ::EntityId entityId, EntitySpawnTicket& ticket, DespawnEntityOptionalArgs optionalArgs = {}) override; + void GetEntitySpawnTicket( + EntitySpawnTicket::Id entitySpawnTicketId, GetEntitySpawnTicketCallback getEntitySpawnTicketCallback) override; void ReloadSpawnable( EntitySpawnTicket& ticket, AZ::Data::Asset spawnable, ReloadSpawnableOptionalArgs optionalArgs = {}) override; @@ -67,8 +69,6 @@ namespace AzFramework EntitySpawnTicket& ticket, ListIndicesEntitiesCallback listCallback, ListEntitiesOptionalArgs optionalArgs = {}) override; void ClaimEntities( EntitySpawnTicket& ticket, ClaimEntitiesCallback listCallback, ClaimEntitiesOptionalArgs optionalArgs = {}) override; - void ClaimEntity( - AZ::EntityId entityId, void* ticket, ClaimEntityOptionalArgs optionalArgs = {}) override; void Barrier(EntitySpawnTicket& spawnInfo, BarrierCallback completionCallback, BarrierOptionalArgs optionalArgs = {}) override; @@ -135,6 +135,14 @@ namespace AzFramework EntitySpawnTicket::Id m_ticketId; uint32_t m_requestId; }; + struct DespawnEntityCommand + { + EntityDespawnCallback m_completionCallback; + Ticket* m_ticket; + EntitySpawnTicket::Id m_ticketId; + uint32_t m_requestId; + AZ::EntityId m_entityId; + }; struct ReloadSpawnableCommand { AZ::Data::Asset m_spawnable; @@ -165,12 +173,6 @@ namespace AzFramework EntitySpawnTicket::Id m_ticketId; uint32_t m_requestId; }; - struct ClaimEntityCommand - { - Ticket* m_ticket; - uint32_t m_requestId; - AZ::EntityId m_entityId; - }; struct BarrierCommand { BarrierCallback m_completionCallback; @@ -185,8 +187,16 @@ namespace AzFramework }; using Requests = AZStd::variant< - SpawnAllEntitiesCommand, SpawnEntitiesCommand, DespawnAllEntitiesCommand, ReloadSpawnableCommand, ListEntitiesCommand, - ListIndicesEntitiesCommand, ClaimEntitiesCommand, ClaimEntityCommand, BarrierCommand, DestroyTicketCommand>; + SpawnAllEntitiesCommand, + SpawnEntitiesCommand, + DespawnAllEntitiesCommand, + DespawnEntityCommand, + ReloadSpawnableCommand, + ListEntitiesCommand, + ListIndicesEntitiesCommand, + ClaimEntitiesCommand, + BarrierCommand, + DestroyTicketCommand>; struct Queue { @@ -196,7 +206,7 @@ namespace AzFramework }; template - void QueueRequest(Ticket* ticket, SpawnablePriority priority, T&& request); + void QueueRequest(EntitySpawnTicket& ticket, SpawnablePriority priority, T&& request); AZStd::pair CreateTicket(AZ::Data::Asset&& spawnable) override; void DestroyTicket(void* ticket) override; @@ -208,11 +218,11 @@ namespace AzFramework bool ProcessRequest(SpawnAllEntitiesCommand& request); bool ProcessRequest(SpawnEntitiesCommand& request); bool ProcessRequest(DespawnAllEntitiesCommand& request); + bool ProcessRequest(DespawnEntityCommand& request); bool ProcessRequest(ReloadSpawnableCommand& request); bool ProcessRequest(ListEntitiesCommand& request); bool ProcessRequest(ListIndicesEntitiesCommand& request); bool ProcessRequest(ClaimEntitiesCommand& request); - bool ProcessRequest(ClaimEntityCommand& request); bool ProcessRequest(BarrierCommand& request); bool ProcessRequest(DestroyTicketCommand& request); @@ -234,8 +244,6 @@ namespace AzFramework //! SpawnablePriority_Default which gives users a bit of room to fine tune the priorities as this value can be configured //! through the Settings Registry under the key "/O3DE/AzFramework/Spawnables/HighPriorityThreshold". SpawnablePriority m_highPriorityThreshold { 64 }; - private: - SpawnedEntityTicketMapper m_spawnedEntityTicketMapper; }; AZ_DEFINE_ENUM_BITWISE_OPERATORS(AzFramework::SpawnableEntitiesManager::CommandQueuePriority); diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnedEntityTicketMapper.cpp b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnedEntityTicketMapper.cpp deleted file mode 100644 index 3a6b40f5a9..0000000000 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnedEntityTicketMapper.cpp +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (c) Contributors to the Open 3D Engine Project. - * For complete copyright and license terms please see the LICENSE at the root of this distribution. - * - * SPDX-License-Identifier: Apache-2.0 OR MIT - * - */ - -#include -#include - -namespace AzFramework -{ - SpawnedEntityTicketMapper::SpawnedEntityTicketMapper() - { - spawnableEntitiesInterface = SpawnableEntitiesInterface::Get(); - AZ_Assert(spawnableEntitiesInterface != nullptr, "SpawnableEntitiesInterface is not found."); - - AZ::Interface::Register(this); - } - - SpawnedEntityTicketMapper::~SpawnedEntityTicketMapper() - { - AZ::Interface::Unregister(this); - } - - void SpawnedEntityTicketMapper::RemoveSpawnedEntity(AZ::EntityId entityId) - { - auto spawnedGameEntitiesIterator = m_spawnedEntities.find(entityId); - if (spawnedGameEntitiesIterator != m_spawnedEntities.end()) - { - spawnableEntitiesInterface->ClaimEntity( - spawnedGameEntitiesIterator->first, spawnedGameEntitiesIterator->second); - m_spawnedEntities.erase(entityId); - } - } - - void SpawnedEntityTicketMapper::AddSpawnedEntity(AZ::EntityId entityId, void* ticket) - { - m_spawnedEntities.emplace(entityId, ticket); - } - -} - diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnedEntityTicketMapper.h b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnedEntityTicketMapper.h deleted file mode 100644 index 45e701db4e..0000000000 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnedEntityTicketMapper.h +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (c) Contributors to the Open 3D Engine Project. - * For complete copyright and license terms please see the LICENSE at the root of this distribution. - * - * SPDX-License-Identifier: Apache-2.0 OR MIT - * - */ - -#pragma once - -#include -#include -#include - -namespace AzFramework -{ - class SpawnedEntityTicketMapper - : public SpawnedEntityTicketMapperInterface - { - public: - AZ_RTTI(SpawnedEntityTicketMapper, "{5C803604-E949-44F4-94A4-F835E5794C77}", SpawnedEntityTicketMapperInterface); - - SpawnedEntityTicketMapper(); - ~SpawnedEntityTicketMapper(); - - //! Removes the entityId from the spawned entities map if present. - //! @param entityId The id of the entity to remove. - void RemoveSpawnedEntity(AZ::EntityId entityId) override; - - //! Adds the entityId,ticket pair to the spawned entities map. - //! @param entityId The id of the entity to add. - //! @param ticket The ticket pointer to add. - void AddSpawnedEntity(AZ::EntityId entityId, void* ticket) override; - private: - SpawnableEntitiesDefinition* spawnableEntitiesInterface = nullptr; - AZStd::unordered_map m_spawnedEntities; - }; -} // namespace AzFramework diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnedEntityTicketMapperInterface.h b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnedEntityTicketMapperInterface.h deleted file mode 100644 index 8f0bd96dbe..0000000000 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnedEntityTicketMapperInterface.h +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (c) Contributors to the Open 3D Engine Project. - * For complete copyright and license terms please see the LICENSE at the root of this distribution. - * - * SPDX-License-Identifier: Apache-2.0 OR MIT - * - */ - -#pragma once - -#include -#include - -namespace AzFramework -{ - class SpawnedEntityTicketMapperInterface - { - public: - AZ_RTTI(SpawnedEntityTicketMapperInterface, "{D407E96B-635C-44F2-B089-084EBAB8B036}"); - - virtual void RemoveSpawnedEntity(AZ::EntityId entityId) = 0; - virtual void AddSpawnedEntity(AZ::EntityId entityId, void* ticket) = 0; - }; -} diff --git a/Code/Framework/AzFramework/AzFramework/azframework_files.cmake b/Code/Framework/AzFramework/AzFramework/azframework_files.cmake index b486596e2a..87cf29ffec 100644 --- a/Code/Framework/AzFramework/AzFramework/azframework_files.cmake +++ b/Code/Framework/AzFramework/AzFramework/azframework_files.cmake @@ -294,9 +294,6 @@ set(FILES Spawnable/SpawnableMonitor.cpp Spawnable/SpawnableSystemComponent.h Spawnable/SpawnableSystemComponent.cpp - Spawnable/SpawnedEntityTicketMapper.h - Spawnable/SpawnedEntityTicketMapper.cpp - Spawnable/SpawnedEntityTicketMapperInterface.h Terrain/TerrainDataRequestBus.h Terrain/TerrainDataRequestBus.cpp Thermal/ThermalInfo.h From 28c056e4fdb9e11bbe22c3b186fd12b279370bc2 Mon Sep 17 00:00:00 2001 From: srikappa-amzn Date: Thu, 23 Sep 2021 18:26:39 -0700 Subject: [PATCH 22/51] Added comments and changed some function names Signed-off-by: srikappa-amzn --- .../AzCore/AzCore/Component/Entity.cpp | 2 +- .../Entity/GameEntityContextComponent.cpp | 2 +- .../Spawnable/SpawnableEntitiesInterface.cpp | 4 +--- .../Spawnable/SpawnableEntitiesInterface.h | 9 ++++----- .../Spawnable/SpawnableEntitiesManager.cpp | 18 +++++++++++------- .../Spawnable/SpawnableEntitiesManager.h | 5 ++--- 6 files changed, 20 insertions(+), 20 deletions(-) diff --git a/Code/Framework/AzCore/AzCore/Component/Entity.cpp b/Code/Framework/AzCore/AzCore/Component/Entity.cpp index 4f55c360cd..4febd893b0 100644 --- a/Code/Framework/AzCore/AzCore/Component/Entity.cpp +++ b/Code/Framework/AzCore/AzCore/Component/Entity.cpp @@ -657,7 +657,7 @@ namespace AZ void Entity::SetSpawnTicketId(u32 spawnTicketId) { - m_spawnTicketId = AZStd::move(spawnTicketId); + m_spawnTicketId = spawnTicketId; } u32 Entity::GetSpawnTicketId() const diff --git a/Code/Framework/AzFramework/AzFramework/Entity/GameEntityContextComponent.cpp b/Code/Framework/AzFramework/AzFramework/Entity/GameEntityContextComponent.cpp index f2571c7549..aa7e03577a 100644 --- a/Code/Framework/AzFramework/AzFramework/Entity/GameEntityContextComponent.cpp +++ b/Code/Framework/AzFramework/AzFramework/Entity/GameEntityContextComponent.cpp @@ -294,7 +294,7 @@ namespace AzFramework { SpawnableEntitiesDefinition* spawnableEntitiesInterface = SpawnableEntitiesInterface::Get(); AZ_Assert(spawnableEntitiesInterface != nullptr, "SpawnableEntitiesInterface is not found."); - spawnableEntitiesInterface->GetEntitySpawnTicket( + spawnableEntitiesInterface->RetrieveEntitySpawnTicket( currentEntity->GetSpawnTicketId(), [spawnableEntitiesInterface, currentEntity](EntitySpawnTicket* entitySpawnTicket) { diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.cpp b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.cpp index 53bffd0d26..d915680760 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.cpp +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.cpp @@ -227,15 +227,13 @@ namespace AzFramework EntitySpawnTicket::EntitySpawnTicket(EntitySpawnTicket&& rhs) : m_payload(rhs.m_payload) + , m_id(rhs.m_id) { auto manager = SpawnableEntitiesInterface::Get(); AZ_Assert(manager, "SpawnableEntitiesInterface has no implementation."); rhs.m_payload = nullptr; - Id previousId = m_id; - m_id = rhs.m_id; rhs.m_id = 0; AZStd::scoped_lock lock(manager->m_entitySpawnTicketMapMutex); - manager->m_entitySpawnTicketMap.erase(previousId); manager->m_entitySpawnTicketMap.insert_or_assign(rhs.m_id, this); } diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.h b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.h index 91b2eafc54..3eea87b511 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.h +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.h @@ -162,7 +162,7 @@ namespace AzFramework using EntitySpawnCallback = AZStd::function; using EntityPreInsertionCallback = AZStd::function; using EntityDespawnCallback = AZStd::function; - using GetEntitySpawnTicketCallback = AZStd::function; + using RetrieveEntitySpawnTicketCallback = AZStd::function; using ReloadSpawnableCallback = AZStd::function; using ListEntitiesCallback = AZStd::function; using ListIndicesEntitiesCallback = AZStd::function; @@ -296,9 +296,8 @@ namespace AzFramework virtual void DespawnEntity(AZ::EntityId entityId, EntitySpawnTicket& ticket, DespawnEntityOptionalArgs optionalArgs = {}) = 0; //! Gets the EntitySpawnTicket associated with the entitySpawnTicketId. //! @param entitySpawnTicketId the id of EntitySpawnTicket to get. - //! @param getEntitySpawnTicketCallback The callback to execute upon fetching the ticket. - virtual void GetEntitySpawnTicket( - EntitySpawnTicket::Id entitySpawnTicketId, GetEntitySpawnTicketCallback getEntitySpawnTicketCallback) = 0; + //! @param callback The callback to execute upon retrieving the ticket. + virtual void RetrieveEntitySpawnTicket(EntitySpawnTicket::Id entitySpawnTicketId, RetrieveEntitySpawnTicketCallback callback) = 0; //! Removes all entities in the provided list from the environment and reconstructs the entities from the provided spawnable. //! @param ticket Holds the information on the entities to reload. //! @param priority The priority at which this call will be executed. @@ -368,7 +367,7 @@ namespace AzFramework } AZStd::unordered_map m_entitySpawnTicketMap; - AZStd::mutex m_entitySpawnTicketMapMutex; + AZStd::recursive_mutex m_entitySpawnTicketMapMutex; }; using SpawnableEntitiesInterface = AZ::Interface; diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp index c75a5fe834..ef7351aabb 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp @@ -96,22 +96,22 @@ namespace AzFramework QueueRequest(ticket, optionalArgs.m_priority, AZStd::move(queueEntry)); } - void SpawnableEntitiesManager::GetEntitySpawnTicket( - EntitySpawnTicket::Id entitySpawnTicketId, GetEntitySpawnTicketCallback getEntitySpawnTicketCallback) + void SpawnableEntitiesManager::RetrieveEntitySpawnTicket(EntitySpawnTicket::Id entitySpawnTicketId, RetrieveEntitySpawnTicketCallback callback) { if (entitySpawnTicketId == 0) { - AZ_Error("Spawnable", false, "Ticket id provided to GetEntitySpawnTicket is invalid."); + AZ_Assert(false, "Ticket id provided to RetrieveEntitySpawnTicket is invalid."); return; } + AZStd::scoped_lock lock(m_entitySpawnTicketMapMutex); auto entitySpawnTicketIterator = m_entitySpawnTicketMap.find(entitySpawnTicketId); if (entitySpawnTicketIterator == m_entitySpawnTicketMap.end()) { - AZ_Error("Spawnable", false, "The EntitySpawnTicket corresponding to id '%lu' cannot be found", entitySpawnTicketId); + AZ_Assert(false, "The EntitySpawnTicket corresponding to id '%lu' cannot be found", entitySpawnTicketId); return; } - getEntitySpawnTicketCallback(entitySpawnTicketIterator->second); + callback(entitySpawnTicketIterator->second); } void SpawnableEntitiesManager::ReloadSpawnable( @@ -369,8 +369,8 @@ namespace AzFramework // Add to the game context, now the entities are active for (auto it = ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount; it != ticket.m_spawnedEntities.end(); ++it) { - GameEntityContextRequestBus::Broadcast(&GameEntityContextRequestBus::Events::AddGameEntity, *it); (*it)->SetSpawnTicketId(request.m_ticketId); + GameEntityContextRequestBus::Broadcast(&GameEntityContextRequestBus::Events::AddGameEntity, *it); } // Let other systems know about newly spawned entities for any post-processing after adding to the scene/game context. @@ -451,8 +451,8 @@ namespace AzFramework // Add to the game context, now the entities are active for (auto it = ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount; it != ticket.m_spawnedEntities.end(); ++it) { - GameEntityContextRequestBus::Broadcast(&GameEntityContextRequestBus::Events::AddGameEntity, *it); (*it)->SetSpawnTicketId(request.m_ticketId); + GameEntityContextRequestBus::Broadcast(&GameEntityContextRequestBus::Events::AddGameEntity, *it); } if (request.m_completionCallback) @@ -479,6 +479,7 @@ namespace AzFramework { if (entity != nullptr) { + // Setting it to 0 is needed to avoid the infite loop between GameEntityContext and SpawnableEntitiesManager. entity->SetSpawnTicketId(0); GameEntityContextRequestBus::Broadcast( &GameEntityContextRequestBus::Events::DestroyGameEntity, entity->GetId()); @@ -512,6 +513,7 @@ namespace AzFramework { if (*entityIterator != nullptr && (*entityIterator)->GetId() == request.m_entityId) { + // Setting it to 0 is needed to avoid the infite loop between GameEntityContext and SpawnableEntitiesManager. (*entityIterator)->SetSpawnTicketId(0); GameEntityContextRequestBus::Broadcast( &GameEntityContextRequestBus::Events::DestroyGameEntity, (*entityIterator)->GetId()); @@ -548,6 +550,7 @@ namespace AzFramework { if (entity != nullptr) { + // Setting it to 0 is needed to avoid the infite loop between GameEntityContext and SpawnableEntitiesManager. entity->SetSpawnTicketId(0); GameEntityContextRequestBus::Broadcast( &GameEntityContextRequestBus::Events::DestroyGameEntity, entity->GetId()); @@ -703,6 +706,7 @@ namespace AzFramework { if (entity != nullptr) { + // Setting it to 0 is needed to avoid the infite loop between GameEntityContext and SpawnableEntitiesManager. entity->SetSpawnTicketId(0); GameEntityContextRequestBus::Broadcast( &GameEntityContextRequestBus::Events::DestroyGameEntity, entity->GetId()); diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h index 5445360d64..c3de5be003 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h @@ -58,8 +58,7 @@ namespace AzFramework EntitySpawnTicket& ticket, AZStd::vector entityIndices, SpawnEntitiesOptionalArgs optionalArgs = {}) override; void DespawnAllEntities(EntitySpawnTicket& ticket, DespawnAllEntitiesOptionalArgs optionalArgs = {}) override; void DespawnEntity(AZ::EntityId entityId, EntitySpawnTicket& ticket, DespawnEntityOptionalArgs optionalArgs = {}) override; - void GetEntitySpawnTicket( - EntitySpawnTicket::Id entitySpawnTicketId, GetEntitySpawnTicketCallback getEntitySpawnTicketCallback) override; + void RetrieveEntitySpawnTicket(EntitySpawnTicket::Id entitySpawnTicketId, RetrieveEntitySpawnTicketCallback callback) override; void ReloadSpawnable( EntitySpawnTicket& ticket, AZ::Data::Asset spawnable, ReloadSpawnableOptionalArgs optionalArgs = {}) override; @@ -139,9 +138,9 @@ namespace AzFramework { EntityDespawnCallback m_completionCallback; Ticket* m_ticket; + AZ::EntityId m_entityId; EntitySpawnTicket::Id m_ticketId; uint32_t m_requestId; - AZ::EntityId m_entityId; }; struct ReloadSpawnableCommand { From 5114c2e0f23597001401f9d83912a0327978e2bf Mon Sep 17 00:00:00 2001 From: puvvadar Date: Fri, 24 Sep 2021 09:49:46 -0700 Subject: [PATCH 23/51] Move AutoComponent jinjas to Include and update cmake accordingly Signed-off-by: puvvadar --- .../AutoGen/AutoComponentTypes_Header.jinja | 0 .../AutoGen/AutoComponentTypes_Source.jinja | 0 .../Multiplayer}/AutoGen/AutoComponent_Common.jinja | 0 .../Multiplayer}/AutoGen/AutoComponent_Header.jinja | 0 .../Multiplayer}/AutoGen/AutoComponent_Source.jinja | 0 Gems/Multiplayer/Code/multiplayer_files.cmake | 10 +++++----- 6 files changed, 5 insertions(+), 5 deletions(-) rename Gems/Multiplayer/Code/{Source => Include/Multiplayer}/AutoGen/AutoComponentTypes_Header.jinja (100%) rename Gems/Multiplayer/Code/{Source => Include/Multiplayer}/AutoGen/AutoComponentTypes_Source.jinja (100%) rename Gems/Multiplayer/Code/{Source => Include/Multiplayer}/AutoGen/AutoComponent_Common.jinja (100%) rename Gems/Multiplayer/Code/{Source => Include/Multiplayer}/AutoGen/AutoComponent_Header.jinja (100%) rename Gems/Multiplayer/Code/{Source => Include/Multiplayer}/AutoGen/AutoComponent_Source.jinja (100%) diff --git a/Gems/Multiplayer/Code/Source/AutoGen/AutoComponentTypes_Header.jinja b/Gems/Multiplayer/Code/Include/Multiplayer/AutoGen/AutoComponentTypes_Header.jinja similarity index 100% rename from Gems/Multiplayer/Code/Source/AutoGen/AutoComponentTypes_Header.jinja rename to Gems/Multiplayer/Code/Include/Multiplayer/AutoGen/AutoComponentTypes_Header.jinja diff --git a/Gems/Multiplayer/Code/Source/AutoGen/AutoComponentTypes_Source.jinja b/Gems/Multiplayer/Code/Include/Multiplayer/AutoGen/AutoComponentTypes_Source.jinja similarity index 100% rename from Gems/Multiplayer/Code/Source/AutoGen/AutoComponentTypes_Source.jinja rename to Gems/Multiplayer/Code/Include/Multiplayer/AutoGen/AutoComponentTypes_Source.jinja diff --git a/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Common.jinja b/Gems/Multiplayer/Code/Include/Multiplayer/AutoGen/AutoComponent_Common.jinja similarity index 100% rename from Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Common.jinja rename to Gems/Multiplayer/Code/Include/Multiplayer/AutoGen/AutoComponent_Common.jinja diff --git a/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Header.jinja b/Gems/Multiplayer/Code/Include/Multiplayer/AutoGen/AutoComponent_Header.jinja similarity index 100% rename from Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Header.jinja rename to Gems/Multiplayer/Code/Include/Multiplayer/AutoGen/AutoComponent_Header.jinja diff --git a/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Source.jinja b/Gems/Multiplayer/Code/Include/Multiplayer/AutoGen/AutoComponent_Source.jinja similarity index 100% rename from Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Source.jinja rename to Gems/Multiplayer/Code/Include/Multiplayer/AutoGen/AutoComponent_Source.jinja diff --git a/Gems/Multiplayer/Code/multiplayer_files.cmake b/Gems/Multiplayer/Code/multiplayer_files.cmake index 3516255c7d..7782444a6e 100644 --- a/Gems/Multiplayer/Code/multiplayer_files.cmake +++ b/Gems/Multiplayer/Code/multiplayer_files.cmake @@ -51,11 +51,11 @@ set(FILES Include/Multiplayer/NetworkTime/RewindableObject.inl Include/Multiplayer/Physics/PhysicsUtils.h Include/Multiplayer/ReplicationWindows/IReplicationWindow.h - Source/AutoGen/AutoComponentTypes_Header.jinja - Source/AutoGen/AutoComponentTypes_Source.jinja - Source/AutoGen/AutoComponent_Common.jinja - Source/AutoGen/AutoComponent_Header.jinja - Source/AutoGen/AutoComponent_Source.jinja + Include/Multiplayer/AutoGen/AutoComponentTypes_Header.jinja + Include/Multiplayer/AutoGen/AutoComponentTypes_Source.jinja + Include/Multiplayer/AutoGen/AutoComponent_Common.jinja + Include/Multiplayer/AutoGen/AutoComponent_Header.jinja + Include/Multiplayer/AutoGen/AutoComponent_Source.jinja Source/AutoGen/LocalPredictionPlayerInputComponent.AutoComponent.xml Source/AutoGen/Multiplayer.AutoPackets.xml Source/AutoGen/MultiplayerEditor.AutoPackets.xml From dcc325dfaa826b803d2d865fa0bf529dc5981471 Mon Sep 17 00:00:00 2001 From: srikappa-amzn Date: Fri, 24 Sep 2021 12:16:38 -0700 Subject: [PATCH 24/51] Added function comments for new entity functions Signed-off-by: srikappa-amzn --- Code/Framework/AzCore/AzCore/Component/Entity.h | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Code/Framework/AzCore/AzCore/Component/Entity.h b/Code/Framework/AzCore/AzCore/Component/Entity.h index 97cec50946..3887b2e03e 100644 --- a/Code/Framework/AzCore/AzCore/Component/Entity.h +++ b/Code/Framework/AzCore/AzCore/Component/Entity.h @@ -133,8 +133,13 @@ namespace AZ //! @return The state of the entity. For example, the entity has been initialized, the entity is active, and so on. State GetState() const { return m_state; } + //! Gets the ticket id used to spawn the entity. + //! @return the ticket id used to spawn the entity. If entity is not spawned, the id will be 0. u32 GetSpawnTicketId() const; - void SetSpawnTicketId(u32); + + //! Sets the ticket id used to spawn the entity. The ticket id in the entity will remain 0 unless it's set using this function. + //! @param spawnTicketId the ticket id used to spawn the entity. + void SetSpawnTicketId(u32 spawnTicketId); //! Connects an entity state event handler to the entity. //! All state changes will be signaled through this event. From c14133e926baa2ff27a555599d1ae1bcae82de19 Mon Sep 17 00:00:00 2001 From: chcurran Date: Fri, 24 Sep 2021 14:34:03 -0700 Subject: [PATCH 25/51] Remove false error for lookup on methods, that occurs during (de)serialization Signed-off-by: chcurran <82187351+carlitosan@users.noreply.github.com> --- .../Include/ScriptCanvas/Libraries/Core/Method.cpp | 14 +++++++++----- .../Include/ScriptCanvas/Libraries/Core/Method.h | 2 +- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Core/Method.cpp b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Core/Method.cpp index 7e3f5f7d24..ca57824b78 100644 --- a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Core/Method.cpp +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Core/Method.cpp @@ -652,8 +652,13 @@ namespace ScriptCanvas return {}; } - bool Method::GetBehaviorContextClassMethod(const AZStd::string&, const AZ::BehaviorClass*& outClass, const AZ::BehaviorMethod*& outMethod, EventType& outType) const + bool Method::GetBehaviorContextClassMethod(const AZ::BehaviorClass*& outClass, const AZ::BehaviorMethod*& outMethod, EventType& outType) const { + if (m_lookupName.empty() && m_className.empty()) + { + return false; + } + AZStd::string prettyClassName; AZStd::string methodName = m_lookupName; @@ -749,15 +754,14 @@ namespace ScriptCanvas AZStd::tuple Method::LookupMethod() const { using TupleType = AZStd::tuple; - AZStd::string methodName = m_lookupName; - + AZStd::string prettyClassName; const AZ::BehaviorClass* bcClass{}; const AZ::BehaviorMethod* method{}; EventType eventType; - if (GetBehaviorContextClassMethod(m_lookupName, bcClass, method, eventType)) + if (GetBehaviorContextClassMethod(bcClass, method, eventType)) { return TupleType{ method, m_methodType, eventType, bcClass }; } @@ -776,7 +780,7 @@ namespace ScriptCanvas const AZ::BehaviorMethod* method{}; EventType eventType; - if (GetBehaviorContextClassMethod(m_lookupName, bcClass, method, eventType)) + if (GetBehaviorContextClassMethod(bcClass, method, eventType)) { m_eventType = eventType; ConfigureMethod(*method, bcClass); diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Core/Method.h b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Core/Method.h index 1f1daddc71..8a8dc09856 100644 --- a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Core/Method.h +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Core/Method.h @@ -168,7 +168,7 @@ namespace ScriptCanvas AZ_INLINE void SetWarnOnMissingFunction(bool enabled) { m_warnOnMissingFunction = enabled; } - bool GetBehaviorContextClassMethod(const AZStd::string& name, const AZ::BehaviorClass*& outClass, const AZ::BehaviorMethod*& outMethod, EventType& outType) const; + bool GetBehaviorContextClassMethod(const AZ::BehaviorClass*& outClass, const AZ::BehaviorMethod*& outMethod, EventType& outType) const; private: friend struct ScriptCanvas::BehaviorContextMethodHelper; From 72110e40cfa4989afd20cb5f6e7598b73bb1fd18 Mon Sep 17 00:00:00 2001 From: Danilo Aimini <82231674+AMZN-daimini@users.noreply.github.com> Date: Fri, 24 Sep 2021 15:05:34 -0700 Subject: [PATCH 26/51] Add Unit tests for EditorFocusMode and PrefabFocus. Minor change to PrefabFocusHandler to avoid instantiating a new AZ::EntityId() for checks. Signed-off-by: Danilo Aimini <82231674+AMZN-daimini@users.noreply.github.com> --- .../Prefab/PrefabFocusHandler.cpp | 4 +- .../Tests/FocusMode/EditorFocusModeTests.cpp | 128 ++++++++++++ .../Prefab/PrefabFocus/PrefabFocusTests.cpp | 185 ++++++++++++++++++ .../Tests/aztoolsframeworktests_files.cmake | 2 + 4 files changed, 317 insertions(+), 2 deletions(-) create mode 100644 Code/Framework/AzToolsFramework/Tests/FocusMode/EditorFocusModeTests.cpp create mode 100644 Code/Framework/AzToolsFramework/Tests/Prefab/PrefabFocus/PrefabFocusTests.cpp diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.cpp index 4fd202a73d..1ad3e5c90a 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.cpp @@ -35,7 +35,7 @@ namespace AzToolsFramework::Prefab { InstanceOptionalReference focusedInstance; - if (entityId == AZ::EntityId()) + if (entityId == static_cast(AZ::EntityId::InvalidEntityId)) { PrefabEditorEntityOwnershipInterface* prefabEditorEntityOwnershipInterface = AZ::Interface::Get(); @@ -89,7 +89,7 @@ namespace AzToolsFramework::Prefab return false; } - if (entityId == AZ::EntityId()) + if (entityId == static_cast(AZ::EntityId::InvalidEntityId)) { return false; } diff --git a/Code/Framework/AzToolsFramework/Tests/FocusMode/EditorFocusModeTests.cpp b/Code/Framework/AzToolsFramework/Tests/FocusMode/EditorFocusModeTests.cpp new file mode 100644 index 0000000000..5972da58a7 --- /dev/null +++ b/Code/Framework/AzToolsFramework/Tests/FocusMode/EditorFocusModeTests.cpp @@ -0,0 +1,128 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include +#include +#include +#include + +namespace AzToolsFramework +{ + class EditorFocusModeTests + : public ::testing::Test + { + protected: + void SetUp() override + { + m_app.Start(m_descriptor); + + // Without this, the user settings component would attempt to save on finalize/shutdown. Since the file is + // shared across the whole engine, if multiple tests are run in parallel, the saving could cause a crash + // in the unit tests. + AZ::UserSettingsComponentRequestBus::Broadcast(&AZ::UserSettingsComponentRequests::DisableSaveOnFinalize); + + GenerateTestHierarchy(); + } + + void GenerateTestHierarchy() + { + /* + * City + * |_ Street + * |_ Car + * | |_ Passenger + * |_ SportsCar + * |_ Passenger + */ + + m_entityMap["cityId"] = CreateEditorEntity("City", AZ::EntityId()); + m_entityMap["streetId"] = CreateEditorEntity("Street", m_entityMap["cityId"]); + m_entityMap["carId"] = CreateEditorEntity("Car", m_entityMap["streetId"]); + m_entityMap["passengerId1"] = CreateEditorEntity("Passenger", m_entityMap["carId"]); + m_entityMap["sportsCarId"] = CreateEditorEntity("SportsCar", m_entityMap["streetId"]); + m_entityMap["passengerId2"] = CreateEditorEntity("Passenger", m_entityMap["sportsCarId"]); + } + + AZ::EntityId CreateEditorEntity(const char* name, AZ::EntityId parentId) + { + AZ::Entity* entity = nullptr; + UnitTest::CreateDefaultEditorEntity(name, &entity); + + // Parent + AZ::TransformBus::Event(entity->GetId(), &AZ::TransformInterface::SetParent, parentId); + + return entity->GetId(); + } + + void TearDown() override + { + m_app.Stop(); + } + + UnitTest::ToolsTestApplication m_app{ "EditorFocusModeTests" }; + AZ::ComponentApplication::Descriptor m_descriptor; + AZStd::unordered_map m_entityMap; + }; + + TEST_F(EditorFocusModeTests, EditorFocusModeTests_SetFocus) + { + FocusModeInterface* focusModeInterface = AZ::Interface::Get(); + EXPECT_TRUE(focusModeInterface != nullptr); + + focusModeInterface->SetFocusRoot(m_entityMap["carId"]); + EXPECT_EQ(focusModeInterface->GetFocusRoot(), m_entityMap["carId"]); + + focusModeInterface->ClearFocusRoot(); + EXPECT_EQ(focusModeInterface->GetFocusRoot(), AZ::EntityId()); + } + + TEST_F(EditorFocusModeTests, EditorFocusModeTests_IsInFocusSubTree) + { + FocusModeInterface* focusModeInterface = AZ::Interface::Get(); + EXPECT_TRUE(focusModeInterface != nullptr); + + focusModeInterface->ClearFocusRoot(); + + EXPECT_EQ(focusModeInterface->IsInFocusSubTree(m_entityMap["cityId"]), true); + EXPECT_EQ(focusModeInterface->IsInFocusSubTree(m_entityMap["streetId"]), true); + EXPECT_EQ(focusModeInterface->IsInFocusSubTree(m_entityMap["carId"]), true); + EXPECT_EQ(focusModeInterface->IsInFocusSubTree(m_entityMap["passengerId1"]), true); + EXPECT_EQ(focusModeInterface->IsInFocusSubTree(m_entityMap["sportsCarId"]), true); + EXPECT_EQ(focusModeInterface->IsInFocusSubTree(m_entityMap["passengerId2"]), true); + + focusModeInterface->SetFocusRoot(m_entityMap["streetId"]); + + EXPECT_EQ(focusModeInterface->IsInFocusSubTree(m_entityMap["cityId"]), false); + EXPECT_EQ(focusModeInterface->IsInFocusSubTree(m_entityMap["streetId"]), true); + EXPECT_EQ(focusModeInterface->IsInFocusSubTree(m_entityMap["carId"]), true); + EXPECT_EQ(focusModeInterface->IsInFocusSubTree(m_entityMap["passengerId1"]), true); + EXPECT_EQ(focusModeInterface->IsInFocusSubTree(m_entityMap["sportsCarId"]), true); + EXPECT_EQ(focusModeInterface->IsInFocusSubTree(m_entityMap["passengerId2"]), true); + + focusModeInterface->SetFocusRoot(m_entityMap["carId"]); + + EXPECT_EQ(focusModeInterface->IsInFocusSubTree(m_entityMap["cityId"]), false); + EXPECT_EQ(focusModeInterface->IsInFocusSubTree(m_entityMap["streetId"]), false); + EXPECT_EQ(focusModeInterface->IsInFocusSubTree(m_entityMap["carId"]), true); + EXPECT_EQ(focusModeInterface->IsInFocusSubTree(m_entityMap["passengerId1"]), true); + EXPECT_EQ(focusModeInterface->IsInFocusSubTree(m_entityMap["sportsCarId"]), false); + EXPECT_EQ(focusModeInterface->IsInFocusSubTree(m_entityMap["passengerId2"]), false); + + focusModeInterface->SetFocusRoot(m_entityMap["passengerId2"]); + + EXPECT_EQ(focusModeInterface->IsInFocusSubTree(m_entityMap["cityId"]), false); + EXPECT_EQ(focusModeInterface->IsInFocusSubTree(m_entityMap["streetId"]), false); + EXPECT_EQ(focusModeInterface->IsInFocusSubTree(m_entityMap["carId"]), false); + EXPECT_EQ(focusModeInterface->IsInFocusSubTree(m_entityMap["passengerId1"]), false); + EXPECT_EQ(focusModeInterface->IsInFocusSubTree(m_entityMap["sportsCarId"]), false); + EXPECT_EQ(focusModeInterface->IsInFocusSubTree(m_entityMap["passengerId2"]), true); + + focusModeInterface->ClearFocusRoot(); + } +} diff --git a/Code/Framework/AzToolsFramework/Tests/Prefab/PrefabFocus/PrefabFocusTests.cpp b/Code/Framework/AzToolsFramework/Tests/Prefab/PrefabFocus/PrefabFocusTests.cpp new file mode 100644 index 0000000000..a9f38425d5 --- /dev/null +++ b/Code/Framework/AzToolsFramework/Tests/Prefab/PrefabFocus/PrefabFocusTests.cpp @@ -0,0 +1,185 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include +#include +#include +#include + +namespace UnitTest +{ + class PrefabFocusTests + : public PrefabTestFixture + { + protected: + void GenerateTestHierarchy() + { + /* + * City (Prefab Container) + * |_ City + * |_ Street (Prefab Container) + * |_ Car (Prefab Container) + * | |_ Passenger + * |_ SportsCar (Prefab Container) + * |_ Passenger + */ + + m_entityMap["passenger1"] = CreateEntity("Passenger1"); + m_entityMap["passenger2"] = CreateEntity("Passenger2"); + m_entityMap["city"] = CreateEntity("City"); + + AzToolsFramework::EditorEntityContextRequestBus::Broadcast( + &AzToolsFramework::EditorEntityContextRequests::HandleEntitiesAdded, + AzToolsFramework::EntityList{ m_entityMap["passenger1"], m_entityMap["passenger2"], m_entityMap["city"] }); + + AZStd::unique_ptr carInstance = + m_prefabSystemComponent->CreatePrefab({ m_entityMap["passenger1"] }, {}, "test/car"); + ASSERT_TRUE(carInstance); + m_instanceMap["car"] = carInstance.get(); + + AZStd::unique_ptr sportsCarInstance = + m_prefabSystemComponent->CreatePrefab({ m_entityMap["passenger2"] }, {}, "test/sportsCar"); + ASSERT_TRUE(sportsCarInstance); + m_instanceMap["sportsCar"] = sportsCarInstance.get(); + + AZStd::unique_ptr streetInstance = + m_prefabSystemComponent->CreatePrefab({}, MakeInstanceList( AZStd::move(carInstance), AZStd::move(sportsCarInstance) ), "test/street"); + ASSERT_TRUE(streetInstance); + m_instanceMap["street"] = streetInstance.get(); + + m_rootInstance = + m_prefabSystemComponent->CreatePrefab({ m_entityMap["city"] }, MakeInstanceList(AZStd::move(streetInstance)), "test/city"); + ASSERT_TRUE(m_rootInstance); + m_instanceMap["city"] = m_rootInstance.get(); + } + + AZ::EntityId CreateEditorEntity(const char* name, AZ::EntityId parentId) + { + AZ::Entity* newEntity = CreateEntity(name); + AzToolsFramework::EditorEntityContextRequestBus::Broadcast( + &AzToolsFramework::EditorEntityContextRequests::HandleEntitiesAdded, AzToolsFramework::EntityList{ newEntity }); + + // Parent + AZ::TransformBus::Event(newEntity->GetId(), &AZ::TransformInterface::SetParent, parentId); + + return newEntity->GetId(); + } + + AZStd::unordered_map m_entityMap; + AZStd::unordered_map m_instanceMap; + + AZStd::unique_ptr m_rootInstance; + }; + + TEST_F(PrefabFocusTests, PrefabFocus_FocusOnOwningPrefab) + { + GenerateTestHierarchy(); + + PrefabFocusInterface* prefabFocusInterface = AZ::Interface::Get(); + EXPECT_TRUE(prefabFocusInterface != nullptr); + + // Verify FocusOnOwningPrefab works when passing the container entity of the root prefab. + { + prefabFocusInterface->FocusOnOwningPrefab(m_instanceMap["city"]->GetContainerEntityId()); + EXPECT_EQ(prefabFocusInterface->GetFocusedPrefabTemplateId(), m_instanceMap["city"]->GetTemplateId()); + + auto instance = prefabFocusInterface->GetFocusedPrefabInstance(); + EXPECT_TRUE(instance.has_value()); + EXPECT_EQ(&instance->get(), m_instanceMap["city"]); + } + + // Verify FocusOnOwningPrefab works when passing a nested entity of the root prefab. + { + prefabFocusInterface->FocusOnOwningPrefab(m_entityMap["city"]->GetId()); + EXPECT_EQ(prefabFocusInterface->GetFocusedPrefabTemplateId(), m_instanceMap["city"]->GetTemplateId()); + + auto instance = prefabFocusInterface->GetFocusedPrefabInstance(); + EXPECT_TRUE(instance.has_value()); + EXPECT_EQ(&instance->get(), m_instanceMap["city"]); + } + + // Verify FocusOnOwningPrefab works when passing the container entity of a nested prefab. + { + prefabFocusInterface->FocusOnOwningPrefab(m_instanceMap["car"]->GetContainerEntityId()); + EXPECT_EQ(prefabFocusInterface->GetFocusedPrefabTemplateId(), m_instanceMap["car"]->GetTemplateId()); + + auto instance = prefabFocusInterface->GetFocusedPrefabInstance(); + EXPECT_TRUE(instance.has_value()); + EXPECT_EQ(&instance->get(), m_instanceMap["car"]); + } + + // Verify FocusOnOwningPrefab works when passing a nested entity of the a nested prefab. + { + prefabFocusInterface->FocusOnOwningPrefab(m_entityMap["passenger1"]->GetId()); + EXPECT_EQ(prefabFocusInterface->GetFocusedPrefabTemplateId(), m_instanceMap["car"]->GetTemplateId()); + + auto instance = prefabFocusInterface->GetFocusedPrefabInstance(); + EXPECT_TRUE(instance.has_value()); + EXPECT_EQ(&instance->get(), m_instanceMap["car"]); + } + + // Verify FocusOnOwningPrefab points to the root prefab when the focus is cleared. + { + AzToolsFramework::PrefabEditorEntityOwnershipInterface* prefabEditorEntityOwnershipInterface = + AZ::Interface::Get(); + AzToolsFramework::Prefab::InstanceOptionalReference rootPrefabInstance = + prefabEditorEntityOwnershipInterface->GetRootPrefabInstance(); + EXPECT_TRUE(rootPrefabInstance.has_value()); + + prefabFocusInterface->FocusOnOwningPrefab(AZ::EntityId()); + EXPECT_EQ(prefabFocusInterface->GetFocusedPrefabTemplateId(), rootPrefabInstance->get().GetTemplateId()); + + auto instance = prefabFocusInterface->GetFocusedPrefabInstance(); + EXPECT_TRUE(instance.has_value()); + EXPECT_EQ(&instance->get(), &rootPrefabInstance->get()); + } + + m_rootInstance.release(); + } + + TEST_F(PrefabFocusTests, PrefabFocus_IsOwningPrefabBeingFocused) + { + GenerateTestHierarchy(); + + PrefabFocusInterface* prefabFocusInterface = AZ::Interface::Get(); + EXPECT_TRUE(prefabFocusInterface != nullptr); + + // Verify IsOwningPrefabBeingFocused returns true for all entities in a focused prefab (container/nested) + { + prefabFocusInterface->FocusOnOwningPrefab(m_instanceMap["city"]->GetContainerEntityId()); + + EXPECT_TRUE(prefabFocusInterface->IsOwningPrefabBeingFocused(m_instanceMap["city"]->GetContainerEntityId())); + EXPECT_TRUE(prefabFocusInterface->IsOwningPrefabBeingFocused(m_entityMap["city"]->GetId())); + } + + // Verify IsOwningPrefabBeingFocused returns false for all entities not in a focused prefab (ancestors/descendants) + { + prefabFocusInterface->FocusOnOwningPrefab(m_instanceMap["street"]->GetContainerEntityId()); + + EXPECT_TRUE(prefabFocusInterface->IsOwningPrefabBeingFocused(m_instanceMap["street"]->GetContainerEntityId())); + EXPECT_FALSE(prefabFocusInterface->IsOwningPrefabBeingFocused(m_instanceMap["city"]->GetContainerEntityId())); + EXPECT_FALSE(prefabFocusInterface->IsOwningPrefabBeingFocused(m_entityMap["city"]->GetId())); + EXPECT_FALSE(prefabFocusInterface->IsOwningPrefabBeingFocused(m_instanceMap["car"]->GetContainerEntityId())); + EXPECT_FALSE(prefabFocusInterface->IsOwningPrefabBeingFocused(m_entityMap["passenger1"]->GetId())); + } + + // Verify IsOwningPrefabBeingFocused returns false for all entities not in a focused prefab (siblings) + { + prefabFocusInterface->FocusOnOwningPrefab(m_instanceMap["sportsCar"]->GetContainerEntityId()); + + EXPECT_TRUE(prefabFocusInterface->IsOwningPrefabBeingFocused(m_instanceMap["sportsCar"]->GetContainerEntityId())); + EXPECT_TRUE(prefabFocusInterface->IsOwningPrefabBeingFocused(m_entityMap["passenger2"]->GetId())); + EXPECT_FALSE(prefabFocusInterface->IsOwningPrefabBeingFocused(m_instanceMap["car"]->GetContainerEntityId())); + EXPECT_FALSE(prefabFocusInterface->IsOwningPrefabBeingFocused(m_entityMap["passenger1"]->GetId())); + } + + m_rootInstance.release(); + } + +} diff --git a/Code/Framework/AzToolsFramework/Tests/aztoolsframeworktests_files.cmake b/Code/Framework/AzToolsFramework/Tests/aztoolsframeworktests_files.cmake index 764afce266..ecc7d17ccc 100644 --- a/Code/Framework/AzToolsFramework/Tests/aztoolsframeworktests_files.cmake +++ b/Code/Framework/AzToolsFramework/Tests/aztoolsframeworktests_files.cmake @@ -34,6 +34,7 @@ set(FILES EntityTestbed.h FileFunc.cpp FingerprintingTests.cpp + FocusMode/EditorFocusModeTests.cpp GenericComponentWrapperTest.cpp InstanceDataHierarchy.cpp IntegerPrimtitiveTestConfig.h @@ -50,6 +51,7 @@ set(FILES Prefab/Benchmark/PrefabLoadBenchmarks.cpp Prefab/Benchmark/PrefabUpdateInstancesBenchmarks.cpp Prefab/Benchmark/SpawnableCreateBenchmarks.cpp + Prefab/PrefabFocus/PrefabFocusTests.cpp Prefab/MockPrefabFileIOActionValidator.cpp Prefab/MockPrefabFileIOActionValidator.h Prefab/PrefabDuplicateTests.cpp From 80e6a6903a94ec38ba335acfd1b898ca43f06517 Mon Sep 17 00:00:00 2001 From: Danilo Aimini <82231674+AMZN-daimini@users.noreply.github.com> Date: Fri, 24 Sep 2021 15:55:02 -0700 Subject: [PATCH 27/51] Remove unused CreateEditorEntity function Signed-off-by: Danilo Aimini <82231674+AMZN-daimini@users.noreply.github.com> --- .../Tests/Prefab/PrefabFocus/PrefabFocusTests.cpp | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/Code/Framework/AzToolsFramework/Tests/Prefab/PrefabFocus/PrefabFocusTests.cpp b/Code/Framework/AzToolsFramework/Tests/Prefab/PrefabFocus/PrefabFocusTests.cpp index a9f38425d5..ffd6cd2a44 100644 --- a/Code/Framework/AzToolsFramework/Tests/Prefab/PrefabFocus/PrefabFocusTests.cpp +++ b/Code/Framework/AzToolsFramework/Tests/Prefab/PrefabFocus/PrefabFocusTests.cpp @@ -59,18 +59,6 @@ namespace UnitTest m_instanceMap["city"] = m_rootInstance.get(); } - AZ::EntityId CreateEditorEntity(const char* name, AZ::EntityId parentId) - { - AZ::Entity* newEntity = CreateEntity(name); - AzToolsFramework::EditorEntityContextRequestBus::Broadcast( - &AzToolsFramework::EditorEntityContextRequests::HandleEntitiesAdded, AzToolsFramework::EntityList{ newEntity }); - - // Parent - AZ::TransformBus::Event(newEntity->GetId(), &AZ::TransformInterface::SetParent, parentId); - - return newEntity->GetId(); - } - AZStd::unordered_map m_entityMap; AZStd::unordered_map m_instanceMap; From f7e964c91d9b137edffca7a3978ab698b1a14715 Mon Sep 17 00:00:00 2001 From: Danilo Aimini <82231674+AMZN-daimini@users.noreply.github.com> Date: Fri, 24 Sep 2021 16:35:48 -0700 Subject: [PATCH 28/51] Better replacement for EntityId checks Signed-off-by: Danilo Aimini <82231674+AMZN-daimini@users.noreply.github.com> --- .../AzToolsFramework/Prefab/PrefabFocusHandler.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.cpp index 1ad3e5c90a..d69d0004cc 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.cpp @@ -35,7 +35,7 @@ namespace AzToolsFramework::Prefab { InstanceOptionalReference focusedInstance; - if (entityId == static_cast(AZ::EntityId::InvalidEntityId)) + if (!entityId.IsValid()) { PrefabEditorEntityOwnershipInterface* prefabEditorEntityOwnershipInterface = AZ::Interface::Get(); @@ -89,7 +89,7 @@ namespace AzToolsFramework::Prefab return false; } - if (entityId == static_cast(AZ::EntityId::InvalidEntityId)) + if (!entityId.IsValid()) { return false; } From a3cc8845dd12d6fa8de34322e7d10c9fd21c23fe Mon Sep 17 00:00:00 2001 From: Jeremy Ong Date: Mon, 27 Sep 2021 01:21:31 -0600 Subject: [PATCH 29/51] Disable Vulkan PDB generation and fix D3D12 PDBs Vulkan PDBs are currently broken because debug data isn't stripped from the shader binary. Furthermore, sub-id hashing is broken because sub-ids count from 1 which generates a collision on the first sub-object. Changing the default value for the sub-id argument resolves this. Signed-off-by: Jeremy Ong --- .../Code/Source/RHI.Edit/ShaderCompilerArguments.cpp | 5 ----- .../Source/RHI.Builders/ShaderPlatformInterface.cpp | 12 ++++++++---- .../Atom/RPI.Reflect/Shader/ShaderVariantAsset.h | 2 +- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/Gems/Atom/RHI/Code/Source/RHI.Edit/ShaderCompilerArguments.cpp b/Gems/Atom/RHI/Code/Source/RHI.Edit/ShaderCompilerArguments.cpp index ab45d2239b..ef330e7902 100644 --- a/Gems/Atom/RHI/Code/Source/RHI.Edit/ShaderCompilerArguments.cpp +++ b/Gems/Atom/RHI/Code/Source/RHI.Edit/ShaderCompilerArguments.cpp @@ -155,11 +155,6 @@ namespace AZ { arguments += " -Zpr"; } - if (m_dxcGenerateDebugInfo) - { - arguments += " -Zi"; // Generate debug information - arguments += " -Zss"; // Compute Shader Hash considering source information - } // strip spaces at both sides AZStd::string dxcAdditionalFreeArguments = m_dxcAdditionalFreeArguments; AzFramework::StringFunc::TrimWhiteSpace(dxcAdditionalFreeArguments, true, true); diff --git a/Gems/Atom/RHI/DX12/Code/Source/RHI.Builders/ShaderPlatformInterface.cpp b/Gems/Atom/RHI/DX12/Code/Source/RHI.Builders/ShaderPlatformInterface.cpp index 89ae3ede46..f30fc72ace 100644 --- a/Gems/Atom/RHI/DX12/Code/Source/RHI.Builders/ShaderPlatformInterface.cpp +++ b/Gems/Atom/RHI/DX12/Code/Source/RHI.Builders/ShaderPlatformInterface.cpp @@ -255,6 +255,11 @@ namespace AZ // Compilation parameters AZStd::string params = shaderCompilerArguments.MakeAdditionalDxcCommandLineString(); + if (BuildHasDebugInfo(shaderCompilerArguments)) + { + params += " -Zi"; // Generate debug information + params += " -Zss"; // Compute Shader Hash considering source information + } // Enable half precision types when shader model >= 6.2 int shaderModelMajor = 0; @@ -281,12 +286,11 @@ namespace AZ AZStd::string symbolDatabaseFileCliArgument{" "}; // when not debug: still insert a space between 5.dxil and 7.hlsl-in if (BuildHasDebugInfo(shaderCompilerArguments)) { - // prepare .ldd filename: + // prepare .pdb filename: AZStd::string md5hex = RHI::ByteToHexString(md5); AZStd::string symbolDatabaseFilePath = dxcInputFile.c_str(); // mutate from source - AZStd::string lldFileName = md5hex // lld is like pdb but it's the default symbol database extension in dxc - + "-" + profileIt->second; // concatenate the shader profile to disambiguate vs/ps... - AzFramework::StringFunc::Path::ReplaceFullName(symbolDatabaseFilePath, lldFileName.c_str(), "lld"); + AZStd::string pdbFileName = md5hex + "-" + profileIt->second; // concatenate the shader profile to disambiguate vs/ps... + AzFramework::StringFunc::Path::ReplaceFullName(symbolDatabaseFilePath, pdbFileName.c_str(), "pdb"); // it is possible that another activated platform/profile, already exported that file. (since it's hashed on the source file) // dxc returns an error in such case. we get less surprising effets by just not mentionning an -Fd argument if (AZ::IO::SystemFile::Exists(symbolDatabaseFilePath.c_str())) diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Shader/ShaderVariantAsset.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Shader/ShaderVariantAsset.h index 1462490884..66bbd7b188 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Shader/ShaderVariantAsset.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Shader/ShaderVariantAsset.h @@ -40,7 +40,7 @@ namespace AZ //! by ShaderVariantAssetBuilder this is 1+. static uint32_t MakeAssetProductSubId( uint32_t rhiApiUniqueIndex, uint32_t supervariantIndex, ShaderVariantStableId variantStableId, - uint32_t subProductType = ShaderVariantAssetSubProductType); + uint32_t subProductType = 0); ShaderVariantAsset() = default; ~ShaderVariantAsset() = default; From 5b6e342fe14fb7d6127cb87245e73514ee3db8ff Mon Sep 17 00:00:00 2001 From: moudgils <47460854+moudgils@users.noreply.github.com> Date: Mon, 27 Sep 2021 09:38:56 -0700 Subject: [PATCH 30/51] Add support for gpu descriptor heap compaction amongst other things (#4219) * Primary Support to compact shader visible Cbv/Srv/Uav heap if it fragments enough to run out of descriptor handles - Split the main heap into two sections - static handles and dynamic handles for descriptor tables - Add support to data drive number of allowed static handles - Add support to data drive ability to compact the relevant shader visible heap as there is overhead associated with compaction - If compaction is enabled we create two shader visible ehaps and ping pong between them since you can not compact an active heap as it needs to stay active for 3 frames atleast - As part of compaction we copy over the static handles as is but recreate all the dynamic section(i.e descriptaor tables) by tracking all the active SRGs, reallocating the descriptor tables and updating them from the non-shader visible heap - Enabled 3 fences for cpu/gpu synchronization so that cpu can get 3 frames ahead of gpu - Use fixed_wstring for commandlist name as a perf optimization Signed-off-by: moudgils * - Disable Heap compaction by default - Disable PSO caching when WARP is enabled - Misc cleanup Signed-off-by: moudgils * Address Feedback Signed-off-by: moudgils * Address feedback Signed-off-by: moudgils --- .../Include/Atom/RHI.Reflect/MemoryUsage.h | 66 ++- .../RHI/Code/Include/Atom/RHI/Allocator.h | 81 ++-- Gems/Atom/RHI/Code/Include/Atom/RHI/Device.h | 6 + Gems/Atom/RHI/Code/Include/Atom/RHI/Factory.h | 3 + .../Code/Include/Atom/RHI/FreeListAllocator.h | 1 + Gems/Atom/RHI/Code/Source/RHI/Factory.cpp | 11 +- .../RHI/Code/Source/RHI/FrameScheduler.cpp | 5 + .../RHI/Code/Source/RHI/FreeListAllocator.cpp | 12 + .../DX12/PlatformLimitsDescriptor.h | 6 + .../RHI.Reflect/PlatformLimitsDescriptor.cpp | 6 +- .../RHI/DX12/Code/Source/RHI/CommandList.h | 14 +- .../DX12/Code/Source/RHI/CommandListBase.cpp | 2 +- .../Code/Source/RHI/CommandQueueContext.cpp | 2 +- .../Code/Source/RHI/DescriptorContext.cpp | 241 ++++++++-- .../DX12/Code/Source/RHI/DescriptorContext.h | 50 ++- .../DX12/Code/Source/RHI/DescriptorPool.cpp | 183 ++++++-- .../RHI/DX12/Code/Source/RHI/DescriptorPool.h | 80 +++- Gems/Atom/RHI/DX12/Code/Source/RHI/Device.cpp | 15 + Gems/Atom/RHI/DX12/Code/Source/RHI/Device.h | 42 +- .../DX12/Code/Source/RHI/PipelineLibrary.cpp | 12 +- .../Code/Source/RHI/ShaderResourceGroup.h | 1 + .../Source/RHI/ShaderResourceGroupPool.cpp | 418 ++++++++++++------ .../Code/Source/RHI/ShaderResourceGroupPool.h | 22 +- .../Platform/Windows/PlatformLimits.setreg | 6 +- 24 files changed, 958 insertions(+), 327 deletions(-) diff --git a/Gems/Atom/RHI/Code/Include/Atom/RHI.Reflect/MemoryUsage.h b/Gems/Atom/RHI/Code/Include/Atom/RHI.Reflect/MemoryUsage.h index 36511ebccf..b6f6e0df11 100644 --- a/Gems/Atom/RHI/Code/Include/Atom/RHI.Reflect/MemoryUsage.h +++ b/Gems/Atom/RHI/Code/Include/Atom/RHI.Reflect/MemoryUsage.h @@ -28,25 +28,19 @@ namespace AZ size_t m_accumulatedInBytes = 0; }; - /** - * Tracks memory usage for a specific heap in the system. The data is expected to adhere to the following constraints: - * - * 1) Reserved <= Budget (unless the budget is 0). - * 2) Resident <= Reserved. - */ + //! Tracks memory usage for a specific heap in the system. The data is expected to adhere to the following constraints: + //! 1) Reserved <= Budget (unless the budget is 0). + //! 2) Resident <= Reserved. struct HeapMemoryUsage { HeapMemoryUsage() = default; HeapMemoryUsage(const HeapMemoryUsage&); HeapMemoryUsage& operator=(const HeapMemoryUsage&); - /** - * This helper reserves memory in a thread-safe fashion. If the result exceeds the budget, the reservation is safely - * reverted and false is returned. otherwise, true is returned. Only m_reservedInBytes is affected. - * - * @param sizeInBytes The amount of bytes to reserve. - * @return Whether the reservation was successful. - */ + //! This helper reserves memory in a thread-safe fashion. If the result exceeds the budget, the reservation is safely + //! reverted and false is returned. otherwise, true is returned. Only m_reservedInBytes is affected. + //! @param sizeInBytes The amount of bytes to reserve. + //! @return Whether the reservation was successful. bool TryReserveMemory(size_t sizeInBytes) { const size_t reservationInBytes = (m_reservedInBytes += sizeInBytes); @@ -60,45 +54,41 @@ namespace AZ return true; } - /** - * Helper function to validate sizes - */ + //! Helper function to validate sizes void Validate() { if (Validation::IsEnabled()) { - AZ_Assert(m_budgetInBytes >= m_reservedInBytes, "Reserved memory is larger than memory budget"); - AZ_Assert(m_reservedInBytes >= m_residentInBytes, "Resident memory is larger than reserved memory"); + AZ_Assert( + m_budgetInBytes >= m_reservedInBytes, + "Reserved memory is larger than memory budget. Memory budget %zu Reserved %zu", m_budgetInBytes, m_reservedInBytes.load()); + AZ_Assert( + m_reservedInBytes >= m_residentInBytes, + "Resident memory is larger than reserved memory. Reserved Memory %zu Resident memory %zu", m_reservedInBytes.load(), + m_residentInBytes.load()); } } - /** - * The budget for the heap in bytes. A non-zero budget means the pool will reject reservation requests - * once the budget is exceeded. A zero budget effectively disables this check. On certain platforms, - * it may be unnecessary to budget certain heaps. Other platforms may require a non-zero budget for certain - * heaps. - */ + // The budget for the heap in bytes. A non-zero budget means the pool will reject reservation requests + // once the budget is exceeded. A zero budget effectively disables this check. On certain platforms, + // it may be unnecessary to budget certain heaps. Other platforms may require a non-zero budget for certain + // heaps. size_t m_budgetInBytes = 0; - /** - * Number of bytes reserved on the heap for allocations. This value represents the allocation capacity for - * the platform. It is validated against the budget and may not exceed it. - */ + // Number of bytes reserved on the heap for allocations. This value represents the allocation capacity for + // the platform. It is validated against the budget and may not exceed it. AZStd::atomic_size_t m_reservedInBytes{ 0 }; - /** - * Number of bytes physically allocated on the heap. This may not exceed the reservation. Certain platforms - * may choose to transfer memory down the heap level hierarchy in response to memory trim events from the driver. - */ + // Number of bytes physically allocated on the heap. This may not exceed the reservation. Certain platforms + // may choose to transfer memory down the heap level hierarchy in response to memory trim events from the driver. AZStd::atomic_size_t m_residentInBytes{ 0 }; }; - /** - * Describes memory usage metrics of a resource pool. Resource pools *can* associate with a single - * device memory heap (i.e. a single GPU) and the host memory heap. Certain pools on specific platforms - * may not require one or the other. In this case, the memory usage / budget will report empty values for - * that heap type. - */ + //! + //! Describes memory usage metrics of a resource pool. Resource pools *can* associate with a single + //! device memory heap (i.e. a single GPU) and the host memory heap. Certain pools on specific platforms + //! may not require one or the other. In this case, the memory usage / budget will report empty values for + //! that heap type. struct PoolMemoryUsage { PoolMemoryUsage() = default; diff --git a/Gems/Atom/RHI/Code/Include/Atom/RHI/Allocator.h b/Gems/Atom/RHI/Code/Include/Atom/RHI/Allocator.h index 0bd0056a0b..dc95078f80 100644 --- a/Gems/Atom/RHI/Code/Include/Atom/RHI/Allocator.h +++ b/Gems/Atom/RHI/Code/Include/Atom/RHI/Allocator.h @@ -13,13 +13,12 @@ namespace AZ { namespace RHI { - /** - * A virtual address which may be relative to a base resource. This means - * 0 might be a valid address (dependent on the Allocator::Descriptor::m_addressBase value). - * To account for this, VirtualAddress::Null is used instead. Check validity of the address - * using IsValid or IsNull instead of checking for 0. VirtualAddress is initialized - * to Null, so returning the default constructor is sufficient to represent an invalid address. - */ + + //! A virtual address which may be relative to a base resource. This means + //! 0 might be a valid address (dependent on the Allocator::Descriptor::m_addressBase value). + //! To account for this, VirtualAddress::Null is used instead. Check validity of the address + //! using IsValid or IsNull instead of checking for 0. VirtualAddress is initialized + //! to Null, so returning the default constructor is sufficient to represent an invalid address. class VirtualAddress { static const VirtualAddress Null; @@ -29,13 +28,13 @@ namespace AZ static VirtualAddress CreateNull(); - /// Creates a valid address with a zero offset. + //! Creates a valid address with a zero offset. static VirtualAddress CreateZero(); - /// Creates an address from a pointer. + //! Creates an address from a pointer. static VirtualAddress CreateFromPointer(void* ptr); - /// Creates an address from an offset from a base pointer. + //! Creates an address from an offset from a base pointer. static VirtualAddress CreateFromOffset(uint64_t offset); inline bool IsValid() const @@ -51,15 +50,13 @@ namespace AZ uintptr_t m_ptr; }; - /** - * An allocator interface used for external GPU allocations. The allocator - * does not manage the host memory. Instead, the user specifies a base address - * (which may be 0, in order to allocate offsets from a base resource). The allocator - * interface also provides an API for garbage collection. If used to manage GPU resources, - * these are often deferred-released after N frames. The user may provide a garbage collection - * latency, which controls the number of GarbageCollect calls that must occur before an allocation - * is actually reclaimed. The intended use case is to garbage collect at the end of each frame. - */ + //! An allocator interface used for external GPU allocations. The allocator + //! does not manage the host memory. Instead, the user specifies a base address + //! (which may be 0, in order to allocate offsets from a base resource). The allocator + //! interface also provides an API for garbage collection. If used to manage GPU resources, + //! these are often deferred-released after N frames. The user may provide a garbage collection + //! latency, which controls the number of GarbageCollect calls that must occur before an allocation + //! is actually reclaimed. The intended use case is to garbage collect at the end of each frame. class Allocator { public: @@ -86,44 +83,42 @@ namespace AZ virtual void Shutdown() = 0; - /** - * Allocates a virtual address relative to the base address provided at initialization time. - * @param byteCount The number of bytes to allocate. - * @param byteAlignement The alignment used to align the allocation. - */ + //! Allocates a virtual address relative to the base address provided at initialization time. + //! @param byteCount The number of bytes to allocate. + //! @param byteAlignement The alignment used to align the allocation. virtual VirtualAddress Allocate(size_t byteCount, size_t byteAlignment) = 0; - /** - * Deallocates an allocation. The memory is not reclaimed until garbage collect is called. - * Depending on the garbage collection latency, it may take several garbage collection cycles - * before the memory is reclaimed. - */ + //! Deallocates an allocation. The memory is not reclaimed until garbage collect is called. + //! Depending on the garbage collection latency, it may take several garbage collection cycles + //! before the memory is reclaimed. virtual void DeAllocate(VirtualAddress offset) = 0; - /// Allocations are deferred-released until a specific number of GC cycles have occurred. This - /// is useful for allocations actively being consumed by the GPU. + //! Allocations are deferred-released until a specific number of GC cycles have occurred. This + //! is useful for allocations actively being consumed by the GPU. virtual void GarbageCollect() = 0; - /// Forces garbage collection of all allocations, regardless of the GC latency. + //! Forces garbage collection of all allocations, regardless of the GC latency. virtual void GarbageCollectForce() = 0; - /** - * Returns the number of allocations active for this allocator. This includes - * allocations that are pending garbage collection. - */ + //! Returns the number of allocations active for this allocator. This includes + //! allocations that are pending garbage collection. virtual size_t GetAllocationCount() const { return 0; } - /** - * Returns the number of bytes used by the allocator. This includes - * allocations that are pending garbage collection. - */ + //! Returns the number of bytes used by the allocator. This includes + //! allocations that are pending garbage collection. virtual size_t GetAllocatedByteCount() const { return 0; } - /// Returns the descriptor used to initialize the allocator. + //! Returns the descriptor used to initialize the allocator. virtual const Descriptor& GetDescriptor() const = 0; - /// Helper for converting agnostic VirtualAddress type to pointer type. Will convert - /// VirtualAddress::Null to nullptr. + //! Clone the current allocator to the new allocator passed in + virtual void Clone([[maybe_unused]] RHI::Allocator* newAllocator) + { + AZ_Assert(false, "Not Implemented"); + }; + + //! Helper for converting agnostic VirtualAddress type to pointer type. Will convert + //! VirtualAddress::Null to nullptr. template T* AllocateAs(size_t byteCount, size_t byteAlignment) { diff --git a/Gems/Atom/RHI/Code/Include/Atom/RHI/Device.h b/Gems/Atom/RHI/Code/Include/Atom/RHI/Device.h index f9df29cf74..fb4082bb25 100644 --- a/Gems/Atom/RHI/Code/Include/Atom/RHI/Device.h +++ b/Gems/Atom/RHI/Code/Include/Atom/RHI/Device.h @@ -139,6 +139,12 @@ namespace AZ //! Notifies after all objects currently in the platform release queue are released virtual void ObjectCollectionNotify(RHI::ObjectCollectorNotifyFunction notifyFunction) = 0; + //! Allows the back-ends to compact SRG related memory if applicable + virtual RHI::ResultCode CompactSRGMemory() + { + return RHI::ResultCode::Success; + }; + protected: DeviceFeatures m_features; DeviceLimits m_limits; diff --git a/Gems/Atom/RHI/Code/Include/Atom/RHI/Factory.h b/Gems/Atom/RHI/Code/Include/Atom/RHI/Factory.h index 992cc0e79a..71f9f1605b 100644 --- a/Gems/Atom/RHI/Code/Include/Atom/RHI/Factory.h +++ b/Gems/Atom/RHI/Code/Include/Atom/RHI/Factory.h @@ -112,6 +112,9 @@ namespace AZ //! Returns true if Pix dll is loaded static bool IsPixModuleLoaded(); + //! Returns true if Warp is enabled + static bool UsingWarpDevice(); + //! Returns the name of the Factory. virtual Name GetName() = 0; diff --git a/Gems/Atom/RHI/Code/Include/Atom/RHI/FreeListAllocator.h b/Gems/Atom/RHI/Code/Include/Atom/RHI/FreeListAllocator.h index ee1cb7c7f4..ce20f9cbe5 100644 --- a/Gems/Atom/RHI/Code/Include/Atom/RHI/FreeListAllocator.h +++ b/Gems/Atom/RHI/Code/Include/Atom/RHI/FreeListAllocator.h @@ -55,6 +55,7 @@ namespace AZ size_t GetAllocationCount() const override; size_t GetAllocatedByteCount() const override; const Descriptor& GetDescriptor() const override; + void Clone(RHI::Allocator* newAllocator) override; ////////////////////////////////////////////////////////////////////////// private: diff --git a/Gems/Atom/RHI/Code/Source/RHI/Factory.cpp b/Gems/Atom/RHI/Code/Source/RHI/Factory.cpp index 48b64c17c0..82b0a13c86 100644 --- a/Gems/Atom/RHI/Code/Source/RHI/Factory.cpp +++ b/Gems/Atom/RHI/Code/Source/RHI/Factory.cpp @@ -8,12 +8,12 @@ #include #include +#include #include #include #if defined(USE_RENDERDOC) || defined(USE_PIX) #include -#include #include #endif @@ -28,6 +28,8 @@ static AZStd::unique_ptr s_pixModule; static bool s_isPixGpuCaptureDllLoaded = false; #endif +static bool s_usingWarpDevice = false; + namespace AZ { namespace RHI @@ -55,6 +57,8 @@ namespace AZ Factory::Factory() { + AZStd::string preferredUserAdapterName = RHI::GetCommandLineValue("forceAdapter"); + s_usingWarpDevice = preferredUserAdapterName == "Microsoft Basic Render Driver"; #if defined(USE_RENDERDOC) // If RenderDoc is requested, we need to load the library as early as possible (before device queries/factories are made) bool enableRenderDoc = RHI::QueryCommandLineOption("enableRenderDoc"); @@ -197,5 +201,10 @@ namespace AZ return false; #endif } + + bool Factory::UsingWarpDevice() + { + return s_usingWarpDevice; + } } } diff --git a/Gems/Atom/RHI/Code/Source/RHI/FrameScheduler.cpp b/Gems/Atom/RHI/Code/Source/RHI/FrameScheduler.cpp index df887379fe..5d2feb1e34 100644 --- a/Gems/Atom/RHI/Code/Source/RHI/FrameScheduler.cpp +++ b/Gems/Atom/RHI/Code/Source/RHI/FrameScheduler.cpp @@ -314,6 +314,11 @@ namespace AZ resourcePoolDatabase.ForEachShaderResourceGroupPool(compileAllLambda); } + + //It is possible for certain back ends to run out of SRG memory (due to fragmentation) in which case + //we try to compact and re-compile SRGs. + RHI::ResultCode resultCode = m_device->CompactSRGMemory(); + AZ_Assert(resultCode == RHI::ResultCode::Success, "SRG compaction failed and this can lead to a gpu crash."); } void FrameScheduler::BuildRayTracingShaderTables() diff --git a/Gems/Atom/RHI/Code/Source/RHI/FreeListAllocator.cpp b/Gems/Atom/RHI/Code/Source/RHI/FreeListAllocator.cpp index c646700495..77877b0020 100644 --- a/Gems/Atom/RHI/Code/Source/RHI/FreeListAllocator.cpp +++ b/Gems/Atom/RHI/Code/Source/RHI/FreeListAllocator.cpp @@ -344,5 +344,17 @@ namespace AZ handle = node.m_nextFree; } } + + void FreeListAllocator::Clone(RHI::Allocator* newAllocator) + { + FreeListAllocator* newFreeListAllocator = static_cast(newAllocator); + newFreeListAllocator->m_headHandle = m_headHandle; + newFreeListAllocator->m_nodeFreeList = m_nodeFreeList; + newFreeListAllocator->m_nodes = m_nodes; + newFreeListAllocator->m_allocations = m_allocations; + newFreeListAllocator->m_garbage = m_garbage; + newFreeListAllocator->m_garbageCollectCycle = m_garbageCollectCycle; + newFreeListAllocator->m_byteCountTotal = m_byteCountTotal; + } } } diff --git a/Gems/Atom/RHI/DX12/Code/Include/Atom/RHI.Reflect/DX12/PlatformLimitsDescriptor.h b/Gems/Atom/RHI/DX12/Code/Include/Atom/RHI.Reflect/DX12/PlatformLimitsDescriptor.h index 1f22599b38..63c2d69ea2 100644 --- a/Gems/Atom/RHI/DX12/Code/Include/Atom/RHI.Reflect/DX12/PlatformLimitsDescriptor.h +++ b/Gems/Atom/RHI/DX12/Code/Include/Atom/RHI.Reflect/DX12/PlatformLimitsDescriptor.h @@ -64,6 +64,12 @@ namespace AZ //! int array: Max count for descriptors AZStd::unordered_map> m_descriptorHeapLimits; + // Number of max static handles for shader visible srv/uav/cbv views + uint32_t m_numShaderVisibleCbvSrvUavStaticHandles = 2000; + + //Bool to indicate allowing compaction of shader visible srv/uav/cbv heap in case of fragmentation + bool m_allowDescriptorHeapCompaction = false; + FrameGraphExecuterData m_frameGraphExecuterData; void LoadPlatformLimitsDescriptor(const char* rhiName) override; diff --git a/Gems/Atom/RHI/DX12/Code/Source/RHI.Reflect/PlatformLimitsDescriptor.cpp b/Gems/Atom/RHI/DX12/Code/Source/RHI.Reflect/PlatformLimitsDescriptor.cpp index 77b41d6ffc..fd9ad84511 100644 --- a/Gems/Atom/RHI/DX12/Code/Source/RHI.Reflect/PlatformLimitsDescriptor.cpp +++ b/Gems/Atom/RHI/DX12/Code/Source/RHI.Reflect/PlatformLimitsDescriptor.cpp @@ -19,8 +19,10 @@ namespace AZ if (SerializeContext* serializeContext = azrtti_cast(context)) { serializeContext->Class() - ->Version(0) + ->Version(1) ->Field("DescriptorHeapLimits", &PlatformLimitsDescriptor::m_descriptorHeapLimits) + ->Field("NumShaderVisibleCbvSrvUavStaticHandles", &PlatformLimitsDescriptor::m_numShaderVisibleCbvSrvUavStaticHandles) + ->Field("AllowDescriptorHeapCompaction", &PlatformLimitsDescriptor::m_allowDescriptorHeapCompaction) ->Field("FrameGraphExecuterData", &PlatformLimitsDescriptor::m_frameGraphExecuterData) ; } @@ -54,7 +56,7 @@ namespace AZ // Map default value must be initialized after attempting to serialize (and result in failure). // Otherwise, serialization won't overwrite the default values. m_descriptorHeapLimits = AZStd::unordered_map>({ - { AZStd::string("DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV"), { 1000000, 1000000 } }, + { AZStd::string("DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV"), { 100000, 1000000 } }, { AZStd::string("DESCRIPTOR_HEAP_TYPE_SAMPLER"), { 2048, 2048 } }, { AZStd::string("DESCRIPTOR_HEAP_TYPE_RTV"), { 2048, 0 } }, { AZStd::string("DESCRIPTOR_HEAP_TYPE_DSV"), { 2048, 0 } } diff --git a/Gems/Atom/RHI/DX12/Code/Source/RHI/CommandList.h b/Gems/Atom/RHI/DX12/Code/Source/RHI/CommandList.h index 443ab3e73a..1472cdc80e 100644 --- a/Gems/Atom/RHI/DX12/Code/Source/RHI/CommandList.h +++ b/Gems/Atom/RHI/DX12/Code/Source/RHI/CommandList.h @@ -438,7 +438,7 @@ namespace AZ switch (pipelineType) { case RHI::PipelineStateType::Draw: - if (binding.m_resourceTable.IsValid()) + if (binding.m_resourceTable.IsValid() && compiledData.m_gpuViewsDescriptorHandle.ptr) { GetCommandList()->SetGraphicsRootDescriptorTable(binding.m_resourceTable.GetIndex(), compiledData.m_gpuViewsDescriptorHandle); } @@ -448,14 +448,15 @@ namespace AZ GetCommandList()->SetGraphicsRootConstantBufferView(binding.m_constantBuffer.GetIndex(), compiledData.m_gpuConstantAddress); } - if (binding.m_samplerTable.IsValid()) + if (binding.m_samplerTable.IsValid() && compiledData.m_gpuSamplersDescriptorHandle.ptr) { GetCommandList()->SetGraphicsRootDescriptorTable(binding.m_samplerTable.GetIndex(), compiledData.m_gpuSamplersDescriptorHandle); } for (uint32_t unboundedArrayIndex = 0; unboundedArrayIndex < ShaderResourceGroupCompiledData::MaxUnboundedArrays; ++unboundedArrayIndex) { - if (binding.m_unboundedArrayResourceTables[unboundedArrayIndex].IsValid()) + if (binding.m_unboundedArrayResourceTables[unboundedArrayIndex].IsValid() && + compiledData.m_gpuUnboundedArraysDescriptorHandles[unboundedArrayIndex].ptr) { GetCommandList()->SetGraphicsRootDescriptorTable( binding.m_unboundedArrayResourceTables[unboundedArrayIndex].GetIndex(), @@ -465,7 +466,7 @@ namespace AZ break; case RHI::PipelineStateType::Dispatch: - if (binding.m_resourceTable.IsValid()) + if (binding.m_resourceTable.IsValid() && compiledData.m_gpuViewsDescriptorHandle.ptr) { GetCommandList()->SetComputeRootDescriptorTable(binding.m_resourceTable.GetIndex(), compiledData.m_gpuViewsDescriptorHandle); } @@ -475,14 +476,15 @@ namespace AZ GetCommandList()->SetComputeRootConstantBufferView(binding.m_constantBuffer.GetIndex(), compiledData.m_gpuConstantAddress); } - if (binding.m_samplerTable.IsValid()) + if (binding.m_samplerTable.IsValid() && compiledData.m_gpuSamplersDescriptorHandle.ptr) { GetCommandList()->SetComputeRootDescriptorTable(binding.m_samplerTable.GetIndex(), compiledData.m_gpuSamplersDescriptorHandle); } for (uint32_t unboundedArrayIndex = 0; unboundedArrayIndex < ShaderResourceGroupCompiledData::MaxUnboundedArrays; ++unboundedArrayIndex) { - if (binding.m_unboundedArrayResourceTables[unboundedArrayIndex].IsValid()) + if (binding.m_unboundedArrayResourceTables[unboundedArrayIndex].IsValid() && + compiledData.m_gpuUnboundedArraysDescriptorHandles[unboundedArrayIndex].ptr) { GetCommandList()->SetComputeRootDescriptorTable( binding.m_unboundedArrayResourceTables[unboundedArrayIndex].GetIndex(), diff --git a/Gems/Atom/RHI/DX12/Code/Source/RHI/CommandListBase.cpp b/Gems/Atom/RHI/DX12/Code/Source/RHI/CommandListBase.cpp index efa9518ee1..9733045179 100644 --- a/Gems/Atom/RHI/DX12/Code/Source/RHI/CommandListBase.cpp +++ b/Gems/Atom/RHI/DX12/Code/Source/RHI/CommandListBase.cpp @@ -50,7 +50,7 @@ namespace AZ void CommandListBase::SetNameInternal(const AZStd::string_view& name) { - AZStd::wstring wname; + AZStd::fixed_wstring<256> wname; AZStd::to_wstring(wname, name.data()); GetCommandList()->SetName(wname.data()); } diff --git a/Gems/Atom/RHI/DX12/Code/Source/RHI/CommandQueueContext.cpp b/Gems/Atom/RHI/DX12/Code/Source/RHI/CommandQueueContext.cpp index 9bf25d8bc6..012784d3fc 100644 --- a/Gems/Atom/RHI/DX12/Code/Source/RHI/CommandQueueContext.cpp +++ b/Gems/Atom/RHI/DX12/Code/Source/RHI/CommandQueueContext.cpp @@ -39,7 +39,7 @@ namespace AZ { Device& device = static_cast(deviceBase); m_currentFrameIndex = 0; - m_frameFences.resize(RHI::Limits::Device::FrameCountMax - 1); + m_frameFences.resize(RHI::Limits::Device::FrameCountMax); for (FenceSet& fences : m_frameFences) { fences.Init(device.GetDevice(), RHI::FenceState::Signaled); diff --git a/Gems/Atom/RHI/DX12/Code/Source/RHI/DescriptorContext.cpp b/Gems/Atom/RHI/DX12/Code/Source/RHI/DescriptorContext.cpp index 635c66f1cd..1b0df5846c 100644 --- a/Gems/Atom/RHI/DX12/Code/Source/RHI/DescriptorContext.cpp +++ b/Gems/Atom/RHI/DX12/Code/Source/RHI/DescriptorContext.cpp @@ -10,7 +10,9 @@ #include #include #include +#include #include +#include namespace AZ { @@ -40,7 +42,7 @@ namespace AZ for (D3D12_SRV_DIMENSION dimension : validSRVDimensions) { - DescriptorHandle srvDescriptorHandle = Allocate(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, D3D12_DESCRIPTOR_HEAP_FLAG_NONE, 1).GetOffset(); + DescriptorHandle srvDescriptorHandle = AllocateHandle(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, D3D12_DESCRIPTOR_HEAP_FLAG_NONE, 1); D3D12_SHADER_RESOURCE_VIEW_DESC desc = {}; desc.Format = DXGI_FORMAT_R32_UINT; @@ -62,7 +64,7 @@ namespace AZ for (D3D12_UAV_DIMENSION dimension : UAVDimensions) { - DescriptorHandle uavDescriptorHandle = Allocate(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, D3D12_DESCRIPTOR_HEAP_FLAG_NONE, 1).GetOffset(); + DescriptorHandle uavDescriptorHandle = AllocateHandle(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, D3D12_DESCRIPTOR_HEAP_FLAG_NONE, 1); D3D12_UNORDERED_ACCESS_VIEW_DESC desc = {}; desc.Format = DXGI_FORMAT_R32_UINT; @@ -75,14 +77,14 @@ namespace AZ void DescriptorContext::CreateNullDescriptorsCBV() { D3D12_CONSTANT_BUFFER_VIEW_DESC constantBufferDesc = {}; - DescriptorHandle cbvDescriptorHandle = Allocate(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, D3D12_DESCRIPTOR_HEAP_FLAG_NONE, 1).GetOffset(); + DescriptorHandle cbvDescriptorHandle = AllocateHandle(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, D3D12_DESCRIPTOR_HEAP_FLAG_NONE, 1); m_device->CreateConstantBufferView(&constantBufferDesc, GetCpuPlatformHandle(cbvDescriptorHandle)); m_nullDescriptorCBV = cbvDescriptorHandle; } void DescriptorContext::CreateNullDescriptorsSampler() { - m_nullSamplerDescriptor = Allocate(D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER, D3D12_DESCRIPTOR_HEAP_FLAG_NONE, 1).GetOffset(); + m_nullSamplerDescriptor = AllocateHandle(D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER, D3D12_DESCRIPTOR_HEAP_FLAG_NONE, 1); D3D12_SAMPLER_DESC samplerDesc = {}; samplerDesc.Filter = D3D12_FILTER_MIN_MAG_MIP_LINEAR; samplerDesc.AddressU = D3D12_TEXTURE_ADDRESS_MODE_WRAP; @@ -102,7 +104,7 @@ namespace AZ AZ_Assert(platformLimitsDescriptor.get(), "Platform limits information is missing"); m_platformLimitsDescriptor = platformLimitsDescriptor; - + m_allowDescriptorHeapCompaction = m_platformLimitsDescriptor->m_allowDescriptorHeapCompaction; for (const auto& itr : platformLimitsDescriptor->m_descriptorHeapLimits) { for (uint32_t shaderVisibleIdx = 0; shaderVisibleIdx < PlatformLimitsDescriptor::NumHeapFlags; ++shaderVisibleIdx) @@ -114,11 +116,33 @@ namespace AZ if (descriptorCountMax) { - GetPool(static_cast(heapTypeIdx.value()), shaderVisibleIdx).Init(m_device.get(), type, flags, descriptorCountMax); + if (m_allowDescriptorHeapCompaction && IsShaderVisibleCbvSrvUavHeap(type, flags)) + { + //Init the two heaps to help support compaction after fragmentation + m_shaderVisibleCbvSrvUavPools[0].Init( + m_device.get(), D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE, + descriptorCountMax, platformLimitsDescriptor->m_numShaderVisibleCbvSrvUavStaticHandles); + + m_shaderVisibleCbvSrvUavPools[1].Init( + m_device.get(), D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE, + descriptorCountMax, platformLimitsDescriptor->m_numShaderVisibleCbvSrvUavStaticHandles); + } + else + { + GetPool(static_cast(heapTypeIdx.value()), shaderVisibleIdx).Init(m_device.get(), type, flags, descriptorCountMax, descriptorCountMax); + } } } } - + + if (m_allowDescriptorHeapCompaction) + { + m_backupStaticHandles.Init( + m_device.get(), D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, D3D12_DESCRIPTOR_HEAP_FLAG_NONE, + platformLimitsDescriptor->m_numShaderVisibleCbvSrvUavStaticHandles, + platformLimitsDescriptor->m_numShaderVisibleCbvSrvUavStaticHandles); + } + CreateNullDescriptors(); } @@ -129,7 +153,7 @@ namespace AZ { if (constantBufferView.IsNull()) { - constantBufferView = Allocate(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, D3D12_DESCRIPTOR_HEAP_FLAG_NONE, 1).GetOffset(); + constantBufferView = AllocateHandle(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, D3D12_DESCRIPTOR_HEAP_FLAG_NONE, 1); } D3D12_CPU_DESCRIPTOR_HANDLE descriptorHandle = GetCpuPlatformHandle(constantBufferView); @@ -145,7 +169,7 @@ namespace AZ { if (shaderResourceView.IsNull()) { - shaderResourceView = Allocate(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, D3D12_DESCRIPTOR_HEAP_FLAG_NONE, 1).GetOffset(); + shaderResourceView = AllocateHandle(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, D3D12_DESCRIPTOR_HEAP_FLAG_NONE, 1); } D3D12_CPU_DESCRIPTOR_HANDLE descriptorHandle = GetCpuPlatformHandle(shaderResourceView); @@ -165,7 +189,7 @@ namespace AZ { if (unorderedAccessView.IsNull()) { - unorderedAccessView = Allocate(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, D3D12_DESCRIPTOR_HEAP_FLAG_NONE, 1).GetOffset(); + unorderedAccessView = AllocateHandle(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, D3D12_DESCRIPTOR_HEAP_FLAG_NONE, 1); } D3D12_CPU_DESCRIPTOR_HANDLE unorderedAccessDescriptor = GetCpuPlatformHandle(unorderedAccessView); @@ -176,7 +200,24 @@ namespace AZ // Copy the UAV descriptor into the GPU-visible version for clearing. if (unorderedAccessViewClear.IsNull()) { - unorderedAccessViewClear = Allocate(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE, 1).GetOffset(); + unorderedAccessViewClear = AllocateHandle(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE, 1); + + if (unorderedAccessViewClear.IsNull()) + { + AZ_Assert( + false, + "Descriptor heap ran out of memory for static handles. Please consider increasing the value of NumShaderVisibleCbvSrvUavStaticHandles" + "within platformlimits.azasset file for dx12."); + return; + } + + if (m_allowDescriptorHeapCompaction) + { + //We make a copy of static handles in case we need to compact and recreate the shader visible heap + m_device->CopyDescriptorsSimple( + 1, m_backupStaticHandles.GetCpuPlatformHandle(unorderedAccessViewClear), unorderedAccessDescriptor, + unorderedAccessViewClear.m_type); + } } CopyDescriptor(unorderedAccessViewClear, unorderedAccessView); } @@ -188,7 +229,7 @@ namespace AZ { if (shaderResourceView.IsNull()) { - shaderResourceView = Allocate(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, D3D12_DESCRIPTOR_HEAP_FLAG_NONE, 1).GetOffset(); + shaderResourceView = AllocateHandle(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, D3D12_DESCRIPTOR_HEAP_FLAG_NONE, 1); } D3D12_CPU_DESCRIPTOR_HANDLE descriptorHandle = GetCpuPlatformHandle(shaderResourceView); @@ -205,7 +246,7 @@ namespace AZ { if (unorderedAccessView.IsNull()) { - unorderedAccessView = Allocate(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, D3D12_DESCRIPTOR_HEAP_FLAG_NONE, 1).GetOffset(); + unorderedAccessView = AllocateHandle(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, D3D12_DESCRIPTOR_HEAP_FLAG_NONE, 1); } D3D12_CPU_DESCRIPTOR_HANDLE unorderedAccessDescriptor = GetCpuPlatformHandle(unorderedAccessView); @@ -216,7 +257,24 @@ namespace AZ // Copy the UAV descriptor into the GPU-visible version for clearing. if (unorderedAccessViewClear.IsNull()) { - unorderedAccessViewClear = Allocate(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE, 1).GetOffset(); + unorderedAccessViewClear = AllocateHandle(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE, 1); + + if (unorderedAccessViewClear.IsNull()) + { + AZ_Assert( + false, + "Descriptor heap ran out of memory for static handles. Please consider increasing the value of " + "NumShaderVisibleCbvSrvUavStaticHandles within platformlimits.azasset file for dx12."); + return; + } + + if (m_allowDescriptorHeapCompaction) + { + // We make a copy of static handles in case we need to compact and recreate the shader visible heap + m_device->CopyDescriptorsSimple( + 1, m_backupStaticHandles.GetCpuPlatformHandle(unorderedAccessViewClear), unorderedAccessDescriptor, + unorderedAccessViewClear.m_type); + } } CopyDescriptor(unorderedAccessViewClear, unorderedAccessView); } @@ -228,7 +286,7 @@ namespace AZ { if (renderTargetView.IsNull()) { - renderTargetView = Allocate(D3D12_DESCRIPTOR_HEAP_TYPE_RTV, D3D12_DESCRIPTOR_HEAP_FLAG_NONE, 1).GetOffset(); + renderTargetView = AllocateHandle(D3D12_DESCRIPTOR_HEAP_TYPE_RTV, D3D12_DESCRIPTOR_HEAP_FLAG_NONE, 1); } D3D12_CPU_DESCRIPTOR_HANDLE renderTargetDescriptor = GetCpuPlatformHandle(renderTargetView); @@ -245,13 +303,13 @@ namespace AZ { if (depthStencilView.IsNull()) { - depthStencilView = Allocate(D3D12_DESCRIPTOR_HEAP_TYPE_DSV, D3D12_DESCRIPTOR_HEAP_FLAG_NONE, 1).GetOffset(); + depthStencilView = AllocateHandle(D3D12_DESCRIPTOR_HEAP_TYPE_DSV, D3D12_DESCRIPTOR_HEAP_FLAG_NONE, 1); } D3D12_CPU_DESCRIPTOR_HANDLE depthStencilDescriptor = GetCpuPlatformHandle(depthStencilView); if (depthStencilReadView.IsNull()) { - depthStencilReadView = Allocate(D3D12_DESCRIPTOR_HEAP_TYPE_DSV, D3D12_DESCRIPTOR_HEAP_FLAG_NONE, 1).GetOffset(); + depthStencilReadView = AllocateHandle(D3D12_DESCRIPTOR_HEAP_TYPE_DSV, D3D12_DESCRIPTOR_HEAP_FLAG_NONE, 1); } D3D12_CPU_DESCRIPTOR_HANDLE depthStencilReadDescriptor = GetCpuPlatformHandle(depthStencilReadView); @@ -274,7 +332,7 @@ namespace AZ { if (samplerHandle.IsNull()) { - samplerHandle = Allocate(D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER, D3D12_DESCRIPTOR_HEAP_FLAG_NONE, 1).GetOffset(); + samplerHandle = AllocateHandle(D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER, D3D12_DESCRIPTOR_HEAP_FLAG_NONE, 1); } D3D12_SAMPLER_DESC samplerDesc; @@ -286,17 +344,49 @@ namespace AZ { if (!descriptorHandle.IsNull()) { - ReleaseDescriptorTable(DescriptorTable(descriptorHandle, 1)); + GetPool(descriptorHandle.m_type, descriptorHandle.m_flags).ReleaseHandle(descriptorHandle); } } DescriptorTable DescriptorContext::CreateDescriptorTable( - D3D12_DESCRIPTOR_HEAP_TYPE descriptorHeapType, - uint32_t descriptorCount) + D3D12_DESCRIPTOR_HEAP_TYPE descriptorHeapType, uint32_t descriptorCount, ShaderResourceGroup* srg) { - return Allocate(descriptorHeapType, D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE, descriptorCount); + if (m_allowDescriptorHeapCompaction && !m_compactionInProgress) + { + // Track active SRGs in case we need to compact the shader visible cbv_srv_uav heap + AZStd::scoped_lock lock{ m_srgMapMutex }; + auto iter = m_srgAllocations.find(srg); + if (iter == m_srgAllocations.end()) + { + m_srgAllocations.emplace(srg, 1); + } + else + { + m_srgAllocations[srg]++; + } + } + + return GetPool(descriptorHeapType, D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE).AllocateTable(descriptorCount); } + void DescriptorContext::ReleaseDescriptorTable(DescriptorTable table, ShaderResourceGroup* srg) + { + if (m_allowDescriptorHeapCompaction && !m_compactionInProgress) + { + //Track active SRGs in case we need to compact the shader visible cbv_srv_uav heap + AZStd::scoped_lock lock{ m_srgMapMutex }; + auto iter = m_srgAllocations.find(srg); + AZ_Assert(iter != m_srgAllocations.end(), "Srg entry not found"); + m_srgAllocations[srg]--; + if (m_srgAllocations[srg] == 0) + { + m_srgAllocations.erase(srg); + } + } + + GetPool(table.GetType(), table.GetFlags()).ReleaseTable(table); + } + void DescriptorContext::UpdateDescriptorTableRange( DescriptorTable gpuDestinationTable, const DescriptorHandle* cpuSourceDescriptors, @@ -313,14 +403,12 @@ namespace AZ } // Resolve destination descriptor to platform handle. - D3D12_CPU_DESCRIPTOR_HANDLE gpuDestinationHandle = GetCpuPlatformHandle(gpuDestinationTable.GetOffset()); + D3D12_CPU_DESCRIPTOR_HANDLE gpuDestinationHandle = GetCpuPlatformHandleForTable(gpuDestinationTable); // An array of descriptor sizes for each range. We just want N ranges with 1 descriptor each. AZStd::vector rangeCounts(DescriptorCount, 1); - /** - * We are gathering N source descriptors into a contiguous destination table. - */ + //We are gathering N source descriptors into a contiguous destination table. m_device->CopyDescriptors( 1, // Number of destination ranges. &gpuDestinationHandle, // Destination range array. @@ -353,19 +441,24 @@ namespace AZ } } } + + if (m_allowDescriptorHeapCompaction) + { + m_backupStaticHandles.GarbageCollect(); + } } - DescriptorTable DescriptorContext::Allocate( + DescriptorTable DescriptorContext::AllocateTable( D3D12_DESCRIPTOR_HEAP_TYPE type, D3D12_DESCRIPTOR_HEAP_FLAGS flags, uint32_t count) { - return GetPool(type, flags).Allocate(count); + return GetPool(type, flags).AllocateTable(count); } - void DescriptorContext::ReleaseDescriptorTable(DescriptorTable table) + DescriptorHandle DescriptorContext::AllocateHandle(D3D12_DESCRIPTOR_HEAP_TYPE type, D3D12_DESCRIPTOR_HEAP_FLAGS flags, uint32_t count) { - GetPool(table.GetType(), table.GetFlags()).Release(table); + return GetPool(type, flags).AllocateHandle(count); } D3D12_CPU_DESCRIPTOR_HANDLE DescriptorContext::GetCpuPlatformHandle(DescriptorHandle handle) const @@ -378,6 +471,16 @@ namespace AZ return GetPool(handle.m_type, handle.m_flags).GetGpuPlatformHandle(handle); } + D3D12_CPU_DESCRIPTOR_HANDLE DescriptorContext::GetCpuPlatformHandleForTable(DescriptorTable descTable) const + { + return GetPool(descTable.GetOffset().m_type, descTable.GetOffset().m_flags).GetCpuPlatformHandleForTable(descTable); + } + + D3D12_GPU_DESCRIPTOR_HANDLE DescriptorContext::GetGpuPlatformHandleForTable(DescriptorTable descTable) const + { + return GetPool(descTable.GetOffset().m_type, descTable.GetOffset().m_flags).GetGpuPlatformHandleForTable(descTable); + } + DescriptorHandle DescriptorContext::GetNullHandleSRV(D3D12_SRV_DIMENSION dimension) const { auto iter = m_nullDescriptorsSRV.find(dimension); @@ -431,14 +534,88 @@ namespace AZ { AZ_Assert(type < D3D12_DESCRIPTOR_HEAP_TYPE_NUM_TYPES, "Trying to get pool with invalid type: [%d]", type); AZ_Assert(flag < NumHeapFlags, "Trying to get pool with invalid flag: [%d]", flag); - return m_pools[type][flag]; + + if (m_allowDescriptorHeapCompaction && IsShaderVisibleCbvSrvUavHeap(type, flag)) + { + return m_shaderVisibleCbvSrvUavPools[m_currentHeapIndex]; + } + else + { + return m_pools[type][flag]; + } } const DescriptorPool& DescriptorContext::GetPool(uint32_t type, uint32_t flag) const { AZ_Assert(type < D3D12_DESCRIPTOR_HEAP_TYPE_NUM_TYPES, "Trying to get pool with invalid type: [%d]", type); AZ_Assert(flag < NumHeapFlags, "Trying to get pool with invalid flag: [%d]", flag); - return m_pools[type][flag]; + if (m_allowDescriptorHeapCompaction && IsShaderVisibleCbvSrvUavHeap(type, flag)) + { + return m_shaderVisibleCbvSrvUavPools[m_currentHeapIndex]; + } + else + { + return m_pools[type][flag]; + } + } + + RHI::ResultCode DescriptorContext::CompactDescriptorHeap() + { + //Check if heap compaction is enabled by the user. Since there is an overhead associated with heap compaction it is not enabled by default + if(!m_allowDescriptorHeapCompaction) + { + AZ_Assert( + false, + "Descriptor heap Compaction not allowed. Please consider increasing number of handles allowed for the second value" + "of DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV or enabling AllowDescriptorHeapCompaction within platformlimits.azasset file for dx12."); + return RHI::ResultCode::OutOfMemory; + } + + //We need to ping-pong between two heaps as we cannot compact the active heap without updating it and that is not allowed as + //we need to keep that gpu memory untouched until GPU is finished consuming which can take up to 3 frames. + m_compactionInProgress = true; + DescriptorPool& srcPool = GetPool(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE); + + //Update the currently active heap index + m_currentHeapIndex = !m_currentHeapIndex; + DescriptorPool& destPool = GetPool(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE); + + //Copy over all the static handles first + for (size_t i = 0; i < m_platformLimitsDescriptor->m_numShaderVisibleCbvSrvUavStaticHandles; i++) + { + DescriptorHandle srcHandle(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, D3D12_DESCRIPTOR_HEAP_FLAG_NONE, static_cast(i)); + DescriptorHandle destHandle(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE, static_cast(i)); + m_device->CopyDescriptorsSimple(1, destPool.GetCpuPlatformHandle(destHandle), m_backupStaticHandles.GetCpuPlatformHandle(srcHandle), destHandle.m_type); + } + + //Clone the allocator of the source pool into the destination pool + srcPool.CloneAllocator(destPool.GetAllocator()); + + { + //The mutex is here 'just in case' Compaction is called from more than one thread. + AZStd::scoped_lock lock{ m_srgMapMutex }; + //Re-update all the descriptor tables associated with active SRGs + for (const auto& [srg, numAllocations] : m_srgAllocations) + { + RHI::ResultCode resultCode = static_cast(srg->GetPool())->UpdateDescriptorTableAfterCompaction(*srg, srg->GetData()); + if (resultCode != RHI::ResultCode::Success) + { + return resultCode; + } + } + } + + //Clear the allocator of the source pool + srcPool.ClearAllocator(); + + m_compactionInProgress = false; + + return RHI::ResultCode::Success; + } + + bool DescriptorContext::IsShaderVisibleCbvSrvUavHeap(uint32_t type, uint32_t flag) const + { + return type == D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV && flag == D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE; } } } diff --git a/Gems/Atom/RHI/DX12/Code/Source/RHI/DescriptorContext.h b/Gems/Atom/RHI/DX12/Code/Source/RHI/DescriptorContext.h index d4b6479fb2..88d2781329 100644 --- a/Gems/Atom/RHI/DX12/Code/Source/RHI/DescriptorContext.h +++ b/Gems/Atom/RHI/DX12/Code/Source/RHI/DescriptorContext.h @@ -15,6 +15,8 @@ #include #include #include +#include +#include namespace AZ { @@ -82,12 +84,14 @@ namespace AZ //! Creates a GPU-visible descriptor table. //! @param descriptorHeapType The descriptor heap to allocate from. //! @param descriptorCount The number of descriptors to allocate. + //! @param srg Shader resource group with which the descriptor table is associated with DescriptorTable CreateDescriptorTable( - D3D12_DESCRIPTOR_HEAP_TYPE descriptorHeapType, - uint32_t descriptorCount); - - void ReleaseDescriptorTable(DescriptorTable descriptorTable); + D3D12_DESCRIPTOR_HEAP_TYPE descriptorHeapType, uint32_t descriptorCount, ShaderResourceGroup* srg); + //! Releases a GPU-visible descriptor table. + //! @param descriptorHeapType The descriptor heap to allocate from. + //! @param srg Shader resource group with which the descriptor table is associated with + void ReleaseDescriptorTable(DescriptorTable descriptorTable, ShaderResourceGroup* srg); //! Performs a gather of disjoint CPU-side descriptors and copies to a contiguous GPU-side descriptor table. //! @param gpuDestinationTable The destination descriptor table that the descriptors will be uploaded to. @@ -110,6 +114,8 @@ namespace AZ D3D12_CPU_DESCRIPTOR_HANDLE GetCpuPlatformHandle(DescriptorHandle handle) const; D3D12_GPU_DESCRIPTOR_HANDLE GetGpuPlatformHandle(DescriptorHandle handle) const; + D3D12_CPU_DESCRIPTOR_HANDLE GetCpuPlatformHandleForTable(DescriptorTable descTable) const; + D3D12_GPU_DESCRIPTOR_HANDLE GetGpuPlatformHandleForTable(DescriptorTable descTable) const; void SetDescriptorHeaps(ID3D12GraphicsCommandList* commandList) const; @@ -117,6 +123,12 @@ namespace AZ ID3D12DeviceX* GetDevice(); + //! Since we are only allowed one shader visible CbvSrvUav heap of a limited size in certain hardware, it is possible that + //! it can get fragmented by constant alloc/de-alloc of descriptor tables related to direct views or unbounded resource views within a SRG. We use two + //! heaps to ping pong during compaction as fragmentation can occur many times. It copies static handles directly and for all the + //! dynamic handles we re-update the new heap by copying over the handles from the 'non-shader visible' heap. + RHI::ResultCode CompactDescriptorHeap(); + private: void CopyDescriptor(DescriptorHandle dst, DescriptorHandle src); @@ -129,10 +141,13 @@ namespace AZ DescriptorPool& GetPool(uint32_t type, uint32_t flag); const DescriptorPool& GetPool(uint32_t type, uint32_t flag) const; - DescriptorTable Allocate( - D3D12_DESCRIPTOR_HEAP_TYPE type, - D3D12_DESCRIPTOR_HEAP_FLAGS flags, - uint32_t count); + //! Allocates a Descriptor table which describes a contiguous range of descriptor handles + DescriptorTable AllocateTable(D3D12_DESCRIPTOR_HEAP_TYPE type, D3D12_DESCRIPTOR_HEAP_FLAGS flags, uint32_t count); + + //! Allocates a single descriptor handle + DescriptorHandle AllocateHandle(D3D12_DESCRIPTOR_HEAP_TYPE type, D3D12_DESCRIPTOR_HEAP_FLAGS flags, uint32_t count); + + bool IsShaderVisibleCbvSrvUavHeap(uint32_t type, uint32_t flag) const; static const uint32_t NumHeapFlags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE + 1; static const uint32_t s_descriptorCountMax[D3D12_DESCRIPTOR_HEAP_TYPE_NUM_TYPES][NumHeapFlags]; @@ -147,6 +162,25 @@ namespace AZ DescriptorHandle m_nullSamplerDescriptor; RHI::ConstPtr m_platformLimitsDescriptor; + + // Use 2 heaps below in order to ping-pong between shader visible CbvSrvUav heap when one of them fragments and run out of memory. + static const uint32_t MaxShaderVisibleCbvSrvUavHeaps = 2; + DescriptorPoolShaderVisibleCbvSrvUav m_shaderVisibleCbvSrvUavPools[MaxShaderVisibleCbvSrvUavHeaps]; + //This pool stores a copy of static handles that can later be used to recreate the compacted shader visible CbvSrvUav heap. + DescriptorPool m_backupStaticHandles; + + //Boolean to dictate when compaction was in progress + bool m_compactionInProgress = false; + + //Boolean to dictate if we should support compaction for shader visible CbvSrvUav heap + bool m_allowDescriptorHeapCompaction = false; + + //Map to store active SRGs and the number of associated descriptor tables. This is used to recreate the new compacted heap when we switch heaps + AZStd::unordered_map m_srgAllocations; + AZStd::mutex m_srgMapMutex; + + //Index that holds the currently active shader visible CbvSrvUav heap + uint32_t m_currentHeapIndex = 0; }; } } diff --git a/Gems/Atom/RHI/DX12/Code/Source/RHI/DescriptorPool.cpp b/Gems/Atom/RHI/DX12/Code/Source/RHI/DescriptorPool.cpp index 7a6d96a519..96a3979f41 100644 --- a/Gems/Atom/RHI/DX12/Code/Source/RHI/DescriptorPool.cpp +++ b/Gems/Atom/RHI/DX12/Code/Source/RHI/DescriptorPool.cpp @@ -18,34 +18,38 @@ namespace AZ ID3D12DeviceX* device, D3D12_DESCRIPTOR_HEAP_TYPE type, D3D12_DESCRIPTOR_HEAP_FLAGS flags, - uint32_t descriptorCount) + uint32_t descriptorCountForHeap, + uint32_t descriptorCountForAllocator) { - m_Desc.Type = type; - m_Desc.Flags = flags; - m_Desc.NumDescriptors = descriptorCount; - m_Desc.NodeMask = 1; + m_desc.Type = type; + m_desc.Flags = flags; + m_desc.NumDescriptors = descriptorCountForHeap; + m_desc.NodeMask = 1; ID3D12DescriptorHeap* heap; - DX12::AssertSuccess(device->CreateDescriptorHeap(&m_Desc, IID_GRAPHICS_PPV_ARGS(&heap))); + DX12::AssertSuccess(device->CreateDescriptorHeap(&m_desc, IID_GRAPHICS_PPV_ARGS(&heap))); heap->SetName(L"DescriptorHeap"); - m_DescriptorHeap.Attach(heap); - m_Stride = device->GetDescriptorHandleIncrementSize(m_Desc.Type); + m_descriptorHeap.Attach(heap); + m_stride = device->GetDescriptorHandleIncrementSize(m_desc.Type); - m_CpuStart = heap->GetCPUDescriptorHandleForHeapStart(); - m_GpuStart = {}; - - if (RHI::CheckBitsAny(flags, D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE)) - { - m_GpuStart = heap->GetGPUDescriptorHandleForHeapStart(); - } + m_cpuStart = heap->GetCPUDescriptorHandleForHeapStart(); + m_gpuStart = {}; const bool isGpuVisible = RHI::CheckBitsAll(flags, D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE); + if (isGpuVisible) + { + m_gpuStart = heap->GetGPUDescriptorHandleForHeapStart(); + } + if (isGpuVisible) { RHI::FreeListAllocator::Descriptor descriptor; descriptor.m_alignmentInBytes = 1; - descriptor.m_capacityInBytes = descriptorCount; + + //It is possible for descriptorCountForAllocator to not match descriptorCountForHeap for DescriptorPoolShaderVisibleCbvSrvUav + //heaps in which case descriptorCountForAllocator defines the number of static handles + descriptor.m_capacityInBytes = aznumeric_cast(descriptorCountForAllocator); descriptor.m_garbageCollectLatency = RHI::Limits::Device::FrameCountMax; RHI::FreeListAllocator* allocator = aznew RHI::FreeListAllocator(); @@ -56,10 +60,11 @@ namespace AZ { // Non-shader-visible heaps don't require contiguous descriptors. Therefore, we can allocate // them using a block allocator. + RHI::PoolAllocator::Descriptor descriptor; descriptor.m_alignmentInBytes = 1; descriptor.m_elementSize = 1; - descriptor.m_capacityInBytes = descriptorCount; + descriptor.m_capacityInBytes = aznumeric_cast(descriptorCountForAllocator); descriptor.m_garbageCollectLatency = 0; RHI::PoolAllocator* allocator = aznew RHI::PoolAllocator(); @@ -68,7 +73,7 @@ namespace AZ } } - DescriptorTable DescriptorPool::Allocate(uint32_t count) + DescriptorHandle DescriptorPool::AllocateHandle(uint32_t count) { RHI::VirtualAddress address; { @@ -78,24 +83,34 @@ namespace AZ if (address.IsValid()) { - DescriptorHandle handle(m_Desc.Type, m_Desc.Flags, static_cast(address.m_ptr)); - return DescriptorTable(handle, static_cast(count)); + DescriptorHandle handle(m_desc.Type, m_desc.Flags, static_cast(address.m_ptr)); + return handle; } else { - return DescriptorTable{}; + return DescriptorHandle{}; } } - void DescriptorPool::Release(DescriptorTable table) + void DescriptorPool::ReleaseHandle(DescriptorHandle handle) { - if (table.IsNull()) + if (handle.IsNull()) { return; } AZStd::lock_guard lock(m_mutex); - m_allocator->DeAllocate(RHI::VirtualAddress::CreateFromOffset(table.GetOffset().m_index)); + m_allocator->DeAllocate(RHI::VirtualAddress::CreateFromOffset(handle.m_index)); + } + + DescriptorTable DescriptorPool::AllocateTable(uint32_t count) + { + return DescriptorTable(AllocateHandle(count), static_cast(count)); + } + + void DescriptorPool::ReleaseTable(DescriptorTable table) + { + ReleaseHandle(table.GetOffset()); } void DescriptorPool::GarbageCollect() @@ -106,20 +121,134 @@ namespace AZ ID3D12DescriptorHeap* DescriptorPool::GetPlatformHeap() const { - return m_DescriptorHeap.Get(); + return m_descriptorHeap.Get(); } D3D12_CPU_DESCRIPTOR_HANDLE DescriptorPool::GetCpuPlatformHandle(DescriptorHandle handle) const { AZ_Assert(handle.m_index != DescriptorHandle::NullIndex, "Index is invalid"); - return D3D12_CPU_DESCRIPTOR_HANDLE{ m_CpuStart.ptr + handle.m_index * m_Stride }; + return D3D12_CPU_DESCRIPTOR_HANDLE{ m_cpuStart.ptr + handle.m_index * m_stride }; } D3D12_GPU_DESCRIPTOR_HANDLE DescriptorPool::GetGpuPlatformHandle(DescriptorHandle handle) const { AZ_Assert(handle.IsShaderVisible(), "Handle is not shader visible"); AZ_Assert(handle.m_index != DescriptorHandle::NullIndex, "Index is invalid"); - return D3D12_GPU_DESCRIPTOR_HANDLE{ m_GpuStart.ptr + handle.m_index * m_Stride }; + return D3D12_GPU_DESCRIPTOR_HANDLE{ m_gpuStart.ptr + (handle.m_index * m_stride) }; + } + + D3D12_CPU_DESCRIPTOR_HANDLE DescriptorPool::GetCpuPlatformHandleForTable(DescriptorTable descTable) const + { + DescriptorHandle handle = descTable.GetOffset(); + AZ_Assert(handle.m_index != DescriptorHandle::NullIndex, "Index is invalid"); + return D3D12_CPU_DESCRIPTOR_HANDLE{ m_cpuStart.ptr + handle.m_index * m_stride }; + } + + D3D12_GPU_DESCRIPTOR_HANDLE DescriptorPool::GetGpuPlatformHandleForTable(DescriptorTable descTable) const + { + DescriptorHandle handle = descTable.GetOffset(); + AZ_Assert(handle.IsShaderVisible(), "Handle is not shader visible"); + AZ_Assert(handle.m_index != DescriptorHandle::NullIndex, "Index is invalid"); + return D3D12_GPU_DESCRIPTOR_HANDLE{ m_gpuStart.ptr + (handle.m_index * m_stride) }; + } + + void DescriptorPool::CloneAllocator(RHI::Allocator* newAllocator) + { + m_allocator->Clone(newAllocator); + } + + void DescriptorPool::ClearAllocator() + { + AZ_Assert(m_gpuStart.ptr, "Clearing the allocator is only supported for the gpu visible heap as only this heap can be compacted"); + static_cast(m_allocator.get()) + ->Init(static_cast(m_allocator.get())->GetDescriptor()); + } + + RHI::Allocator* DescriptorPool::GetAllocator() const + { + return m_allocator.get(); + } + + void DescriptorPoolShaderVisibleCbvSrvUav::Init( + ID3D12DeviceX* device, + D3D12_DESCRIPTOR_HEAP_TYPE type, + D3D12_DESCRIPTOR_HEAP_FLAGS flags, + uint32_t descriptorCount, + uint32_t staticHandlesCount) + { + //This pool manages two allocators. The allocator in the base class manages static handles + Base::Init(device, type, flags, descriptorCount, staticHandlesCount); + + //This allocator manages dynamic handles associated with descriptor tables. This allows us to + //reconstruct the full heap in a compact manner if it ever fragments. + RHI::FreeListAllocator::Descriptor descriptor; + descriptor.m_alignmentInBytes = 1; + descriptor.m_capacityInBytes = aznumeric_cast(descriptorCount - staticHandlesCount); + descriptor.m_garbageCollectLatency = RHI::Limits::Device::FrameCountMax; + + RHI::FreeListAllocator* allocator = aznew RHI::FreeListAllocator(); + allocator->Init(descriptor); + m_unboundedArrayAllocator.reset(allocator); + + //Cache the starting point of the dynamic section of the heap + m_startingHandleIndex = staticHandlesCount; + } + + DescriptorTable DescriptorPoolShaderVisibleCbvSrvUav::AllocateTable(uint32_t count) + { + RHI::VirtualAddress address; + { + AZStd::lock_guard lock(m_mutex); + address = m_unboundedArrayAllocator->Allocate(count, 1); + } + + if (address.IsValid()) + { + DescriptorHandle handle(m_desc.Type, m_desc.Flags, static_cast(address.m_ptr)); + return DescriptorTable(handle, static_cast(count)); + } + else + { + return DescriptorTable{}; + } + } + + void DescriptorPoolShaderVisibleCbvSrvUav::ReleaseTable(DescriptorTable table) + { + if (table.IsNull()) + { + return; + } + + AZStd::lock_guard lock(m_mutex); + m_unboundedArrayAllocator->DeAllocate(RHI::VirtualAddress::CreateFromOffset(table.GetOffset().m_index)); + } + + void DescriptorPoolShaderVisibleCbvSrvUav::GarbageCollect() + { + Base::GarbageCollect(); + m_unboundedArrayAllocator->GarbageCollect(); + } + + D3D12_CPU_DESCRIPTOR_HANDLE DescriptorPoolShaderVisibleCbvSrvUav::GetCpuPlatformHandleForTable(DescriptorTable descTable) const + { + DescriptorHandle handle = descTable.GetOffset(); + AZ_Assert(handle.m_index != DescriptorHandle::NullIndex, "Index is invalid"); + return D3D12_CPU_DESCRIPTOR_HANDLE{ m_cpuStart.ptr + (m_startingHandleIndex * m_stride) + (handle.m_index * m_stride) }; + } + + D3D12_GPU_DESCRIPTOR_HANDLE DescriptorPoolShaderVisibleCbvSrvUav::GetGpuPlatformHandleForTable(DescriptorTable descTable) const + { + DescriptorHandle handle = descTable.GetOffset(); + AZ_Assert(handle.IsShaderVisible(), "Handle is not shader visible"); + AZ_Assert(handle.m_index != DescriptorHandle::NullIndex, "Index is invalid"); + return D3D12_GPU_DESCRIPTOR_HANDLE{ m_gpuStart.ptr + (m_startingHandleIndex * m_stride) + (handle.m_index * m_stride) }; + } + + void DescriptorPoolShaderVisibleCbvSrvUav::ClearAllocator() + { + Base::ClearAllocator(); + static_cast(m_unboundedArrayAllocator.get())->Init(static_cast(m_unboundedArrayAllocator.get())->GetDescriptor()); } } } diff --git a/Gems/Atom/RHI/DX12/Code/Source/RHI/DescriptorPool.h b/Gems/Atom/RHI/DX12/Code/Source/RHI/DescriptorPool.h index cd90d1aba3..2237841ea7 100644 --- a/Gems/Atom/RHI/DX12/Code/Source/RHI/DescriptorPool.h +++ b/Gems/Atom/RHI/DX12/Code/Source/RHI/DescriptorPool.h @@ -18,37 +18,91 @@ namespace AZ { namespace DX12 { + //! This class defines a Descriptor pool which manages all the descriptors used for binding resources class DescriptorPool { public: DescriptorPool() = default; + virtual ~DescriptorPool() = default; - void Init( + //! Initialize the native heap as well as init the allocators tracking the memory for descriptor handles + virtual void Init( ID3D12DeviceX* device, D3D12_DESCRIPTOR_HEAP_TYPE type, D3D12_DESCRIPTOR_HEAP_FLAGS flags, - uint32_t descriptorCount); + uint32_t descriptorCountForHeap, + uint32_t descriptorCountForAllocator); ID3D12DescriptorHeap* GetPlatformHeap() const; - DescriptorTable Allocate(uint32_t count = 1); + //! Allocate a Descriptor handles + DescriptorHandle AllocateHandle(uint32_t count = 1); + //! Release a descriptor handle + void ReleaseHandle(DescriptorHandle table); + //! Allocate a range contiguous handles (i.e Descriptor table) + virtual DescriptorTable AllocateTable(uint32_t count = 1); + //! Release a range contiguous handles (i.e Descriptor table) + virtual void ReleaseTable(DescriptorTable table); + //! Garbage collection for freed handles or tables + virtual void GarbageCollect(); + //Get native pointers from the heap + virtual D3D12_CPU_DESCRIPTOR_HANDLE GetCpuPlatformHandleForTable(DescriptorTable handle) const; + virtual D3D12_GPU_DESCRIPTOR_HANDLE GetGpuPlatformHandleForTable(DescriptorTable handle) const; + //Clear the tracking allocator + virtual void ClearAllocator(); - void Release(DescriptorTable table); - - void GarbageCollect(); D3D12_CPU_DESCRIPTOR_HANDLE GetCpuPlatformHandle(DescriptorHandle handle) const; D3D12_GPU_DESCRIPTOR_HANDLE GetGpuPlatformHandle(DescriptorHandle handle) const; - private: - D3D12_CPU_DESCRIPTOR_HANDLE m_CpuStart = {}; - D3D12_GPU_DESCRIPTOR_HANDLE m_GpuStart = {}; - D3D12_CPU_DESCRIPTOR_HANDLE m_NullDescriptor = {}; - uint32_t m_Stride = 0; - D3D12_DESCRIPTOR_HEAP_DESC m_Desc; - Microsoft::WRL::ComPtr m_DescriptorHeap; + //Clone the tracking allocator + void CloneAllocator(RHI::Allocator* newAllocator); + RHI::Allocator* GetAllocator() const; + + protected: + D3D12_DESCRIPTOR_HEAP_DESC m_desc; AZStd::mutex m_mutex; + D3D12_CPU_DESCRIPTOR_HANDLE m_cpuStart = {}; + D3D12_GPU_DESCRIPTOR_HANDLE m_gpuStart = {}; + uint32_t m_stride = 0; + private: + + // Native heap + Microsoft::WRL::ComPtr m_descriptorHeap; + + // Allocator used to manage the whole native heap. In the case of DescriptorPoolShaderVisibleCbvSrvUav this allocator + // is used to manage the part of the heap that only manages static handles. AZStd::unique_ptr m_allocator; }; + + //! A specialized pool that specifically handles Descriptor tables for Cbv/Srv/Uav views and allows for Compaction + //! Specifically this pool handles the dynamic part of the heap + class DescriptorPoolShaderVisibleCbvSrvUav : public DescriptorPool + { + using Base = DescriptorPool; + + public: + void Init( + ID3D12DeviceX* device, + D3D12_DESCRIPTOR_HEAP_TYPE type, + D3D12_DESCRIPTOR_HEAP_FLAGS flags, + uint32_t descriptorCount, + uint32_t staticHandlesCount); + + DescriptorTable AllocateTable(uint32_t count = 1) override; + void ReleaseTable(DescriptorTable table) override; + void GarbageCollect() override; + + D3D12_CPU_DESCRIPTOR_HANDLE GetCpuPlatformHandleForTable(DescriptorTable handle) const override; + D3D12_GPU_DESCRIPTOR_HANDLE GetGpuPlatformHandleForTable(DescriptorTable handle) const override; + void ClearAllocator() override; + + private: + + // A separate allocator that handles descriptor tables which are dynamic in nature and may fragment and require compaction + AZStd::unique_ptr m_unboundedArrayAllocator; + //Starting index of the dynamic part of the heap + uint32_t m_startingHandleIndex = 0; + }; } } diff --git a/Gems/Atom/RHI/DX12/Code/Source/RHI/Device.cpp b/Gems/Atom/RHI/DX12/Code/Source/RHI/Device.cpp index 82e4aba57c..6f614499d2 100644 --- a/Gems/Atom/RHI/DX12/Code/Source/RHI/Device.cpp +++ b/Gems/Atom/RHI/DX12/Code/Source/RHI/Device.cpp @@ -625,5 +625,20 @@ namespace AZ { return m_isAftermathInitialized; } + + RHI::ResultCode Device::CompactSRGMemory() + { + if (m_isDescriptorHeapCompactionNeeded) + { + m_isDescriptorHeapCompactionNeeded = false; + return m_descriptorContext->CompactDescriptorHeap(); + } + return RHI::ResultCode::Success; + } + + void Device::DescriptorHeapCompactionNeeded() + { + m_isDescriptorHeapCompactionNeeded = true; + } } } diff --git a/Gems/Atom/RHI/DX12/Code/Source/RHI/Device.h b/Gems/Atom/RHI/DX12/Code/Source/RHI/Device.h index 7004900ec5..40e26db891 100644 --- a/Gems/Atom/RHI/DX12/Code/Source/RHI/Device.h +++ b/Gems/Atom/RHI/DX12/Code/Source/RHI/Device.h @@ -98,39 +98,27 @@ namespace AZ D3D12_RESOURCE_STATES initialState, ImageTileLayout& imageTilingInfo); - /** - * Queues a DX12 COM object for release (by taking a reference) after the current frame has flushed - * through the GPU. - */ + //! Queues a DX12 COM object for release (by taking a reference) after the current frame has flushed + //! through the GPU. void QueueForRelease(RHI::Ptr dx12Object); - /** - * Queues the backing Memory instance of a MemoryView for release (by taking a reference) after the - * current frame has flushed through the GPU. The reference on the MemoryView itself is not released. - */ + //! Queues the backing Memory instance of a MemoryView for release (by taking a reference) after the + //! current frame has flushed through the GPU. The reference on the MemoryView itself is not released. void QueueForRelease(const MemoryView& memoryView); - /** - * Allocates host memory from the internal frame allocator that is suitable for staging - * uploads to the GPU for the current frame. The memory is valid for the lifetime of - * the frame and is automatically reclaimed after the frame has completed on the GPU. - */ + //! Allocates host memory from the internal frame allocator that is suitable for staging + //! uploads to the GPU for the current frame. The memory is valid for the lifetime of + //! the frame and is automatically reclaimed after the frame has completed on the GPU. MemoryView AcquireStagingMemory(size_t size, size_t alignment); - /** - * Acquires a pipeline layout from the internal cache. - */ + //! Acquires a pipeline layout from the internal cache. RHI::ConstPtr AcquirePipelineLayout(const RHI::PipelineLayoutDescriptor& descriptor); - /** - * Acquires a new command list for the frame given the hardware queue class. The command list is - * automatically reclaimed after the current frame has flushed through the GPU. - */ + //! Acquires a new command list for the frame given the hardware queue class. The command list is + //! automatically reclaimed after the current frame has flushed through the GPU. CommandList* AcquireCommandList(RHI::HardwareQueueClass hardwareQueueClass); - /** - * Acquires a sampler from the internal cache. - */ + //! Acquires a sampler from the internal cache. RHI::ConstPtr AcquireSampler(const RHI::SamplerState& state); const PhysicalDevice& GetPhysicalDevice() const; @@ -146,6 +134,10 @@ namespace AZ AsyncUploadQueue& GetAsyncUploadQueue(); bool IsAftermathInitialized() const; + + //! Indicate that we need to compact the shader visible srv/uav/cbv shader visible heap. + void DescriptorHeapCompactionNeeded(); + private: Device(); @@ -167,6 +159,7 @@ namespace AZ RHI::ResourceMemoryRequirements GetResourceMemoryRequirements(const RHI::ImageDescriptor & descriptor) override; RHI::ResourceMemoryRequirements GetResourceMemoryRequirements(const RHI::BufferDescriptor & descriptor) override; void ObjectCollectionNotify(RHI::ObjectCollectorNotifyFunction notifyFunction) override; + RHI::ResultCode CompactSRGMemory() override; ////////////////////////////////////////////////////////////////////////// RHI::ResultCode InitSubPlatform(RHI::PhysicalDevice& physicalDevice); @@ -198,6 +191,9 @@ namespace AZ AZStd::mutex m_samplerCacheMutex; bool m_isAftermathInitialized = false; + + // Boolean used to compact the view specific shader visible heap + bool m_isDescriptorHeapCompactionNeeded = false; }; } } diff --git a/Gems/Atom/RHI/DX12/Code/Source/RHI/PipelineLibrary.cpp b/Gems/Atom/RHI/DX12/Code/Source/RHI/PipelineLibrary.cpp index 66b3f9623a..63625fde72 100644 --- a/Gems/Atom/RHI/DX12/Code/Source/RHI/PipelineLibrary.cpp +++ b/Gems/Atom/RHI/DX12/Code/Source/RHI/PipelineLibrary.cpp @@ -49,9 +49,11 @@ namespace AZ AZStd::array_view bytes; bool shouldCreateLibFromSerializedData = true; - if (RHI::Factory::Get().IsRenderDocModuleLoaded() || RHI::Factory::Get().IsPixModuleLoaded()) + if (RHI::Factory::Get().IsRenderDocModuleLoaded() || + RHI::Factory::Get().IsPixModuleLoaded() || + RHI::Factory::Get().UsingWarpDevice()) { - // CreatePipelineLibrary api does not function properly if Renderdoc or Pix is enabled + // CreatePipelineLibrary api does not function properly if Renderdoc, Pix or Warp is enabled shouldCreateLibFromSerializedData = false; } @@ -215,9 +217,11 @@ namespace AZ RHI::ResultCode PipelineLibrary::MergeIntoInternal([[maybe_unused]] AZStd::array_view pipelineLibraries) { - if (RHI::Factory::Get().IsRenderDocModuleLoaded() || RHI::Factory::Get().IsPixModuleLoaded()) + if (RHI::Factory::Get().IsRenderDocModuleLoaded() || + RHI::Factory::Get().IsPixModuleLoaded() || + RHI::Factory::Get().UsingWarpDevice()) { - // StorePipeline api does not function properly if RenderDoc or Pix is enabled + // StorePipeline api does not function properly if RenderDoc, Pix or Warp is enabled return RHI::ResultCode::Fail; } diff --git a/Gems/Atom/RHI/DX12/Code/Source/RHI/ShaderResourceGroup.h b/Gems/Atom/RHI/DX12/Code/Source/RHI/ShaderResourceGroup.h index 4c686fe525..0a7185ccd5 100644 --- a/Gems/Atom/RHI/DX12/Code/Source/RHI/ShaderResourceGroup.h +++ b/Gems/Atom/RHI/DX12/Code/Source/RHI/ShaderResourceGroup.h @@ -51,6 +51,7 @@ namespace AZ ShaderResourceGroup() = default; friend class ShaderResourceGroupPool; + friend class DescriptorContext; /// The current index into the compiled data array. uint32_t m_compiledDataIndex = 0; diff --git a/Gems/Atom/RHI/DX12/Code/Source/RHI/ShaderResourceGroupPool.cpp b/Gems/Atom/RHI/DX12/Code/Source/RHI/ShaderResourceGroupPool.cpp index d51fdd3495..bd648cf535 100644 --- a/Gems/Atom/RHI/DX12/Code/Source/RHI/ShaderResourceGroupPool.cpp +++ b/Gems/Atom/RHI/DX12/Code/Source/RHI/ShaderResourceGroupPool.cpp @@ -132,33 +132,17 @@ namespace AZ compiledData.m_cpuConstantAddress = cpuAddress + m_constantBufferSize * i; } } - - if (m_viewsDescriptorTableSize) - { - group.m_viewsDescriptorTable = m_descriptorContext->CreateDescriptorTable(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, m_viewsDescriptorTableRingSize); - - if (!group.m_viewsDescriptorTable.IsValid()) - { - AZ_Error("ShaderResourceGroupPool", false, "Descriptor context failed to allocate view descriptor table. Try increasing the limits specified in platformlimits.azasset file for dx12"); - return RHI::ResultCode::OutOfMemory; - } - - for (uint32_t i = 0; i < copyCount; ++i) - { - const DescriptorHandle descriptorHandle = group.m_viewsDescriptorTable.GetOffset() + m_viewsDescriptorTableSize * i; - - ShaderResourceGroupCompiledData& compiledData = group.m_compiledData[i]; - compiledData.m_gpuViewsDescriptorHandle = m_descriptorContext->GetGpuPlatformHandle(descriptorHandle); - } - } - + if (m_samplersDescriptorTableSize) { - group.m_samplersDescriptorTable = m_descriptorContext->CreateDescriptorTable(D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER, m_samplersDescriptorTableRingSize); + group.m_samplersDescriptorTable = m_descriptorContext->CreateDescriptorTable(D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER, m_samplersDescriptorTableRingSize, &group); if (!group.m_samplersDescriptorTable.IsValid()) { - AZ_Error("ShaderResourceGroupPool", false, "Descriptor context failed to allocate sampler descriptor table. Try increasing the limits specified in platformlimits.azasset file for dx12."); + AZ_Error( + "ShaderResourceGroupPool", false, + "Descriptor context failed to allocate sampler descriptor table. Please consider increasing number of handles " + "allowed for the second value of DESCRIPTOR_HEAP_TYPE_SAMPLER within platformlimits.azasset file for dx12."); return RHI::ResultCode::OutOfMemory; } @@ -167,7 +151,7 @@ namespace AZ const DescriptorHandle descriptorHandle = group.m_samplersDescriptorTable.GetOffset() + m_samplersDescriptorTableSize * i; ShaderResourceGroupCompiledData& compiledData = group.m_compiledData[i]; - compiledData.m_gpuSamplersDescriptorHandle = m_descriptorContext->GetGpuPlatformHandle(descriptorHandle); + compiledData.m_gpuSamplersDescriptorHandle = m_descriptorContext->GetGpuPlatformHandleForTable(DescriptorTable(descriptorHandle, static_cast(m_samplersDescriptorTableSize))); } } @@ -186,19 +170,25 @@ namespace AZ if (m_viewsDescriptorTableSize) { - m_descriptorContext->ReleaseDescriptorTable(group.m_viewsDescriptorTable); + if (group.m_viewsDescriptorTable.IsValid()) + { + m_descriptorContext->ReleaseDescriptorTable(group.m_viewsDescriptorTable, &group); + } } if (m_samplersDescriptorTableSize) { - m_descriptorContext->ReleaseDescriptorTable(group.m_samplersDescriptorTable); + if (group.m_viewsDescriptorTable.IsValid()) + { + m_descriptorContext->ReleaseDescriptorTable(group.m_samplersDescriptorTable, &group); + } } for (uint32_t unboundedArrayindex = 0; unboundedArrayindex < (ShaderResourceGroupCompiledData::MaxUnboundedArrays * RHI::Limits::Device::FrameCountMax); ++unboundedArrayindex) { if (group.m_unboundedDescriptorTables[unboundedArrayindex].IsValid()) { - m_descriptorContext->ReleaseDescriptorTable(group.m_unboundedDescriptorTables[unboundedArrayindex]); + m_descriptorContext->ReleaseDescriptorTable(group.m_unboundedDescriptorTables[unboundedArrayindex], &group); } } @@ -213,6 +203,7 @@ namespace AZ const RHI::ShaderResourceGroupData& groupData) { ShaderResourceGroup& group = static_cast(groupBase); + auto& device = static_cast(GetDevice()); group.m_compiledDataIndex = (group.m_compiledDataIndex + 1) % RHI::Limits::Device::FrameCountMax; if (m_constantBufferSize) @@ -222,6 +213,22 @@ namespace AZ if (m_viewsDescriptorTableSize) { + //Lazy initialization for cbv/srv/uav Descriptor Tables + if (!group.m_viewsDescriptorTable.IsValid()) + { + group.m_viewsDescriptorTable = m_descriptorContext->CreateDescriptorTable( + D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, m_viewsDescriptorTableRingSize, &group); + + if (!group.m_viewsDescriptorTable.IsValid()) + { + //We have support for compacting D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV (if applicable) so try that. + device.DescriptorHeapCompactionNeeded(); + return RHI::ResultCode::Success; + } + + CacheGpuHandlesForViews(group); + } + const DescriptorTable descriptorTable( group.m_viewsDescriptorTable.GetOffset() + group.m_compiledDataIndex * m_viewsDescriptorTableSize, static_cast(m_viewsDescriptorTableSize)); @@ -246,6 +253,18 @@ namespace AZ return RHI::ResultCode::Success; } + void ShaderResourceGroupPool::CacheGpuHandlesForViews(ShaderResourceGroup& group) + { + for (uint32_t i = 0; i < RHI::Limits::Device::FrameCountMax; ++i) + { + const DescriptorHandle descriptorHandle = group.m_viewsDescriptorTable.GetOffset() + m_viewsDescriptorTableSize * i; + + ShaderResourceGroupCompiledData& compiledData = group.m_compiledData[i]; + compiledData.m_gpuViewsDescriptorHandle = m_descriptorContext->GetGpuPlatformHandleForTable( + DescriptorTable(descriptorHandle, static_cast(m_viewsDescriptorTableSize))); + } + } + void ShaderResourceGroupPool::UpdateViewsDescriptorTable(DescriptorTable descriptorTable, const RHI::ShaderResourceGroupData& groupData) { const RHI::ShaderResourceGroupLayout& groupLayout = *groupData.GetLayout(); @@ -261,27 +280,27 @@ namespace AZ AZStd::vector descriptorHandles; switch (descriptorRangeType) { - case D3D12_DESCRIPTOR_RANGE_TYPE_SRV: - { - descriptorHandles = GetSRVsFromImageViews< RHI::BufferView, BufferView> (bufferViews, D3D12_SRV_DIMENSION_BUFFER); - break; + case D3D12_DESCRIPTOR_RANGE_TYPE_SRV: + { + descriptorHandles = GetSRVsFromImageViews< RHI::BufferView, BufferView> (bufferViews, D3D12_SRV_DIMENSION_BUFFER); + break; + } + case D3D12_DESCRIPTOR_RANGE_TYPE_UAV: + { + descriptorHandles = GetUAVsFromImageViews(bufferViews, D3D12_UAV_DIMENSION_BUFFER); + break; + } + case D3D12_DESCRIPTOR_RANGE_TYPE_CBV: + { + descriptorHandles = GetCBVsFromBufferViews(bufferViews); + break; + } + default: + AZ_Assert(false, "Unhandled D3D12_DESCRIPTOR_RANGE_TYPE enumeration"); + break; } - case D3D12_DESCRIPTOR_RANGE_TYPE_UAV: - { - descriptorHandles = GetUAVsFromImageViews(bufferViews, D3D12_UAV_DIMENSION_BUFFER); - break; - } - case D3D12_DESCRIPTOR_RANGE_TYPE_CBV: - { - descriptorHandles = GetCBVsFromBufferViews(bufferViews); - break; - } - default: - AZ_Assert(false, "Unhandled D3D12_DESCRIPTOR_RANGE_TYPE enumeration"); - break; - } - UpdateDescriptorTableRange(descriptorTable, descriptorHandles, bufferInputIndex); + UpdateDescriptorTableRange(descriptorTable, descriptorHandles, bufferInputIndex); ++shaderInputIndex; } @@ -297,23 +316,24 @@ namespace AZ AZStd::vector descriptorHandles; switch (descriptorRangeType) { - case D3D12_DESCRIPTOR_RANGE_TYPE_SRV: - { - descriptorHandles = GetSRVsFromImageViews(imageViews, ConvertSRVDimension(shaderInputImage.m_type)); - break; - } - case D3D12_DESCRIPTOR_RANGE_TYPE_UAV: - { - descriptorHandles = GetUAVsFromImageViews(imageViews, ConvertUAVDimension(shaderInputImage.m_type)); - break; - } - default: + case D3D12_DESCRIPTOR_RANGE_TYPE_SRV: + { + descriptorHandles = + GetSRVsFromImageViews(imageViews, ConvertSRVDimension(shaderInputImage.m_type)); + break; + } + case D3D12_DESCRIPTOR_RANGE_TYPE_UAV: + { + descriptorHandles = + GetUAVsFromImageViews(imageViews, ConvertUAVDimension(shaderInputImage.m_type)); + break; + } + default: AZ_Assert(false, "Unhandled D3D12_DESCRIPTOR_RANGE_TYPE enumeration"); break; } UpdateDescriptorTableRange(descriptorTable, descriptorHandles, imageInputIndex); - ++shaderInputIndex; } } @@ -334,7 +354,7 @@ namespace AZ void ShaderResourceGroupPool::UpdateUnboundedArrayDescriptorTables(ShaderResourceGroup& group, const RHI::ShaderResourceGroupData& groupData) { const RHI::ShaderResourceGroupLayout& groupLayout = *groupData.GetLayout(); - + auto& device = static_cast(GetDevice()); uint32_t shaderInputIndex = 0; // process buffer unbounded arrays @@ -350,50 +370,30 @@ namespace AZ { if (group.m_unboundedDescriptorTables[tableIndex].IsValid()) { - m_descriptorContext->ReleaseDescriptorTable(group.m_unboundedDescriptorTables[tableIndex]); + m_descriptorContext->ReleaseDescriptorTable(group.m_unboundedDescriptorTables[tableIndex], &group); group.m_unboundedDescriptorTables[tableIndex] = DescriptorTable{}; } if (!bufferViews.empty()) { - group.m_unboundedDescriptorTables[tableIndex] = m_descriptorContext->CreateDescriptorTable(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, static_cast(bufferViews.size())); - AZ_Assert(group.m_unboundedDescriptorTables[tableIndex].IsValid(), "Descriptor context failed to allocate unbounded array descriptor table, most likely out of memory."); + group.m_unboundedDescriptorTables[tableIndex] = m_descriptorContext->CreateDescriptorTable( + D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, static_cast(bufferViews.size()), &group); + + if (!group.m_unboundedDescriptorTables[tableIndex].IsValid()) + { + // We have support for compacting D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV (if applicable) so try that. + device.DescriptorHeapCompactionNeeded(); + return; + } ShaderResourceGroupCompiledData& compiledData = group.m_compiledData[group.m_compiledDataIndex]; - compiledData.m_gpuUnboundedArraysDescriptorHandles[shaderInputIndex] = m_descriptorContext->GetGpuPlatformHandle(group.m_unboundedDescriptorTables[tableIndex].GetOffset()); + compiledData.m_gpuUnboundedArraysDescriptorHandles[shaderInputIndex] = m_descriptorContext->GetGpuPlatformHandleForTable(group.m_unboundedDescriptorTables[tableIndex]); } } - - ++shaderInputIndex; - - if (bufferViews.empty()) - { - // we don't need to update descriptors since the buffer list is empty - continue; - } - - D3D12_DESCRIPTOR_RANGE_TYPE descriptorRangeType = ConvertShaderInputBufferAccess(shaderInputBufferUnboundedArray.m_access); - - AZStd::vector descriptorHandles; - switch (descriptorRangeType) - { - case D3D12_DESCRIPTOR_RANGE_TYPE_SRV: - { - descriptorHandles = GetSRVsFromImageViews(bufferViews, D3D12_SRV_DIMENSION_BUFFER); - break; - } - case D3D12_DESCRIPTOR_RANGE_TYPE_UAV: - { - descriptorHandles = GetUAVsFromImageViews(bufferViews, D3D12_UAV_DIMENSION_BUFFER); - break; - } - default: - AZ_Assert(false, "Unhandled D3D12_DESCRIPTOR_RANGE_TYPE enumeration"); - break; - } - + const DescriptorTable descriptorTable(group.m_unboundedDescriptorTables[tableIndex].GetOffset(), static_cast(bufferViews.size())); - m_descriptorContext->UpdateDescriptorTableRange(descriptorTable, descriptorHandles.data(), D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV); + UpdateUnboundedBuffersDescTable(descriptorTable, groupData, shaderInputIndex, shaderInputBufferUnboundedArray.m_access); + ++shaderInputIndex; } // process image unbounded arrays @@ -407,55 +407,223 @@ namespace AZ // resize the descriptor table allocation if necessary if (group.m_unboundedDescriptorTables[tableIndex].GetSize() != imageViews.size()) { + if (group.m_unboundedDescriptorTables[tableIndex].IsValid()) { - m_descriptorContext->ReleaseDescriptorTable(group.m_unboundedDescriptorTables[tableIndex]); + m_descriptorContext->ReleaseDescriptorTable(group.m_unboundedDescriptorTables[tableIndex], &group); group.m_unboundedDescriptorTables[tableIndex] = DescriptorTable{}; } if (!imageViews.empty()) { - group.m_unboundedDescriptorTables[tableIndex] = m_descriptorContext->CreateDescriptorTable(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, static_cast(imageViews.size())); - AZ_Assert(group.m_unboundedDescriptorTables[tableIndex].IsValid(), "Descriptor context failed to allocate unbounded array descriptor table, most likely out of memory."); + group.m_unboundedDescriptorTables[tableIndex] = m_descriptorContext->CreateDescriptorTable( + D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, static_cast(imageViews.size()), &group); + + if (!group.m_unboundedDescriptorTables[tableIndex].IsValid()) + { + // We have support for compacting D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV (if applicable) so try that + device.DescriptorHeapCompactionNeeded(); + return; + } ShaderResourceGroupCompiledData& compiledData = group.m_compiledData[group.m_compiledDataIndex]; - compiledData.m_gpuUnboundedArraysDescriptorHandles[shaderInputIndex] = m_descriptorContext->GetGpuPlatformHandle(group.m_unboundedDescriptorTables[tableIndex].GetOffset()); + compiledData.m_gpuUnboundedArraysDescriptorHandles[shaderInputIndex] = m_descriptorContext->GetGpuPlatformHandleForTable(group.m_unboundedDescriptorTables[tableIndex]); } } - ++shaderInputIndex; - - if (imageViews.empty()) - { - // we don't need to update descriptors since the image list is empty - continue; - } - - D3D12_DESCRIPTOR_RANGE_TYPE descriptorRangeType = ConvertShaderInputImageAccess(shaderInputImageUnboundedArray.m_access); - - AZStd::vector descriptorHandles; - switch (descriptorRangeType) - { - case D3D12_DESCRIPTOR_RANGE_TYPE_SRV: - { - descriptorHandles = GetSRVsFromImageViews(imageViews, ConvertSRVDimension(shaderInputImageUnboundedArray.m_type)); - break; - } - case D3D12_DESCRIPTOR_RANGE_TYPE_UAV: - { - descriptorHandles = GetUAVsFromImageViews(imageViews, ConvertUAVDimension(shaderInputImageUnboundedArray.m_type)); - break; - } - default: - AZ_Assert(false, "Unhandled D3D12_DESCRIPTOR_RANGE_TYPE enumeration"); - break; - } - const DescriptorTable descriptorTable(group.m_unboundedDescriptorTables[tableIndex].GetOffset(), static_cast(imageViews.size())); - m_descriptorContext->UpdateDescriptorTableRange(descriptorTable, descriptorHandles.data(), D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV); + UpdateUnboundedImagesDescTable(descriptorTable, groupData, shaderInputIndex, shaderInputImageUnboundedArray.m_access, shaderInputImageUnboundedArray.m_type); + ++shaderInputIndex; } } + void ShaderResourceGroupPool::UpdateUnboundedBuffersDescTable( + DescriptorTable descriptorTable, + const RHI::ShaderResourceGroupData& groupData, + uint32_t shaderInputIndex, + RHI::ShaderInputBufferAccess bufferAccess) + { + const RHI::ShaderInputBufferUnboundedArrayIndex bufferUnboundedArrayInputIndex(shaderInputIndex); + AZStd::array_view> bufferViews = + groupData.GetBufferViewUnboundedArray(bufferUnboundedArrayInputIndex); + + if (bufferViews.empty()) + { + // we don't need to update descriptors since the buffer list is empty + return; + } + + D3D12_DESCRIPTOR_RANGE_TYPE descriptorRangeType = ConvertShaderInputBufferAccess(bufferAccess); + + AZStd::vector descriptorHandles; + switch (descriptorRangeType) + { + case D3D12_DESCRIPTOR_RANGE_TYPE_SRV: + { + descriptorHandles = GetSRVsFromImageViews(bufferViews, D3D12_SRV_DIMENSION_BUFFER); + break; + } + case D3D12_DESCRIPTOR_RANGE_TYPE_UAV: + { + descriptorHandles = GetUAVsFromImageViews(bufferViews, D3D12_UAV_DIMENSION_BUFFER); + break; + } + default: + AZ_Assert(false, "Unhandled D3D12_DESCRIPTOR_RANGE_TYPE enumeration"); + break; + } + + m_descriptorContext->UpdateDescriptorTableRange( + descriptorTable, descriptorHandles.data(), D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV); + } + + void ShaderResourceGroupPool::UpdateUnboundedImagesDescTable( + DescriptorTable descriptorTable, + const RHI::ShaderResourceGroupData& groupData, + uint32_t shaderInputIndex, + RHI::ShaderInputImageAccess imageAccess, + RHI::ShaderInputImageType imageType) + { + const RHI::ShaderInputImageUnboundedArrayIndex imageUnboundedArrayInputIndex(shaderInputIndex); + AZStd::array_view> imageViews = + groupData.GetImageViewUnboundedArray(imageUnboundedArrayInputIndex); + + if (imageViews.empty()) + { + // we don't need to update descriptors since the image list is empty + return; + } + + D3D12_DESCRIPTOR_RANGE_TYPE descriptorRangeType = ConvertShaderInputImageAccess(imageAccess); + + AZStd::vector descriptorHandles; + switch (descriptorRangeType) + { + case D3D12_DESCRIPTOR_RANGE_TYPE_SRV: + { + descriptorHandles = GetSRVsFromImageViews(imageViews, ConvertSRVDimension(imageType)); + break; + } + case D3D12_DESCRIPTOR_RANGE_TYPE_UAV: + { + descriptorHandles = GetUAVsFromImageViews(imageViews, ConvertUAVDimension(imageType)); + break; + } + default: + AZ_Assert(false, "Unhandled D3D12_DESCRIPTOR_RANGE_TYPE enumeration"); + break; + } + + m_descriptorContext->UpdateDescriptorTableRange( + descriptorTable, descriptorHandles.data(), D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV); + } + + RHI::ResultCode ShaderResourceGroupPool::UpdateDescriptorTableAfterCompaction( + RHI::ShaderResourceGroup& groupBase, const RHI::ShaderResourceGroupData& groupData) + { + // Since we are trying to compact we will re-create all the descriptor tables and re-update them all + ShaderResourceGroup& group = static_cast(groupBase); + + if (m_viewsDescriptorTableSize) + { + group.m_viewsDescriptorTable = m_descriptorContext->CreateDescriptorTable( + D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, m_viewsDescriptorTableRingSize, &group); + + if (!group.m_viewsDescriptorTable.IsValid()) + { + AZ_Assert( + false, + "Descriptor heap ran out of memory. Please consider increasing number of handles allowed for the second value" + "of DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV within platformlimits.azasset file for dx12."); + return RHI::ResultCode::OutOfMemory; + } + + CacheGpuHandlesForViews(group); + + const DescriptorTable descriptorTable( + group.m_viewsDescriptorTable.GetOffset() + group.m_compiledDataIndex * m_viewsDescriptorTableSize, + static_cast(m_viewsDescriptorTableSize)); + + UpdateViewsDescriptorTable(descriptorTable, groupData); + } + + if (m_unboundedArrayCount) + { + //Reset all the old descriptor tables as the previous heap is gone. + for (uint32_t unboundedArrayindex = 0; unboundedArrayindex < (ShaderResourceGroupCompiledData::MaxUnboundedArrays * RHI::Limits::Device::FrameCountMax); ++unboundedArrayindex) + { + group.m_unboundedDescriptorTables[unboundedArrayindex] = DescriptorTable{}; + } + + const RHI::ShaderResourceGroupLayout& groupLayout = *groupData.GetLayout(); + uint32_t shaderInputIndex = 0; + + // process buffer unbounded arrays + for (const RHI::ShaderInputBufferUnboundedArrayDescriptor& shaderInputBufferUnboundedArray : groupLayout.GetShaderInputListForBufferUnboundedArrays()) + { + const RHI::ShaderInputBufferUnboundedArrayIndex bufferUnboundedArrayInputIndex(shaderInputIndex); + AZStd::array_view> bufferViews = groupData.GetBufferViewUnboundedArray(bufferUnboundedArrayInputIndex); + + uint32_t tableIndex = shaderInputIndex * RHI::Limits::Device::FrameCountMax + group.m_compiledDataIndex; + if (!bufferViews.empty()) + { + group.m_unboundedDescriptorTables[tableIndex] = m_descriptorContext->CreateDescriptorTable( + D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, static_cast(bufferViews.size()), &group); + + if (!group.m_unboundedDescriptorTables[tableIndex].IsValid()) + { + AZ_Assert( + false, + "Descriptor heap ran out of memory. Please consider increasing number of handles allowed for the second value" + "of DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV within platformlimits.azasset file for dx12."); + return RHI::ResultCode::OutOfMemory; + } + + ShaderResourceGroupCompiledData& compiledData = group.m_compiledData[group.m_compiledDataIndex]; + compiledData.m_gpuUnboundedArraysDescriptorHandles[shaderInputIndex] = m_descriptorContext->GetGpuPlatformHandleForTable(group.m_unboundedDescriptorTables[tableIndex]); + + const DescriptorTable descriptorTable( + group.m_unboundedDescriptorTables[tableIndex].GetOffset(), static_cast(bufferViews.size())); + UpdateUnboundedBuffersDescTable(descriptorTable, groupData, shaderInputIndex, shaderInputBufferUnboundedArray.m_access); + } + shaderInputIndex++; + } + + // process image unbounded arrays + for (const RHI::ShaderInputImageUnboundedArrayDescriptor& shaderInputImageUnboundedArray : + groupLayout.GetShaderInputListForImageUnboundedArrays()) + { + const RHI::ShaderInputImageUnboundedArrayIndex imageUnboundedArrayInputIndex(shaderInputIndex); + AZStd::array_view> imageViews = + groupData.GetImageViewUnboundedArray(imageUnboundedArrayInputIndex); + + uint32_t tableIndex = shaderInputIndex * RHI::Limits::Device::FrameCountMax + group.m_compiledDataIndex; + if (!imageViews.empty()) + { + group.m_unboundedDescriptorTables[tableIndex] = m_descriptorContext->CreateDescriptorTable( + D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, static_cast(imageViews.size()), &group); + + if (!group.m_unboundedDescriptorTables[tableIndex].IsValid()) + { + AZ_Assert( + false, + "Descriptor heap ran out of memory. Please consider increasing number of handles allowed for the second value" + "of DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV within platformlimits.azasset file for dx12."); + return RHI::ResultCode::OutOfMemory; + } + + ShaderResourceGroupCompiledData& compiledData = group.m_compiledData[group.m_compiledDataIndex]; + compiledData.m_gpuUnboundedArraysDescriptorHandles[shaderInputIndex] = m_descriptorContext->GetGpuPlatformHandleForTable(group.m_unboundedDescriptorTables[tableIndex]); + + const DescriptorTable descriptorTable(group.m_unboundedDescriptorTables[tableIndex].GetOffset(), static_cast(imageViews.size())); + UpdateUnboundedImagesDescTable(descriptorTable, groupData, shaderInputIndex, shaderInputImageUnboundedArray.m_access, shaderInputImageUnboundedArray.m_type); + } + shaderInputIndex++; + } + } + return RHI::ResultCode::Success; + } + void ShaderResourceGroupPool::OnFrameEnd() { m_constantAllocator.GarbageCollect(); diff --git a/Gems/Atom/RHI/DX12/Code/Source/RHI/ShaderResourceGroupPool.h b/Gems/Atom/RHI/DX12/Code/Source/RHI/ShaderResourceGroupPool.h index bbe7e6596d..e1b2145097 100644 --- a/Gems/Atom/RHI/DX12/Code/Source/RHI/ShaderResourceGroupPool.h +++ b/Gems/Atom/RHI/DX12/Code/Source/RHI/ShaderResourceGroupPool.h @@ -30,6 +30,9 @@ namespace AZ static RHI::Ptr Create(); + //! Re-Update the descriptor tables for all the cbv/srv/uav views (direct and via unbounded array) + RHI::ResultCode UpdateDescriptorTableAfterCompaction(RHI::ShaderResourceGroup& groupBase, const RHI::ShaderResourceGroupData& groupData); + private: ShaderResourceGroupPool() = default; @@ -51,6 +54,21 @@ namespace AZ void UpdateSamplersDescriptorTable(DescriptorTable descriptorTable, const RHI::ShaderResourceGroupData& groupData); void UpdateUnboundedArrayDescriptorTables(ShaderResourceGroup& group, const RHI::ShaderResourceGroupData& groupData); + //! Update all the buffer views for the unbounded array + void UpdateUnboundedBuffersDescTable( + DescriptorTable descriptorTable, + const RHI::ShaderResourceGroupData& groupData, + uint32_t shaderInputIndex, + RHI::ShaderInputBufferAccess bufferAccess); + + //! Update all the image views for the unbounded array + void UpdateUnboundedImagesDescTable( + DescriptorTable descriptorTable, + const RHI::ShaderResourceGroupData& groupData, + uint32_t shaderInputIndex, + RHI::ShaderInputImageAccess imageAccess, + RHI::ShaderInputImageType imageType); + void UpdateDescriptorTableRange( DescriptorTable descriptorTable, const AZStd::vector& descriptors, @@ -66,6 +84,9 @@ namespace AZ RHI::ShaderInputSamplerIndex samplerIndex, AZStd::array_view samplerStates); + //Cache all the gpu handles for the Descriptor tables related to all the views + void CacheGpuHandlesForViews(ShaderResourceGroup& group); + DescriptorTable GetBufferTable(DescriptorTable descriptorTable, RHI::ShaderInputBufferIndex bufferIndex) const; DescriptorTable GetBufferTableUnbounded(DescriptorTable descriptorTable, RHI::ShaderInputBufferIndex bufferIndex) const; DescriptorTable GetImageTable(DescriptorTable descriptorTable, RHI::ShaderInputImageIndex imageIndex) const; @@ -79,7 +100,6 @@ namespace AZ AZStd::vector GetCBVsFromBufferViews(const AZStd::array_view>& bufferViews); - AZStd::mutex m_constantAllocatorMutex; MemoryPoolSubAllocator m_constantAllocator; DescriptorContext* m_descriptorContext = nullptr; uint32_t m_constantBufferSize = 0; diff --git a/Gems/Atom/RHI/Registry/Platform/Windows/PlatformLimits.setreg b/Gems/Atom/RHI/Registry/Platform/Windows/PlatformLimits.setreg index 240f8b041c..22b7d1bf29 100644 --- a/Gems/Atom/RHI/Registry/Platform/Windows/PlatformLimits.setreg +++ b/Gems/Atom/RHI/Registry/Platform/Windows/PlatformLimits.setreg @@ -21,11 +21,13 @@ "$type": "AZ::DX12::PlatformLimitsDescriptor", "DescriptorHeapLimits": { - "DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV": [1000000, 1000000], + "DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV": [100000, 1000000], "DESCRIPTOR_HEAP_TYPE_SAMPLER": [2048, 2048], "DESCRIPTOR_HEAP_TYPE_RTV": [2048, 0], "DESCRIPTOR_HEAP_TYPE_DSV": [2048, 0] - } + }, + "NumShaderVisibleCbvSrvUavStaticHandles": 2000, + "AllowDescriptorHeapCompaction": false }, "vulkan": { From 1dbf9157ea94791fa0cc30c93cb475cbb6c9fab4 Mon Sep 17 00:00:00 2001 From: SJ Date: Mon, 27 Sep 2021 09:48:59 -0700 Subject: [PATCH 31/51] Set working directory to the install directory so that cmake can find includes in the engine root's cmake directory. (#4328) Signed-off-by: amzn-sj --- cmake/Platform/Mac/InstallUtils_mac.cmake.in | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cmake/Platform/Mac/InstallUtils_mac.cmake.in b/cmake/Platform/Mac/InstallUtils_mac.cmake.in index de6d9ddf65..d73c4db459 100644 --- a/cmake/Platform/Mac/InstallUtils_mac.cmake.in +++ b/cmake/Platform/Mac/InstallUtils_mac.cmake.in @@ -155,7 +155,9 @@ function(ly_copy source_file target_directory) endfunction() function(ly_download_and_codesign_sdk_python) - execute_process(COMMAND ${CMAKE_COMMAND} -DPAL_PLATFORM_NAME=Mac -DLY_3RDPARTY_PATH=${CMAKE_INSTALL_PREFIX}/python -P ${CMAKE_INSTALL_PREFIX}/python/get_python.cmake) + execute_process(COMMAND ${CMAKE_COMMAND} -DPAL_PLATFORM_NAME=Mac -DLY_3RDPARTY_PATH=${CMAKE_INSTALL_PREFIX}/python -P ${CMAKE_INSTALL_PREFIX}/python/get_python.cmake + WORKING_DIRECTORY ${CMAKE_INSTALL_PREFIX} + ) fixup_python_framework(${CMAKE_INSTALL_PREFIX}/python/runtime/@LY_PYTHON_PACKAGE_NAME@/Python.framework) codesign_python_framework_binaries(${CMAKE_INSTALL_PREFIX}/python/runtime/@LY_PYTHON_PACKAGE_NAME@/Python.framework) codesign_file(${CMAKE_INSTALL_PREFIX}/python/runtime/@LY_PYTHON_PACKAGE_NAME@/Python.framework @LY_ROOT_FOLDER@/python/Platform/Mac/PythonEntitlements.plist) From 08e3d14013cbe7e35f1f0a14bbb055e3685b7ce9 Mon Sep 17 00:00:00 2001 From: Chris Burel Date: Mon, 27 Sep 2021 11:38:43 -0700 Subject: [PATCH 32/51] Move xcb files to common directory Signed-off-by: Chris Burel --- .../Xcb/AzFramework/XcbApplication.cpp} | 0 .../Xcb/AzFramework/XcbApplication.h} | 0 .../Xcb/AzFramework/XcbInputDeviceKeyboard.cpp} | 0 .../Xcb/AzFramework/XcbNativeWindow.cpp} | 0 .../Xcb/AzFramework/XcbNativeWindow.h} | 0 5 files changed, 0 insertions(+), 0 deletions(-) rename Code/Framework/AzFramework/Platform/{Linux/AzFramework/Application/Application_Linux_xcb.cpp => Common/Xcb/AzFramework/XcbApplication.cpp} (100%) rename Code/Framework/AzFramework/Platform/{Linux/AzFramework/Application/Application_Linux_xcb.h => Common/Xcb/AzFramework/XcbApplication.h} (100%) rename Code/Framework/AzFramework/Platform/{Linux/AzFramework/Input/Devices/Keyboard/InputDeviceKeyboard_xcb.cpp => Common/Xcb/AzFramework/XcbInputDeviceKeyboard.cpp} (100%) rename Code/Framework/AzFramework/Platform/{Linux/AzFramework/Windowing/NativeWindow_Linux_xcb.cpp => Common/Xcb/AzFramework/XcbNativeWindow.cpp} (100%) rename Code/Framework/AzFramework/Platform/{Linux/AzFramework/Windowing/NativeWindow_Linux_xcb.h => Common/Xcb/AzFramework/XcbNativeWindow.h} (100%) diff --git a/Code/Framework/AzFramework/Platform/Linux/AzFramework/Application/Application_Linux_xcb.cpp b/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbApplication.cpp similarity index 100% rename from Code/Framework/AzFramework/Platform/Linux/AzFramework/Application/Application_Linux_xcb.cpp rename to Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbApplication.cpp diff --git a/Code/Framework/AzFramework/Platform/Linux/AzFramework/Application/Application_Linux_xcb.h b/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbApplication.h similarity index 100% rename from Code/Framework/AzFramework/Platform/Linux/AzFramework/Application/Application_Linux_xcb.h rename to Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbApplication.h diff --git a/Code/Framework/AzFramework/Platform/Linux/AzFramework/Input/Devices/Keyboard/InputDeviceKeyboard_xcb.cpp b/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbInputDeviceKeyboard.cpp similarity index 100% rename from Code/Framework/AzFramework/Platform/Linux/AzFramework/Input/Devices/Keyboard/InputDeviceKeyboard_xcb.cpp rename to Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbInputDeviceKeyboard.cpp diff --git a/Code/Framework/AzFramework/Platform/Linux/AzFramework/Windowing/NativeWindow_Linux_xcb.cpp b/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbNativeWindow.cpp similarity index 100% rename from Code/Framework/AzFramework/Platform/Linux/AzFramework/Windowing/NativeWindow_Linux_xcb.cpp rename to Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbNativeWindow.cpp diff --git a/Code/Framework/AzFramework/Platform/Linux/AzFramework/Windowing/NativeWindow_Linux_xcb.h b/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbNativeWindow.h similarity index 100% rename from Code/Framework/AzFramework/Platform/Linux/AzFramework/Windowing/NativeWindow_Linux_xcb.h rename to Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbNativeWindow.h From f5effaabcccefc138aa29f018ea2cc2b04e90b57 Mon Sep 17 00:00:00 2001 From: Chris Burel Date: Fri, 17 Sep 2021 16:43:42 -0700 Subject: [PATCH 33/51] Consolidate all xcb functionality into one subdirectory The Linux platform has multiple windowing systems. Support for xcb is currently in progress, support for Wayland is planned in the future. The way the current xcb support is included is by making some file with a `_xcb` suffix, and placing `#if PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB` around most of that file's contents. With this change, all of the code in AzFramework that uses xcb directly is inside the `Platform/Common/Xcb` subdirectory. It greatly reduces the amount of code in compile-time `#ifdef` checks for the chosen windowing system. It also provides a logical place to include O3DE-specific xcb C++ wrappers and interfaces, without polluting non-xcb related code. Signed-off-by: Chris Burel --- .../Editor/Core/QtEditorApplication_linux.cpp | 4 +- Code/Framework/AzFramework/CMakeLists.txt | 2 +- .../Common/Xcb/AzFramework/XcbApplication.cpp | 45 +- .../Common/Xcb/AzFramework/XcbApplication.h | 20 +- .../Xcb/AzFramework/XcbConnectionManager.h | 42 ++ .../Common/Xcb/AzFramework/XcbEventHandler.h | 40 ++ .../AzFramework/XcbInputDeviceKeyboard.cpp | 492 +++++++++--------- .../Xcb/AzFramework/XcbInputDeviceKeyboard.h | 46 ++ .../Common/Xcb/AzFramework/XcbInterface.h | 38 ++ .../Xcb/AzFramework/XcbNativeWindow.cpp | 51 +- .../Common/Xcb/AzFramework/XcbNativeWindow.h | 19 +- .../Common/Xcb/azframework_xcb_files.cmake | 18 + .../AzFramework/API/ApplicationAPI_Linux.h | 54 -- .../Application/Application_Linux.cpp | 6 +- .../Keyboard/InputDeviceKeyboard_Linux.cpp | 27 + .../Windowing/NativeWindow_Linux.cpp | 7 +- .../Platform/Linux/platform_linux.cmake | 10 +- .../Platform/Linux/platform_linux_files.cmake | 6 +- .../Platform/Linux/RHI/WSISurface_Linux.cpp | 6 +- 19 files changed, 529 insertions(+), 404 deletions(-) create mode 100644 Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbConnectionManager.h create mode 100644 Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbEventHandler.h create mode 100644 Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbInputDeviceKeyboard.h create mode 100644 Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbInterface.h create mode 100644 Code/Framework/AzFramework/Platform/Common/Xcb/azframework_xcb_files.cmake create mode 100644 Code/Framework/AzFramework/Platform/Linux/AzFramework/Input/Devices/Keyboard/InputDeviceKeyboard_Linux.cpp diff --git a/Code/Editor/Core/QtEditorApplication_linux.cpp b/Code/Editor/Core/QtEditorApplication_linux.cpp index 175bef0238..2fd4ef629e 100644 --- a/Code/Editor/Core/QtEditorApplication_linux.cpp +++ b/Code/Editor/Core/QtEditorApplication_linux.cpp @@ -9,7 +9,7 @@ #include "QtEditorApplication.h" #ifdef PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB -#include +#include #endif namespace Editor @@ -19,7 +19,7 @@ namespace Editor if (GetIEditor()->IsInGameMode()) { #ifdef PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB - AzFramework::LinuxXcbEventHandlerBus::Broadcast(&AzFramework::LinuxXcbEventHandler::HandleXcbEvent, static_cast(message)); + AzFramework::XcbEventHandlerBus::Broadcast(&AzFramework::XcbEventHandler::HandleXcbEvent, static_cast(message)); #endif return true; } diff --git a/Code/Framework/AzFramework/CMakeLists.txt b/Code/Framework/AzFramework/CMakeLists.txt index 6f6e47855e..f6851f0ab8 100644 --- a/Code/Framework/AzFramework/CMakeLists.txt +++ b/Code/Framework/AzFramework/CMakeLists.txt @@ -95,4 +95,4 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED) endif() -endif() \ No newline at end of file +endif() diff --git a/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbApplication.cpp b/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbApplication.cpp index aaab67b2a1..7cbd9ae0e2 100644 --- a/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbApplication.cpp +++ b/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbApplication.cpp @@ -6,29 +6,26 @@ * */ -#include -#include -#include "Application_Linux_xcb.h" +#include +#include -//////////////////////////////////////////////////////////////////////////////////////////////////// namespace AzFramework { -#if PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB //////////////////////////////////////////////////////////////////////////////////////////////// - class LinuxXcbConnectionManagerImpl - : public LinuxXcbConnectionManagerBus::Handler + class XcbConnectionManagerImpl + : public XcbConnectionManagerBus::Handler { public: - LinuxXcbConnectionManagerImpl() + XcbConnectionManagerImpl() { m_xcbConnection = xcb_connect(nullptr, nullptr); - AZ_Error("ApplicationLinux", m_xcbConnection != nullptr, "Unable to connect to X11 Server."); - LinuxXcbConnectionManagerBus::Handler::BusConnect(); + AZ_Error("Application", m_xcbConnection != nullptr, "Unable to connect to X11 Server."); + XcbConnectionManagerBus::Handler::BusConnect(); } - ~LinuxXcbConnectionManagerImpl() + ~XcbConnectionManagerImpl() override { - LinuxXcbConnectionManagerBus::Handler::BusDisconnect(); + XcbConnectionManagerBus::Handler::BusDisconnect(); xcb_disconnect(m_xcbConnection); } @@ -42,53 +39,51 @@ namespace AzFramework }; //////////////////////////////////////////////////////////////////////////////////////////////// - ApplicationLinux_xcb::ApplicationLinux_xcb() + XcbApplication::XcbApplication() { LinuxLifecycleEvents::Bus::Handler::BusConnect(); - m_xcbConnectionManager = AZStd::make_unique(); - if (LinuxXcbConnectionManagerInterface::Get() == nullptr) + m_xcbConnectionManager = AZStd::make_unique(); + if (XcbConnectionManagerInterface::Get() == nullptr) { - LinuxXcbConnectionManagerInterface::Register(m_xcbConnectionManager.get()); + XcbConnectionManagerInterface::Register(m_xcbConnectionManager.get()); } } //////////////////////////////////////////////////////////////////////////////////////////////// - ApplicationLinux_xcb::~ApplicationLinux_xcb() + XcbApplication::~XcbApplication() { - if (LinuxXcbConnectionManagerInterface::Get() == m_xcbConnectionManager.get()) + if (XcbConnectionManagerInterface::Get() == m_xcbConnectionManager.get()) { - LinuxXcbConnectionManagerInterface::Unregister(m_xcbConnectionManager.get()); + XcbConnectionManagerInterface::Unregister(m_xcbConnectionManager.get()); } m_xcbConnectionManager.reset(); LinuxLifecycleEvents::Bus::Handler::BusDisconnect(); } //////////////////////////////////////////////////////////////////////////////////////////////// - void ApplicationLinux_xcb::PumpSystemEventLoopOnce() + void XcbApplication::PumpSystemEventLoopOnce() { if (xcb_connection_t* xcbConnection = m_xcbConnectionManager->GetXcbConnection()) { if (xcb_generic_event_t* event = xcb_poll_for_event(xcbConnection)) { - LinuxXcbEventHandlerBus::Broadcast(&LinuxXcbEventHandlerBus::Events::HandleXcbEvent, event); + XcbEventHandlerBus::Broadcast(&XcbEventHandlerBus::Events::HandleXcbEvent, event); free(event); } } } //////////////////////////////////////////////////////////////////////////////////////////////// - void ApplicationLinux_xcb::PumpSystemEventLoopUntilEmpty() + void XcbApplication::PumpSystemEventLoopUntilEmpty() { if (xcb_connection_t* xcbConnection = m_xcbConnectionManager->GetXcbConnection()) { while (xcb_generic_event_t* event = xcb_poll_for_event(xcbConnection)) { - LinuxXcbEventHandlerBus::Broadcast(&LinuxXcbEventHandlerBus::Events::HandleXcbEvent, event); + XcbEventHandlerBus::Broadcast(&XcbEventHandlerBus::Events::HandleXcbEvent, event); free(event); } } } -#endif // PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB - } // namespace AzFramework diff --git a/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbApplication.h b/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbApplication.h index fbd7f165d3..f27b5dd680 100644 --- a/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbApplication.h +++ b/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbApplication.h @@ -5,27 +5,24 @@ * SPDX-License-Identifier: Apache-2.0 OR MIT * */ + #pragma once #include #include +#include -//////////////////////////////////////////////////////////////////////////////////////////////////// namespace AzFramework { - -#if PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB - - //////////////////////////////////////////////////////////////////////////////////////////////// - class ApplicationLinux_xcb + class XcbApplication : public Application::Implementation , public LinuxLifecycleEvents::Bus::Handler { public: //////////////////////////////////////////////////////////////////////////////////////////// - AZ_CLASS_ALLOCATOR(ApplicationLinux_xcb, AZ::SystemAllocator, 0); - ApplicationLinux_xcb(); - ~ApplicationLinux_xcb() override; + AZ_CLASS_ALLOCATOR(XcbApplication, AZ::SystemAllocator, 0); + XcbApplication(); + ~XcbApplication() override; //////////////////////////////////////////////////////////////////////////////////////////// // Application::Implementation @@ -33,9 +30,6 @@ namespace AzFramework void PumpSystemEventLoopUntilEmpty() override; private: - AZStd::unique_ptr m_xcbConnectionManager; + AZStd::unique_ptr m_xcbConnectionManager; }; - -#endif // PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB - } // namespace AzFramework diff --git a/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbConnectionManager.h b/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbConnectionManager.h new file mode 100644 index 0000000000..daa5bf35af --- /dev/null +++ b/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbConnectionManager.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include +#include +#include + +#include + +namespace AzFramework +{ + class XcbConnectionManager + { + public: + AZ_RTTI(XcbConnectionManager, "{1F756E14-8D74-42FD-843C-4863307710DB}"); + + virtual ~XcbConnectionManager() = default; + + virtual xcb_connection_t* GetXcbConnection() const = 0; + }; + + class XcbConnectionManagerBusTraits + : public AZ::EBusTraits + { + public: + ////////////////////////////////////////////////////////////////////////// + // EBusTraits overrides + static constexpr AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single; + static constexpr AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single; + ////////////////////////////////////////////////////////////////////////// + }; + + using XcbConnectionManagerBus = AZ::EBus; + using XcbConnectionManagerInterface = AZ::Interface; +} // namespace AzFramework diff --git a/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbEventHandler.h b/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbEventHandler.h new file mode 100644 index 0000000000..c6307755a7 --- /dev/null +++ b/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbEventHandler.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include +#include + +#include + +namespace AzFramework +{ + class XcbEventHandler + { + public: + AZ_RTTI(XcbEventHandler, "{3F756E14-8D74-42FD-843C-4863307710DB}"); + + virtual ~XcbEventHandler() = default; + + virtual void HandleXcbEvent(xcb_generic_event_t* event) = 0; + }; + + class XcbEventHandlerBusTraits + : public AZ::EBusTraits + { + public: + ////////////////////////////////////////////////////////////////////////// + // EBusTraits overrides + static constexpr AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple; + static constexpr AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single; + ////////////////////////////////////////////////////////////////////////// + }; + + using XcbEventHandlerBus = AZ::EBus; +} // namespace AzFramework diff --git a/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbInputDeviceKeyboard.cpp b/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbInputDeviceKeyboard.cpp index 8ff4c72d42..c971c3b793 100644 --- a/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbInputDeviceKeyboard.cpp +++ b/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbInputDeviceKeyboard.cpp @@ -6,9 +6,10 @@ * */ -#include -#include #include +#include +#include +#include #define explicit ExplicitIsACXXKeyword #include @@ -19,275 +20,252 @@ namespace AzFramework { - class InputDeviceKeyboardXcb - : public InputDeviceKeyboard::Implementation - , public LinuxXcbEventHandlerBus::Handler + XcbInputDeviceKeyboard::XcbInputDeviceKeyboard(InputDeviceKeyboard& inputDevice) + : InputDeviceKeyboard::Implementation(inputDevice) { - public: - AZ_CLASS_ALLOCATOR(InputDeviceKeyboardXcb, AZ::SystemAllocator, 0); + XcbEventHandlerBus::Handler::BusConnect(); - using InputDeviceKeyboard::Implementation::Implementation; - InputDeviceKeyboardXcb(InputDeviceKeyboard& inputDevice) - : InputDeviceKeyboard::Implementation(inputDevice) + auto* interface = AzFramework::XcbConnectionManagerInterface::Get(); + if (!interface) { - LinuxXcbEventHandlerBus::Handler::BusConnect(); - - auto* interface = AzFramework::LinuxXcbConnectionManagerInterface::Get(); - if (!interface) - { - AZ_Warning("ApplicationLinux", false, "XCB interface not available"); - return; - } - - auto* connection = AzFramework::LinuxXcbConnectionManagerInterface::Get()->GetXcbConnection(); - if (!connection) - { - AZ_Warning("ApplicationLinux", false, "XCB connection not available"); - return; - } - - AZStd::unique_ptr> xkbUseExtensionReply{ - xcb_xkb_use_extension_reply(connection, xcb_xkb_use_extension(connection, 1, 0), nullptr) - }; - if (!xkbUseExtensionReply) - { - AZ_Warning("ApplicationLinux", false, "Failed to initialize the xkb extension"); - return; - } - if (!xkbUseExtensionReply->supported) - { - AZ_Warning("ApplicationLinux", false, "The X server does not support the xkb extension"); - return; - } - - m_coreDeviceId = xkb_x11_get_core_keyboard_device_id(connection); - - m_xkbContext.reset(xkb_context_new(XKB_CONTEXT_NO_FLAGS)); - m_xkbKeymap.reset(xkb_x11_keymap_new_from_device(m_xkbContext.get(), connection, m_coreDeviceId, XKB_KEYMAP_COMPILE_NO_FLAGS)); - m_xkbState.reset(xkb_x11_state_new_from_device(m_xkbKeymap.get(), connection, m_coreDeviceId)); - - m_initialized = true; + AZ_Warning("ApplicationLinux", false, "XCB interface not available"); + return; } - bool IsConnected() const override + auto* connection = interface->GetXcbConnection(); + if (!connection) { - return m_initialized; + AZ_Warning("ApplicationLinux", false, "XCB connection not available"); + return; } - bool HasTextEntryStarted() const override + XcbStdFreePtr xkbUseExtensionReply{ + xcb_xkb_use_extension_reply(connection, xcb_xkb_use_extension(connection, 1, 0), nullptr) + }; + if (!xkbUseExtensionReply) { - return false; + AZ_Warning("ApplicationLinux", false, "Failed to initialize the xkb extension"); + return; + } + if (!xkbUseExtensionReply->supported) + { + AZ_Warning("ApplicationLinux", false, "The X server does not support the xkb extension"); + return; } - void TextEntryStart(const InputDeviceKeyboard::VirtualKeyboardOptions& options) override - { - } + m_coreDeviceId = xkb_x11_get_core_keyboard_device_id(connection); - void TextEntryStop() override - { - } + m_xkbContext.reset(xkb_context_new(XKB_CONTEXT_NO_FLAGS)); + m_xkbKeymap.reset(xkb_x11_keymap_new_from_device(m_xkbContext.get(), connection, m_coreDeviceId, XKB_KEYMAP_COMPILE_NO_FLAGS)); + m_xkbState.reset(xkb_x11_state_new_from_device(m_xkbKeymap.get(), connection, m_coreDeviceId)); - void TickInputDevice() override - { - ProcessRawEventQueues(); - } + m_initialized = true; + } - void HandleXcbEvent(xcb_generic_event_t* event) override - { - if (!IsConnected()) - { - return; - } - - switch (event->response_type & ~0x80) - { - case XCB_KEY_PRESS: - { - auto* keyPress = reinterpret_cast(event); - - const InputChannelId* key = InputChannelFromKeyEvent(keyPress->detail); - if (key) - { - QueueRawKeyEvent(*key, true); - } - break; - } - case XCB_KEY_RELEASE: - { - auto* keyRelease = reinterpret_cast(event); - - const InputChannelId* key = InputChannelFromKeyEvent(keyRelease->detail); - if (key) - { - QueueRawKeyEvent(*key, false); - } - break; - } - } - } - - private: - [[nodiscard]] const InputChannelId* InputChannelFromKeyEvent(xcb_keycode_t code) const - { - const xcb_keysym_t keysym = xkb_state_key_get_one_sym(m_xkbState.get(), code); - - switch(keysym) - { - case XKB_KEY_0: return &InputDeviceKeyboard::Key::Alphanumeric0; - case XKB_KEY_1: return &InputDeviceKeyboard::Key::Alphanumeric1; - case XKB_KEY_2: return &InputDeviceKeyboard::Key::Alphanumeric2; - case XKB_KEY_3: return &InputDeviceKeyboard::Key::Alphanumeric3; - case XKB_KEY_4: return &InputDeviceKeyboard::Key::Alphanumeric4; - case XKB_KEY_5: return &InputDeviceKeyboard::Key::Alphanumeric5; - case XKB_KEY_6: return &InputDeviceKeyboard::Key::Alphanumeric6; - case XKB_KEY_7: return &InputDeviceKeyboard::Key::Alphanumeric7; - case XKB_KEY_8: return &InputDeviceKeyboard::Key::Alphanumeric8; - case XKB_KEY_9: return &InputDeviceKeyboard::Key::Alphanumeric9; - case XKB_KEY_A: - case XKB_KEY_a: return &InputDeviceKeyboard::Key::AlphanumericA; - case XKB_KEY_B: - case XKB_KEY_b: return &InputDeviceKeyboard::Key::AlphanumericB; - case XKB_KEY_C: - case XKB_KEY_c: return &InputDeviceKeyboard::Key::AlphanumericC; - case XKB_KEY_D: - case XKB_KEY_d: return &InputDeviceKeyboard::Key::AlphanumericD; - case XKB_KEY_E: - case XKB_KEY_e: return &InputDeviceKeyboard::Key::AlphanumericE; - case XKB_KEY_F: - case XKB_KEY_f: return &InputDeviceKeyboard::Key::AlphanumericF; - case XKB_KEY_G: - case XKB_KEY_g: return &InputDeviceKeyboard::Key::AlphanumericG; - case XKB_KEY_H: - case XKB_KEY_h: return &InputDeviceKeyboard::Key::AlphanumericH; - case XKB_KEY_I: - case XKB_KEY_i: return &InputDeviceKeyboard::Key::AlphanumericI; - case XKB_KEY_J: - case XKB_KEY_j: return &InputDeviceKeyboard::Key::AlphanumericJ; - case XKB_KEY_K: - case XKB_KEY_k: return &InputDeviceKeyboard::Key::AlphanumericK; - case XKB_KEY_L: - case XKB_KEY_l: return &InputDeviceKeyboard::Key::AlphanumericL; - case XKB_KEY_M: - case XKB_KEY_m: return &InputDeviceKeyboard::Key::AlphanumericM; - case XKB_KEY_N: - case XKB_KEY_n: return &InputDeviceKeyboard::Key::AlphanumericN; - case XKB_KEY_O: - case XKB_KEY_o: return &InputDeviceKeyboard::Key::AlphanumericO; - case XKB_KEY_P: - case XKB_KEY_p: return &InputDeviceKeyboard::Key::AlphanumericP; - case XKB_KEY_Q: - case XKB_KEY_q: return &InputDeviceKeyboard::Key::AlphanumericQ; - case XKB_KEY_R: - case XKB_KEY_r: return &InputDeviceKeyboard::Key::AlphanumericR; - case XKB_KEY_S: - case XKB_KEY_s: return &InputDeviceKeyboard::Key::AlphanumericS; - case XKB_KEY_T: - case XKB_KEY_t: return &InputDeviceKeyboard::Key::AlphanumericT; - case XKB_KEY_U: - case XKB_KEY_u: return &InputDeviceKeyboard::Key::AlphanumericU; - case XKB_KEY_V: - case XKB_KEY_v: return &InputDeviceKeyboard::Key::AlphanumericV; - case XKB_KEY_W: - case XKB_KEY_w: return &InputDeviceKeyboard::Key::AlphanumericW; - case XKB_KEY_X: - case XKB_KEY_x: return &InputDeviceKeyboard::Key::AlphanumericX; - case XKB_KEY_Y: - case XKB_KEY_y: return &InputDeviceKeyboard::Key::AlphanumericY; - case XKB_KEY_Z: - case XKB_KEY_z: return &InputDeviceKeyboard::Key::AlphanumericZ; - case XKB_KEY_BackSpace: return &InputDeviceKeyboard::Key::EditBackspace; - case XKB_KEY_Caps_Lock: return &InputDeviceKeyboard::Key::EditCapsLock; - case XKB_KEY_Return: return &InputDeviceKeyboard::Key::EditEnter; - case XKB_KEY_space: return &InputDeviceKeyboard::Key::EditSpace; - case XKB_KEY_Tab: return &InputDeviceKeyboard::Key::EditTab; - case XKB_KEY_Escape: return &InputDeviceKeyboard::Key::Escape; - case XKB_KEY_F1: return &InputDeviceKeyboard::Key::Function01; - case XKB_KEY_F2: return &InputDeviceKeyboard::Key::Function02; - case XKB_KEY_F3: return &InputDeviceKeyboard::Key::Function03; - case XKB_KEY_F4: return &InputDeviceKeyboard::Key::Function04; - case XKB_KEY_F5: return &InputDeviceKeyboard::Key::Function05; - case XKB_KEY_F6: return &InputDeviceKeyboard::Key::Function06; - case XKB_KEY_F7: return &InputDeviceKeyboard::Key::Function07; - case XKB_KEY_F8: return &InputDeviceKeyboard::Key::Function08; - case XKB_KEY_F9: return &InputDeviceKeyboard::Key::Function09; - case XKB_KEY_F10: return &InputDeviceKeyboard::Key::Function10; - case XKB_KEY_F11: return &InputDeviceKeyboard::Key::Function11; - case XKB_KEY_F12: return &InputDeviceKeyboard::Key::Function12; - case XKB_KEY_F13: return &InputDeviceKeyboard::Key::Function13; - case XKB_KEY_F14: return &InputDeviceKeyboard::Key::Function14; - case XKB_KEY_F15: return &InputDeviceKeyboard::Key::Function15; - case XKB_KEY_F16: return &InputDeviceKeyboard::Key::Function16; - case XKB_KEY_F17: return &InputDeviceKeyboard::Key::Function17; - case XKB_KEY_F18: return &InputDeviceKeyboard::Key::Function18; - case XKB_KEY_F19: return &InputDeviceKeyboard::Key::Function19; - case XKB_KEY_F20: return &InputDeviceKeyboard::Key::Function20; - case XKB_KEY_Alt_L: return &InputDeviceKeyboard::Key::ModifierAltL; - case XKB_KEY_Alt_R: return &InputDeviceKeyboard::Key::ModifierAltR; - case XKB_KEY_Control_L: return &InputDeviceKeyboard::Key::ModifierCtrlL; - case XKB_KEY_Control_R: return &InputDeviceKeyboard::Key::ModifierCtrlR; - case XKB_KEY_Shift_L: return &InputDeviceKeyboard::Key::ModifierShiftL; - case XKB_KEY_Shift_R: return &InputDeviceKeyboard::Key::ModifierShiftR; - case XKB_KEY_Super_L: return &InputDeviceKeyboard::Key::ModifierSuperL; - case XKB_KEY_Super_R: return &InputDeviceKeyboard::Key::ModifierSuperR; - case XKB_KEY_Down: return &InputDeviceKeyboard::Key::NavigationArrowDown; - case XKB_KEY_Left: return &InputDeviceKeyboard::Key::NavigationArrowLeft; - case XKB_KEY_Right: return &InputDeviceKeyboard::Key::NavigationArrowRight; - case XKB_KEY_Up: return &InputDeviceKeyboard::Key::NavigationArrowUp; - case XKB_KEY_Delete: return &InputDeviceKeyboard::Key::NavigationDelete; - case XKB_KEY_End: return &InputDeviceKeyboard::Key::NavigationEnd; - case XKB_KEY_Home: return &InputDeviceKeyboard::Key::NavigationHome; - case XKB_KEY_Insert: return &InputDeviceKeyboard::Key::NavigationInsert; - case XKB_KEY_Page_Down: return &InputDeviceKeyboard::Key::NavigationPageDown; - case XKB_KEY_Page_Up: return &InputDeviceKeyboard::Key::NavigationPageUp; - case XKB_KEY_Num_Lock: return &InputDeviceKeyboard::Key::NumLock; - case XKB_KEY_KP_0: return &InputDeviceKeyboard::Key::NumPad0; - case XKB_KEY_KP_1: return &InputDeviceKeyboard::Key::NumPad1; - case XKB_KEY_KP_2: return &InputDeviceKeyboard::Key::NumPad2; - case XKB_KEY_KP_3: return &InputDeviceKeyboard::Key::NumPad3; - case XKB_KEY_KP_4: return &InputDeviceKeyboard::Key::NumPad4; - case XKB_KEY_KP_5: return &InputDeviceKeyboard::Key::NumPad5; - case XKB_KEY_KP_6: return &InputDeviceKeyboard::Key::NumPad6; - case XKB_KEY_KP_7: return &InputDeviceKeyboard::Key::NumPad7; - case XKB_KEY_KP_8: return &InputDeviceKeyboard::Key::NumPad8; - case XKB_KEY_KP_9: return &InputDeviceKeyboard::Key::NumPad9; - case XKB_KEY_KP_Add: return &InputDeviceKeyboard::Key::NumPadAdd; - case XKB_KEY_KP_Decimal: return &InputDeviceKeyboard::Key::NumPadDecimal; - case XKB_KEY_KP_Divide: return &InputDeviceKeyboard::Key::NumPadDivide; - case XKB_KEY_KP_Enter: return &InputDeviceKeyboard::Key::NumPadEnter; - case XKB_KEY_KP_Multiply: return &InputDeviceKeyboard::Key::NumPadMultiply; - case XKB_KEY_KP_Subtract: return &InputDeviceKeyboard::Key::NumPadSubtract; - case XKB_KEY_apostrophe: return &InputDeviceKeyboard::Key::PunctuationApostrophe; - case XKB_KEY_backslash: return &InputDeviceKeyboard::Key::PunctuationBackslash; - case XKB_KEY_bracketleft: return &InputDeviceKeyboard::Key::PunctuationBracketL; - case XKB_KEY_bracketright: return &InputDeviceKeyboard::Key::PunctuationBracketR; - case XKB_KEY_comma: return &InputDeviceKeyboard::Key::PunctuationComma; - case XKB_KEY_equal: return &InputDeviceKeyboard::Key::PunctuationEquals; - case XKB_KEY_hyphen: return &InputDeviceKeyboard::Key::PunctuationHyphen; - case XKB_KEY_period: return &InputDeviceKeyboard::Key::PunctuationPeriod; - case XKB_KEY_semicolon: return &InputDeviceKeyboard::Key::PunctuationSemicolon; - case XKB_KEY_slash: return &InputDeviceKeyboard::Key::PunctuationSlash; - case XKB_KEY_grave: - case XKB_KEY_asciitilde: return &InputDeviceKeyboard::Key::PunctuationTilde; - case XKB_KEY_ISO_Group_Shift: return &InputDeviceKeyboard::Key::SupplementaryISO; - case XKB_KEY_Pause: return &InputDeviceKeyboard::Key::WindowsSystemPause; - case XKB_KEY_Print: return &InputDeviceKeyboard::Key::WindowsSystemPrint; - case XKB_KEY_Scroll_Lock: return &InputDeviceKeyboard::Key::WindowsSystemScrollLock; - default: return nullptr; - } - } - - template - using DeleterForFreeFn = AZStd::integral_constant; - - AZStd::unique_ptr> m_xkbContext; - AZStd::unique_ptr> m_xkbKeymap; - AZStd::unique_ptr> m_xkbState; - int m_coreDeviceId{-1}; - bool m_initialized{false}; - }; - - InputDeviceKeyboard::Implementation* InputDeviceKeyboard::Implementation::Create(InputDeviceKeyboard& inputDevice) + bool XcbInputDeviceKeyboard::IsConnected() const { - return aznew InputDeviceKeyboardXcb(inputDevice); + auto* connection = AzFramework::XcbConnectionManagerInterface::Get()->GetXcbConnection(); + return connection && !xcb_connection_has_error(connection); + } + + bool XcbInputDeviceKeyboard::HasTextEntryStarted() const + { + return false; + } + + void XcbInputDeviceKeyboard::TextEntryStart(const InputDeviceKeyboard::VirtualKeyboardOptions& options) + { + } + + void XcbInputDeviceKeyboard::TextEntryStop() + { + } + + void XcbInputDeviceKeyboard::TickInputDevice() + { + ProcessRawEventQueues(); + } + + void XcbInputDeviceKeyboard::HandleXcbEvent(xcb_generic_event_t* event) + { + if (!m_initialized) + { + return; + } + + switch (event->response_type & ~0x80) + { + case XCB_KEY_PRESS: + { + auto* keyPress = reinterpret_cast(event); + + const InputChannelId* key = InputChannelFromKeyEvent(keyPress->detail); + if (key) + { + QueueRawKeyEvent(*key, true); + } + break; + } + case XCB_KEY_RELEASE: + { + auto* keyRelease = reinterpret_cast(event); + + const InputChannelId* key = InputChannelFromKeyEvent(keyRelease->detail); + if (key) + { + QueueRawKeyEvent(*key, false); + } + break; + } + } + } + + [[nodiscard]] const InputChannelId* XcbInputDeviceKeyboard::InputChannelFromKeyEvent(xcb_keycode_t code) const + { + const xcb_keysym_t keysym = xkb_state_key_get_one_sym(m_xkbState.get(), code); + + switch(keysym) + { + case XKB_KEY_0: return &InputDeviceKeyboard::Key::Alphanumeric0; + case XKB_KEY_1: return &InputDeviceKeyboard::Key::Alphanumeric1; + case XKB_KEY_2: return &InputDeviceKeyboard::Key::Alphanumeric2; + case XKB_KEY_3: return &InputDeviceKeyboard::Key::Alphanumeric3; + case XKB_KEY_4: return &InputDeviceKeyboard::Key::Alphanumeric4; + case XKB_KEY_5: return &InputDeviceKeyboard::Key::Alphanumeric5; + case XKB_KEY_6: return &InputDeviceKeyboard::Key::Alphanumeric6; + case XKB_KEY_7: return &InputDeviceKeyboard::Key::Alphanumeric7; + case XKB_KEY_8: return &InputDeviceKeyboard::Key::Alphanumeric8; + case XKB_KEY_9: return &InputDeviceKeyboard::Key::Alphanumeric9; + case XKB_KEY_A: + case XKB_KEY_a: return &InputDeviceKeyboard::Key::AlphanumericA; + case XKB_KEY_B: + case XKB_KEY_b: return &InputDeviceKeyboard::Key::AlphanumericB; + case XKB_KEY_C: + case XKB_KEY_c: return &InputDeviceKeyboard::Key::AlphanumericC; + case XKB_KEY_D: + case XKB_KEY_d: return &InputDeviceKeyboard::Key::AlphanumericD; + case XKB_KEY_E: + case XKB_KEY_e: return &InputDeviceKeyboard::Key::AlphanumericE; + case XKB_KEY_F: + case XKB_KEY_f: return &InputDeviceKeyboard::Key::AlphanumericF; + case XKB_KEY_G: + case XKB_KEY_g: return &InputDeviceKeyboard::Key::AlphanumericG; + case XKB_KEY_H: + case XKB_KEY_h: return &InputDeviceKeyboard::Key::AlphanumericH; + case XKB_KEY_I: + case XKB_KEY_i: return &InputDeviceKeyboard::Key::AlphanumericI; + case XKB_KEY_J: + case XKB_KEY_j: return &InputDeviceKeyboard::Key::AlphanumericJ; + case XKB_KEY_K: + case XKB_KEY_k: return &InputDeviceKeyboard::Key::AlphanumericK; + case XKB_KEY_L: + case XKB_KEY_l: return &InputDeviceKeyboard::Key::AlphanumericL; + case XKB_KEY_M: + case XKB_KEY_m: return &InputDeviceKeyboard::Key::AlphanumericM; + case XKB_KEY_N: + case XKB_KEY_n: return &InputDeviceKeyboard::Key::AlphanumericN; + case XKB_KEY_O: + case XKB_KEY_o: return &InputDeviceKeyboard::Key::AlphanumericO; + case XKB_KEY_P: + case XKB_KEY_p: return &InputDeviceKeyboard::Key::AlphanumericP; + case XKB_KEY_Q: + case XKB_KEY_q: return &InputDeviceKeyboard::Key::AlphanumericQ; + case XKB_KEY_R: + case XKB_KEY_r: return &InputDeviceKeyboard::Key::AlphanumericR; + case XKB_KEY_S: + case XKB_KEY_s: return &InputDeviceKeyboard::Key::AlphanumericS; + case XKB_KEY_T: + case XKB_KEY_t: return &InputDeviceKeyboard::Key::AlphanumericT; + case XKB_KEY_U: + case XKB_KEY_u: return &InputDeviceKeyboard::Key::AlphanumericU; + case XKB_KEY_V: + case XKB_KEY_v: return &InputDeviceKeyboard::Key::AlphanumericV; + case XKB_KEY_W: + case XKB_KEY_w: return &InputDeviceKeyboard::Key::AlphanumericW; + case XKB_KEY_X: + case XKB_KEY_x: return &InputDeviceKeyboard::Key::AlphanumericX; + case XKB_KEY_Y: + case XKB_KEY_y: return &InputDeviceKeyboard::Key::AlphanumericY; + case XKB_KEY_Z: + case XKB_KEY_z: return &InputDeviceKeyboard::Key::AlphanumericZ; + case XKB_KEY_BackSpace: return &InputDeviceKeyboard::Key::EditBackspace; + case XKB_KEY_Caps_Lock: return &InputDeviceKeyboard::Key::EditCapsLock; + case XKB_KEY_Return: return &InputDeviceKeyboard::Key::EditEnter; + case XKB_KEY_space: return &InputDeviceKeyboard::Key::EditSpace; + case XKB_KEY_Tab: return &InputDeviceKeyboard::Key::EditTab; + case XKB_KEY_Escape: return &InputDeviceKeyboard::Key::Escape; + case XKB_KEY_F1: return &InputDeviceKeyboard::Key::Function01; + case XKB_KEY_F2: return &InputDeviceKeyboard::Key::Function02; + case XKB_KEY_F3: return &InputDeviceKeyboard::Key::Function03; + case XKB_KEY_F4: return &InputDeviceKeyboard::Key::Function04; + case XKB_KEY_F5: return &InputDeviceKeyboard::Key::Function05; + case XKB_KEY_F6: return &InputDeviceKeyboard::Key::Function06; + case XKB_KEY_F7: return &InputDeviceKeyboard::Key::Function07; + case XKB_KEY_F8: return &InputDeviceKeyboard::Key::Function08; + case XKB_KEY_F9: return &InputDeviceKeyboard::Key::Function09; + case XKB_KEY_F10: return &InputDeviceKeyboard::Key::Function10; + case XKB_KEY_F11: return &InputDeviceKeyboard::Key::Function11; + case XKB_KEY_F12: return &InputDeviceKeyboard::Key::Function12; + case XKB_KEY_F13: return &InputDeviceKeyboard::Key::Function13; + case XKB_KEY_F14: return &InputDeviceKeyboard::Key::Function14; + case XKB_KEY_F15: return &InputDeviceKeyboard::Key::Function15; + case XKB_KEY_F16: return &InputDeviceKeyboard::Key::Function16; + case XKB_KEY_F17: return &InputDeviceKeyboard::Key::Function17; + case XKB_KEY_F18: return &InputDeviceKeyboard::Key::Function18; + case XKB_KEY_F19: return &InputDeviceKeyboard::Key::Function19; + case XKB_KEY_F20: return &InputDeviceKeyboard::Key::Function20; + case XKB_KEY_Alt_L: return &InputDeviceKeyboard::Key::ModifierAltL; + case XKB_KEY_Alt_R: return &InputDeviceKeyboard::Key::ModifierAltR; + case XKB_KEY_Control_L: return &InputDeviceKeyboard::Key::ModifierCtrlL; + case XKB_KEY_Control_R: return &InputDeviceKeyboard::Key::ModifierCtrlR; + case XKB_KEY_Shift_L: return &InputDeviceKeyboard::Key::ModifierShiftL; + case XKB_KEY_Shift_R: return &InputDeviceKeyboard::Key::ModifierShiftR; + case XKB_KEY_Super_L: return &InputDeviceKeyboard::Key::ModifierSuperL; + case XKB_KEY_Super_R: return &InputDeviceKeyboard::Key::ModifierSuperR; + case XKB_KEY_Down: return &InputDeviceKeyboard::Key::NavigationArrowDown; + case XKB_KEY_Left: return &InputDeviceKeyboard::Key::NavigationArrowLeft; + case XKB_KEY_Right: return &InputDeviceKeyboard::Key::NavigationArrowRight; + case XKB_KEY_Up: return &InputDeviceKeyboard::Key::NavigationArrowUp; + case XKB_KEY_Delete: return &InputDeviceKeyboard::Key::NavigationDelete; + case XKB_KEY_End: return &InputDeviceKeyboard::Key::NavigationEnd; + case XKB_KEY_Home: return &InputDeviceKeyboard::Key::NavigationHome; + case XKB_KEY_Insert: return &InputDeviceKeyboard::Key::NavigationInsert; + case XKB_KEY_Page_Down: return &InputDeviceKeyboard::Key::NavigationPageDown; + case XKB_KEY_Page_Up: return &InputDeviceKeyboard::Key::NavigationPageUp; + case XKB_KEY_Num_Lock: return &InputDeviceKeyboard::Key::NumLock; + case XKB_KEY_KP_0: return &InputDeviceKeyboard::Key::NumPad0; + case XKB_KEY_KP_1: return &InputDeviceKeyboard::Key::NumPad1; + case XKB_KEY_KP_2: return &InputDeviceKeyboard::Key::NumPad2; + case XKB_KEY_KP_3: return &InputDeviceKeyboard::Key::NumPad3; + case XKB_KEY_KP_4: return &InputDeviceKeyboard::Key::NumPad4; + case XKB_KEY_KP_5: return &InputDeviceKeyboard::Key::NumPad5; + case XKB_KEY_KP_6: return &InputDeviceKeyboard::Key::NumPad6; + case XKB_KEY_KP_7: return &InputDeviceKeyboard::Key::NumPad7; + case XKB_KEY_KP_8: return &InputDeviceKeyboard::Key::NumPad8; + case XKB_KEY_KP_9: return &InputDeviceKeyboard::Key::NumPad9; + case XKB_KEY_KP_Add: return &InputDeviceKeyboard::Key::NumPadAdd; + case XKB_KEY_KP_Decimal: return &InputDeviceKeyboard::Key::NumPadDecimal; + case XKB_KEY_KP_Divide: return &InputDeviceKeyboard::Key::NumPadDivide; + case XKB_KEY_KP_Enter: return &InputDeviceKeyboard::Key::NumPadEnter; + case XKB_KEY_KP_Multiply: return &InputDeviceKeyboard::Key::NumPadMultiply; + case XKB_KEY_KP_Subtract: return &InputDeviceKeyboard::Key::NumPadSubtract; + case XKB_KEY_apostrophe: return &InputDeviceKeyboard::Key::PunctuationApostrophe; + case XKB_KEY_backslash: return &InputDeviceKeyboard::Key::PunctuationBackslash; + case XKB_KEY_bracketleft: return &InputDeviceKeyboard::Key::PunctuationBracketL; + case XKB_KEY_bracketright: return &InputDeviceKeyboard::Key::PunctuationBracketR; + case XKB_KEY_comma: return &InputDeviceKeyboard::Key::PunctuationComma; + case XKB_KEY_equal: return &InputDeviceKeyboard::Key::PunctuationEquals; + case XKB_KEY_hyphen: return &InputDeviceKeyboard::Key::PunctuationHyphen; + case XKB_KEY_period: return &InputDeviceKeyboard::Key::PunctuationPeriod; + case XKB_KEY_semicolon: return &InputDeviceKeyboard::Key::PunctuationSemicolon; + case XKB_KEY_slash: return &InputDeviceKeyboard::Key::PunctuationSlash; + case XKB_KEY_grave: + case XKB_KEY_asciitilde: return &InputDeviceKeyboard::Key::PunctuationTilde; + case XKB_KEY_ISO_Group_Shift: return &InputDeviceKeyboard::Key::SupplementaryISO; + case XKB_KEY_Pause: return &InputDeviceKeyboard::Key::WindowsSystemPause; + case XKB_KEY_Print: return &InputDeviceKeyboard::Key::WindowsSystemPrint; + case XKB_KEY_Scroll_Lock: return &InputDeviceKeyboard::Key::WindowsSystemScrollLock; + default: return nullptr; + } } } // namespace AzFramework diff --git a/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbInputDeviceKeyboard.h b/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbInputDeviceKeyboard.h new file mode 100644 index 0000000000..de65941a67 --- /dev/null +++ b/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbInputDeviceKeyboard.h @@ -0,0 +1,46 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include +#include + +#include +#include + +namespace AzFramework +{ + class XcbInputDeviceKeyboard + : public InputDeviceKeyboard::Implementation + , public XcbEventHandlerBus::Handler + { + public: + AZ_CLASS_ALLOCATOR(XcbInputDeviceKeyboard, AZ::SystemAllocator, 0); + + using InputDeviceKeyboard::Implementation::Implementation; + XcbInputDeviceKeyboard(InputDeviceKeyboard& inputDevice); + + bool IsConnected() const override; + + bool HasTextEntryStarted() const override; + void TextEntryStart(const InputDeviceKeyboard::VirtualKeyboardOptions& options) override; + void TextEntryStop() override; + void TickInputDevice() override; + + void HandleXcbEvent(xcb_generic_event_t* event) override; + + private: + [[nodiscard]] const InputChannelId* InputChannelFromKeyEvent(xcb_keycode_t code) const; + + XcbUniquePtr m_xkbContext; + XcbUniquePtr m_xkbKeymap; + XcbUniquePtr m_xkbState; + int m_coreDeviceId{-1}; + bool m_initialized{false}; + }; +} // namespace AzFramework diff --git a/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbInterface.h b/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbInterface.h new file mode 100644 index 0000000000..838048b174 --- /dev/null +++ b/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbInterface.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include + +#include + +namespace AzFramework +{ + // @brief Wrap a function pointer in a type + // This serves as a convenient way to wrap a function pointer in a given + // type. That type can then be used in a `unique_ptr` or `shared_ptr`. + // Using a type instead of a function pointer by value prevents the need to + // copy the pointer when copying the smart poiner. + template + struct XcbDeleterFreeFunctionWrapper + { + using value_type = decltype(Callable); + static constexpr value_type s_value = Callable; + constexpr operator value_type() const noexcept + { + return s_value; + } + }; + + template + using XcbUniquePtr = AZStd::unique_ptr>; + + template + using XcbStdFreePtr = XcbUniquePtr; +} // namespace AzFramework diff --git a/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbNativeWindow.cpp b/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbNativeWindow.cpp index cfc0222214..6ab97fd616 100644 --- a/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbNativeWindow.cpp +++ b/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbNativeWindow.cpp @@ -6,41 +6,37 @@ * */ -#include #include #include -#include +#include +#include -#include "NativeWindow_Linux_xcb.h" +#include namespace AzFramework { -#if PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB - - [[maybe_unused]] const char LinuxXcbErrorWindow[] = "NativeWindow_Linux_xcb"; + [[maybe_unused]] const char XcbErrorWindow[] = "XcbNativeWindow"; static constexpr uint8_t s_XcbFormatDataSize = 32; // Format indicator for xcb for client messages static constexpr uint16_t s_DefaultXcbWindowBorderWidth = 4; // The default border with in pixels if a border was specified static constexpr uint8_t s_XcbResponseTypeMask = 0x7f; // Mask to extract the specific event type from an xcb event //////////////////////////////////////////////////////////////////////////////////////////////// - NativeWindowImpl_Linux_xcb::NativeWindowImpl_Linux_xcb() + XcbNativeWindow::XcbNativeWindow() : NativeWindow::Implementation() { - if (auto xcbConnectionManager = AzFramework::LinuxXcbConnectionManagerInterface::Get(); + if (auto xcbConnectionManager = AzFramework::XcbConnectionManagerInterface::Get(); xcbConnectionManager != nullptr) { m_xcbConnection = xcbConnectionManager->GetXcbConnection(); } - AZ_Error(LinuxXcbErrorWindow, m_xcbConnection != nullptr, "Unable to get XCB Connection"); + AZ_Error(XcbErrorWindow, m_xcbConnection != nullptr, "Unable to get XCB Connection"); } //////////////////////////////////////////////////////////////////////////////////////////////// - NativeWindowImpl_Linux_xcb::~NativeWindowImpl_Linux_xcb() - { - } + XcbNativeWindow::~XcbNativeWindow() = default; //////////////////////////////////////////////////////////////////////////////////////////////// - void NativeWindowImpl_Linux_xcb::InitWindow(const AZStd::string& title, + void XcbNativeWindow::InitWindow(const AZStd::string& title, const WindowGeometry& geometry, const WindowStyleMasks& styleMasks) { @@ -98,13 +94,13 @@ namespace AzFramework xcb_intern_atom_cookie_t cookieProtocol = xcb_intern_atom(m_xcbConnection, 1, strlen(wmProtocolString), wmProtocolString); xcb_intern_atom_reply_t* replyProtocol = xcb_intern_atom_reply(m_xcbConnection, cookieProtocol, nullptr); - AZ_Error(LinuxXcbErrorWindow, replyProtocol != nullptr, "Unable to query xcb '%s' atom", wmProtocolString); + AZ_Error(XcbErrorWindow, replyProtocol != nullptr, "Unable to query xcb '%s' atom", wmProtocolString); m_xcbAtomProtocols = replyProtocol->atom; const static char* wmDeleteWindowString = "WM_DELETE_WINDOW"; xcb_intern_atom_cookie_t cookieDeleteWindow = xcb_intern_atom(m_xcbConnection, 0, strlen(wmDeleteWindowString), wmDeleteWindowString); xcb_intern_atom_reply_t* replyDeleteWindow = xcb_intern_atom_reply(m_xcbConnection, cookieDeleteWindow, nullptr); - AZ_Error(LinuxXcbErrorWindow, replyDeleteWindow != nullptr, "Unable to query xcb '%s' atom", wmDeleteWindowString); + AZ_Error(XcbErrorWindow, replyDeleteWindow != nullptr, "Unable to query xcb '%s' atom", wmDeleteWindowString); m_xcbAtomDeleteWindow = replyDeleteWindow->atom; xcbCheckResult = xcb_change_property_checked(m_xcbConnection, @@ -123,9 +119,9 @@ namespace AzFramework } //////////////////////////////////////////////////////////////////////////////////////////////// - void NativeWindowImpl_Linux_xcb::Activate() + void XcbNativeWindow::Activate() { - LinuxXcbEventHandlerBus::Handler::BusConnect(); + XcbEventHandlerBus::Handler::BusConnect(); if (!m_activated) // nothing to do if window was already activated { @@ -137,7 +133,7 @@ namespace AzFramework } //////////////////////////////////////////////////////////////////////////////////////////////// - void NativeWindowImpl_Linux_xcb::Deactivate() + void XcbNativeWindow::Deactivate() { if (m_activated) // nothing to do if window was already deactivated { @@ -148,17 +144,17 @@ namespace AzFramework xcb_unmap_window(m_xcbConnection, m_xcbWindow); xcb_flush(m_xcbConnection); } - LinuxXcbEventHandlerBus::Handler::BusDisconnect(); + XcbEventHandlerBus::Handler::BusDisconnect(); } //////////////////////////////////////////////////////////////////////////////////////////////// - NativeWindowHandle NativeWindowImpl_Linux_xcb::GetWindowHandle() const + NativeWindowHandle XcbNativeWindow::GetWindowHandle() const { return reinterpret_cast(m_xcbWindow); } //////////////////////////////////////////////////////////////////////////////////////////////// - void NativeWindowImpl_Linux_xcb::SetWindowTitle(const AZStd::string& title) + void XcbNativeWindow::SetWindowTitle(const AZStd::string& title) { xcb_void_cookie_t xcbCheckResult; xcbCheckResult = xcb_change_property(m_xcbConnection, @@ -173,7 +169,7 @@ namespace AzFramework } //////////////////////////////////////////////////////////////////////////////////////////////// - void NativeWindowImpl_Linux_xcb::ResizeClientArea(WindowSize clientAreaSize) + void XcbNativeWindow::ResizeClientArea(WindowSize clientAreaSize) { const uint32_t values[] = { clientAreaSize.m_width, clientAreaSize.m_height }; @@ -184,7 +180,7 @@ namespace AzFramework } //////////////////////////////////////////////////////////////////////////////////////////////// - uint32_t NativeWindowImpl_Linux_xcb::GetDisplayRefreshRate() const + uint32_t XcbNativeWindow::GetDisplayRefreshRate() const { // [GFX TODO][GHI - 2678] // Using 60 for now until proper support is added @@ -192,7 +188,7 @@ namespace AzFramework } //////////////////////////////////////////////////////////////////////////////////////////////// - bool NativeWindowImpl_Linux_xcb::ValidateXcbResult(xcb_void_cookie_t cookie) + bool XcbNativeWindow::ValidateXcbResult(xcb_void_cookie_t cookie) { bool result = true; if (xcb_generic_error_t* error = xcb_request_check(m_xcbConnection, cookie)) @@ -204,7 +200,7 @@ namespace AzFramework } //////////////////////////////////////////////////////////////////////////////////////////////// - void NativeWindowImpl_Linux_xcb::HandleXcbEvent(xcb_generic_event_t* event) + void XcbNativeWindow::HandleXcbEvent(xcb_generic_event_t* event) { switch (event->response_type & s_XcbResponseTypeMask) { @@ -233,7 +229,7 @@ namespace AzFramework } //////////////////////////////////////////////////////////////////////////////////////////////// - void NativeWindowImpl_Linux_xcb::WindowSizeChanged(const uint32_t width, const uint32_t height) + void XcbNativeWindow::WindowSizeChanged(const uint32_t width, const uint32_t height) { if (m_width != width || m_height != height) { @@ -246,7 +242,4 @@ namespace AzFramework } } } - -#endif // PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB - } // namespace AzFramework diff --git a/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbNativeWindow.h b/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbNativeWindow.h index 40181395e4..36737e24e8 100644 --- a/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbNativeWindow.h +++ b/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbNativeWindow.h @@ -5,24 +5,25 @@ * SPDX-License-Identifier: Apache-2.0 OR MIT * */ + #pragma once -#include #include #include +#include + #include namespace AzFramework { -#if PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB - class NativeWindowImpl_Linux_xcb final + class XcbNativeWindow final : public NativeWindow::Implementation - , public LinuxXcbEventHandlerBus::Handler + , public XcbEventHandlerBus::Handler { public: - AZ_CLASS_ALLOCATOR(NativeWindowImpl_Linux_xcb, AZ::SystemAllocator, 0); - NativeWindowImpl_Linux_xcb(); - ~NativeWindowImpl_Linux_xcb() override; + AZ_CLASS_ALLOCATOR(XcbNativeWindow, AZ::SystemAllocator, 0); + XcbNativeWindow(); + ~XcbNativeWindow() override; //////////////////////////////////////////////////////////////////////////////////////////// // NativeWindow::Implementation @@ -37,7 +38,7 @@ namespace AzFramework uint32_t GetDisplayRefreshRate() const override; //////////////////////////////////////////////////////////////////////////////////////////// - // LinuxXcbEventHandlerBus::Handler + // XcbEventHandlerBus::Handler void HandleXcbEvent(xcb_generic_event_t* event) override; private: @@ -49,6 +50,4 @@ namespace AzFramework xcb_atom_t m_xcbAtomProtocols; xcb_atom_t m_xcbAtomDeleteWindow; }; -#endif // PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB - } // namespace AzFramework diff --git a/Code/Framework/AzFramework/Platform/Common/Xcb/azframework_xcb_files.cmake b/Code/Framework/AzFramework/Platform/Common/Xcb/azframework_xcb_files.cmake new file mode 100644 index 0000000000..68de710fa1 --- /dev/null +++ b/Code/Framework/AzFramework/Platform/Common/Xcb/azframework_xcb_files.cmake @@ -0,0 +1,18 @@ +# +# Copyright (c) Contributors to the Open 3D Engine Project. +# For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT +# +# + +set(FILES + AzFramework/XcbApplication.cpp + AzFramework/XcbApplication.h + AzFramework/XcbConnectionManager.h + AzFramework/XcbInputDeviceKeyboard.cpp + AzFramework/XcbInputDeviceKeyboard.h + AzFramework/XcbInterface.h + AzFramework/XcbNativeWindow.cpp + AzFramework/XcbNativeWindow.h +) diff --git a/Code/Framework/AzFramework/Platform/Linux/AzFramework/API/ApplicationAPI_Linux.h b/Code/Framework/AzFramework/Platform/Linux/AzFramework/API/ApplicationAPI_Linux.h index 833f4019f8..adf3ee0bdd 100644 --- a/Code/Framework/AzFramework/Platform/Linux/AzFramework/API/ApplicationAPI_Linux.h +++ b/Code/Framework/AzFramework/Platform/Linux/AzFramework/API/ApplicationAPI_Linux.h @@ -12,10 +12,6 @@ #include #include -#if PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB -#include -#endif // LY_COMPILE_DEFINITIONS - namespace AzFramework { class LinuxLifecycleEvents @@ -30,54 +26,4 @@ namespace AzFramework using Bus = AZ::EBus; }; - -#if PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB - class LinuxXcbConnectionManager - { - public: - AZ_RTTI(LinuxXcbConnectionManager, "{1F756E14-8D74-42FD-843C-4863307710DB}"); - - virtual ~LinuxXcbConnectionManager() = default; - - virtual xcb_connection_t* GetXcbConnection() const = 0; - }; - - class LinuxXcbConnectionManagerBusTraits - : public AZ::EBusTraits - { - public: - ////////////////////////////////////////////////////////////////////////// - // EBusTraits overrides - static constexpr AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single; - static constexpr AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single; - ////////////////////////////////////////////////////////////////////////// - }; - - using LinuxXcbConnectionManagerBus = AZ::EBus; - using LinuxXcbConnectionManagerInterface = AZ::Interface; - - class LinuxXcbEventHandler - { - public: - AZ_RTTI(LinuxXcbEventHandler, "{3F756E14-8D74-42FD-843C-4863307710DB}"); - - virtual ~LinuxXcbEventHandler() = default; - - virtual void HandleXcbEvent(xcb_generic_event_t* event) = 0; - }; - - class LinuxXcbEventHandlerBusTraits - : public AZ::EBusTraits - { - public: - ////////////////////////////////////////////////////////////////////////// - // EBusTraits overrides - static constexpr AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple; - static constexpr AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single; - ////////////////////////////////////////////////////////////////////////// - }; - - using LinuxXcbEventHandlerBus = AZ::EBus; - -#endif // PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB } // namespace AzFramework diff --git a/Code/Framework/AzFramework/Platform/Linux/AzFramework/Application/Application_Linux.cpp b/Code/Framework/AzFramework/Platform/Linux/AzFramework/Application/Application_Linux.cpp index 59602fe788..158240210e 100644 --- a/Code/Framework/AzFramework/Platform/Linux/AzFramework/Application/Application_Linux.cpp +++ b/Code/Framework/AzFramework/Platform/Linux/AzFramework/Application/Application_Linux.cpp @@ -8,7 +8,9 @@ #include -#include "Application_Linux_xcb.h" +#if PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB +#include +#endif //////////////////////////////////////////////////////////////////////////////////////////////////// namespace AzFramework @@ -17,7 +19,7 @@ namespace AzFramework Application::Implementation* Application::Implementation::Create() { #if PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB - return aznew ApplicationLinux_xcb(); + return aznew XcbApplication(); #elif PAL_TRAIT_LINUX_WINDOW_MANAGER_WAYLAND #error "Linux Window Manager Wayland not supported." return nullptr; diff --git a/Code/Framework/AzFramework/Platform/Linux/AzFramework/Input/Devices/Keyboard/InputDeviceKeyboard_Linux.cpp b/Code/Framework/AzFramework/Platform/Linux/AzFramework/Input/Devices/Keyboard/InputDeviceKeyboard_Linux.cpp new file mode 100644 index 0000000000..d1f90c3052 --- /dev/null +++ b/Code/Framework/AzFramework/Platform/Linux/AzFramework/Input/Devices/Keyboard/InputDeviceKeyboard_Linux.cpp @@ -0,0 +1,27 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#if PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB +#include +#endif + +namespace AzFramework +{ + InputDeviceKeyboard::Implementation* InputDeviceKeyboard::Implementation::Create(InputDeviceKeyboard& inputDevice) + { +#if PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB + return aznew XcbInputDeviceKeyboard(inputDevice); +#elif PAL_TRAIT_LINUX_WINDOW_MANAGER_WAYLAND + #error "Linux Window Manager Wayland not supported." + return nullptr; +#else + #error "Linux Window Manager not recognized." + return nullptr; +#endif // PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB + } +} // namespace AzFramework diff --git a/Code/Framework/AzFramework/Platform/Linux/AzFramework/Windowing/NativeWindow_Linux.cpp b/Code/Framework/AzFramework/Platform/Linux/AzFramework/Windowing/NativeWindow_Linux.cpp index 2950f8dcfc..ba3e761974 100644 --- a/Code/Framework/AzFramework/Platform/Linux/AzFramework/Windowing/NativeWindow_Linux.cpp +++ b/Code/Framework/AzFramework/Platform/Linux/AzFramework/Windowing/NativeWindow_Linux.cpp @@ -6,14 +6,16 @@ * */ -#include "NativeWindow_Linux_xcb.h" +#if PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB +#include +#endif namespace AzFramework { NativeWindow::Implementation* NativeWindow::Implementation::Create() { #if PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB - return aznew NativeWindowImpl_Linux_xcb(); + return aznew XcbNativeWindow(); #elif PAL_TRAIT_LINUX_WINDOW_MANAGER_WAYLAND #error "Linux Window Manager Wayland not supported." return nullptr; @@ -22,5 +24,4 @@ namespace AzFramework return nullptr; #endif // PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB } - } // namespace AzFramework diff --git a/Code/Framework/AzFramework/Platform/Linux/platform_linux.cmake b/Code/Framework/AzFramework/Platform/Linux/platform_linux.cmake index 5c8ca8392f..b08c3b310d 100644 --- a/Code/Framework/AzFramework/Platform/Linux/platform_linux.cmake +++ b/Code/Framework/AzFramework/Platform/Linux/platform_linux.cmake @@ -10,6 +10,14 @@ # Only 'xcb' and 'wayland' are recognized if (${PAL_TRAIT_LINUX_WINDOW_MANAGER} STREQUAL "xcb") + set(LY_COMPILE_DEFINITIONS PUBLIC PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB) + set(LY_INCLUDE_DIRECTORIES + PUBLIC + Platform/Common/Xcb + ) + set(LY_FILES_CMAKE + Platform/Common/Xcb/azframework_xcb_files.cmake + ) set(LY_BUILD_DEPENDENCIES PRIVATE 3rdParty::X11::xcb @@ -18,8 +26,6 @@ if (${PAL_TRAIT_LINUX_WINDOW_MANAGER} STREQUAL "xcb") 3rdParty::X11::xkbcommon_X11 ) - set(LY_COMPILE_DEFINITIONS PUBLIC PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB) - elseif(PAL_TRAIT_LINUX_WINDOW_MANAGER STREQUAL "wayland") set(LY_COMPILE_DEFINITIONS PUBLIC PAL_TRAIT_LINUX_WINDOW_MANAGER_WAYLAND) diff --git a/Code/Framework/AzFramework/Platform/Linux/platform_linux_files.cmake b/Code/Framework/AzFramework/Platform/Linux/platform_linux_files.cmake index 93767322b2..4f06aa5c57 100644 --- a/Code/Framework/AzFramework/Platform/Linux/platform_linux_files.cmake +++ b/Code/Framework/AzFramework/Platform/Linux/platform_linux_files.cmake @@ -12,8 +12,6 @@ set(FILES AzFramework/API/ApplicationAPI_Platform.h AzFramework/API/ApplicationAPI_Linux.h AzFramework/Application/Application_Linux.cpp - AzFramework/Application/Application_Linux_xcb.h - AzFramework/Application/Application_Linux_xcb.cpp AzFramework/Asset/AssetSystemComponentHelper_Linux.cpp AzFramework/Process/ProcessWatcher_Linux.cpp AzFramework/Process/ProcessCommon.h @@ -22,10 +20,8 @@ set(FILES ../Common/Unimplemented/AzFramework/StreamingInstall/StreamingInstall_Unimplemented.cpp ../Common/Default/AzFramework/TargetManagement/TargetManagementComponent_Default.cpp AzFramework/Windowing/NativeWindow_Linux.cpp - AzFramework/Windowing/NativeWindow_Linux_xcb.h - AzFramework/Windowing/NativeWindow_Linux_xcb.cpp ../Common/Unimplemented/AzFramework/Input/Devices/Gamepad/InputDeviceGamepad_Unimplemented.cpp - AzFramework/Input/Devices/Keyboard/InputDeviceKeyboard_xcb.cpp + AzFramework/Input/Devices/Keyboard/InputDeviceKeyboard_Linux.cpp ../Common/Unimplemented/AzFramework/Input/Devices/Motion/InputDeviceMotion_Unimplemented.cpp ../Common/Unimplemented/AzFramework/Input/Devices/Mouse/InputDeviceMouse_Unimplemented.cpp ../Common/Unimplemented/AzFramework/Input/Devices/Touch/InputDeviceTouch_Unimplemented.cpp diff --git a/Gems/Atom/RHI/Vulkan/Code/Source/Platform/Linux/RHI/WSISurface_Linux.cpp b/Gems/Atom/RHI/Vulkan/Code/Source/Platform/Linux/RHI/WSISurface_Linux.cpp index a818599b0d..7d70d0245f 100644 --- a/Gems/Atom/RHI/Vulkan/Code/Source/Platform/Linux/RHI/WSISurface_Linux.cpp +++ b/Gems/Atom/RHI/Vulkan/Code/Source/Platform/Linux/RHI/WSISurface_Linux.cpp @@ -10,6 +10,10 @@ #include #include +#if PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB +#include +#endif + namespace AZ { namespace Vulkan @@ -21,7 +25,7 @@ namespace AZ #if PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB xcb_connection_t* xcb_connection = nullptr; - if (auto xcbConnectionManager = AzFramework::LinuxXcbConnectionManagerInterface::Get(); + if (auto xcbConnectionManager = AzFramework::XcbConnectionManagerInterface::Get(); xcbConnectionManager != nullptr) { xcb_connection = xcbConnectionManager->GetXcbConnection(); From 4b2dc9b3a44ce7d49e88306ab8ebc22004463672 Mon Sep 17 00:00:00 2001 From: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> Date: Mon, 27 Sep 2021 14:58:39 -0500 Subject: [PATCH 34/51] Terrain System fixes (#4299) * Remove the "TEST_SUPPORTED" traits. Terrain unit tests should be usable on all platforms, so they shouldn't need a platform-specific trait to enable/disable. Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> * Fix a few misc terrain bugs. * Change Activate/Deactivate to happen immediately instead of deferring. There were too many order-of-operation bugs caused by trying to defer this. * Added implementation for calculating normals. * Fixed bug where GetHeightSynchronous wasn't stopping at the highest-priority layer. * Added locks for SurfaceData bus to help ensure we lock our mutexes in the correct order and avoid deadlocks. Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> * Add trivial TerrainSystem tests. Tests construction, Activate(), Deactivate(), and destruction. Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> * Unified Terrain system calls on single bus. Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> * Added mock for TerrainDataNotificationBus listener. Also added unit tests to verify the listener, and added in missing notification events. Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> * Removed extra Sampler class. Fixed up APIs to correctly pass Sampler and terrainExistsPtr around. Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> * Switched MockTerrainSystem to be proper gmock. This makes it for flexible to use and easier to reuse from other test environments. Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> * Fix settings bug caused by bad order of operations that occurred when the methods moved to a different bus. Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> * Eliminate extra EBus by simplifying area initialization. Previously, there was a back-and-forth ebus signal used for the terrain system to find any terrain spawners that were created prior to the terrain system activation. Now it uses the more simple technique of just grabbing all the spawners that are currently hooked up to the spawner ebus. Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> * Switch to NiceMock so that "uninteresting" mock calls get ignored. Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> * Addressed PR feedback. Filled in terrainExistsPtr at the end, and added it to GetNormal as well. Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> * Fixed shader height calculation. It was off by half a pixel, and it was interpolating, both of which were wrong. Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> * Create initial LmbrCentral mocks that other Gems can use. To help improve mock maintenance over time, this creates mocks in the same Gem as the systems being mocked, instead of the other Gems that need to use mocked systems during testing. This way, the mocks should more easily stay in sync with the interface that they mock out. Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> * Null-guard any uses of Atom to make the class easier to unit test. Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> * Add more mocked terrain services Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> * Moved Terrain mocks to publicly-available Mocks directory. Also added more unit tests. Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> * Change debug code to use EXACT instead of BILINEAR height sampling, because it's specifically using the terrain grid. Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> * Added support for the sampler filters. Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> * Fix bad merge. Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> * Add unit test to verify terrain layers define terrain regions. Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> * Moved the AABB component mock into a private terrain header, since it's a specialized mock just for the terrain tests. Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> * Moved sampler and normal calculations into TerrainSystem so that they can work across multiple adjacent areas. Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> * Missed a couple of unit test changes. Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> * Removed normal query as well, that needs a seprate unit test. Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> * Removed unused variable that was only caught in Linux/Android builds. Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> * Addressed PR feedback Made the parameter names consistently listed in MockShapes.h. Added comments to TerrainHeightGradientListComponent explaining why terrainExists is always true when a gradient exists. Also fixed a bug where terrainExists should technically be *false* if no gradient exists. Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> * Added more descriptive comments and names for ClampPosition. Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> * Unit tests to verify height samplers work correctly. Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> * Fix bug where wireframe didn't refresh when the setting changed. Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> * Created TerrainWorldRenderer Component and moved all terrain rendering management code into it. Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> * Fixed the icon references Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> * Removed expensive profile marker. Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> * Addressed PR feedback Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> * Fixed AR error - constexpr doesn't need to be in a lambda capture. Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> --- .../Code/Source/Mesh/MeshFeatureProcessor.cpp | 1 - .../Components/TerrainSystemComponent.cpp | 10 +- .../TerrainWorldDebuggerComponent.cpp | 2 + .../TerrainWorldRendererComponent.cpp | 213 ++++++++++++ .../TerrainWorldRendererComponent.h | 75 +++++ .../EditorTerrainWorldRendererComponent.cpp | 56 ++++ .../EditorTerrainWorldRendererComponent.h | 38 +++ .../Code/Source/EditorTerrainModule.cpp | 2 + Gems/Terrain/Code/Source/TerrainModule.cpp | 2 + .../Source/TerrainSystem/TerrainSystem.cpp | 70 ---- Gems/Terrain/Code/Tests/TerrainSystemTest.cpp | 315 ++++++++++++++++-- .../Code/terrain_editor_shared_files.cmake | 2 + Gems/Terrain/Code/terrain_files.cmake | 2 + 13 files changed, 674 insertions(+), 114 deletions(-) create mode 100644 Gems/Terrain/Code/Source/Components/TerrainWorldRendererComponent.cpp create mode 100644 Gems/Terrain/Code/Source/Components/TerrainWorldRendererComponent.h create mode 100644 Gems/Terrain/Code/Source/EditorComponents/EditorTerrainWorldRendererComponent.cpp create mode 100644 Gems/Terrain/Code/Source/EditorComponents/EditorTerrainWorldRendererComponent.h diff --git a/Gems/Atom/Feature/Common/Code/Source/Mesh/MeshFeatureProcessor.cpp b/Gems/Atom/Feature/Common/Code/Source/Mesh/MeshFeatureProcessor.cpp index 82568126df..3fcda71998 100644 --- a/Gems/Atom/Feature/Common/Code/Source/Mesh/MeshFeatureProcessor.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/Mesh/MeshFeatureProcessor.cpp @@ -85,7 +85,6 @@ namespace AZ { const auto jobLambda = [&]() -> void { - AZ_PROFILE_SCOPE(AzRender, "MeshFP::Simulate() Lambda"); for (auto meshDataIter = iteratorRange.first; meshDataIter != iteratorRange.second; ++meshDataIter) { if (!meshDataIter->m_model) diff --git a/Gems/Terrain/Code/Source/Components/TerrainSystemComponent.cpp b/Gems/Terrain/Code/Source/Components/TerrainSystemComponent.cpp index afaf0470b4..5357ccce32 100644 --- a/Gems/Terrain/Code/Source/Components/TerrainSystemComponent.cpp +++ b/Gems/Terrain/Code/Source/Components/TerrainSystemComponent.cpp @@ -13,8 +13,6 @@ #include #include -#include -#include #include namespace Terrain @@ -34,8 +32,6 @@ namespace Terrain ->Attribute(AZ::Edit::Attributes::AutoExpand, true) ; } - - Terrain::TerrainFeatureProcessor::Reflect(context); } } @@ -49,9 +45,8 @@ namespace Terrain incompatible.push_back(AZ_CRC_CE("TerrainService")); } - void TerrainSystemComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required) + void TerrainSystemComponent::GetRequiredServices([[maybe_unused]] AZ::ComponentDescriptor::DependencyArrayType& required) { - required.push_back(AZ_CRC_CE("RPISystem")); } void TerrainSystemComponent::GetDependentServices([[maybe_unused]] AZ::ComponentDescriptor::DependencyArrayType& dependent) @@ -68,14 +63,11 @@ namespace Terrain // every time an entity is added or removed to a level. If this ever changes, the Terrain System ownership could move into // the level component. m_terrainSystem = new TerrainSystem(); - AZ::RPI::FeatureProcessorFactory::Get()->RegisterFeatureProcessor(); } void TerrainSystemComponent::Deactivate() { delete m_terrainSystem; m_terrainSystem = nullptr; - - AZ::RPI::FeatureProcessorFactory::Get()->UnregisterFeatureProcessor(); } } diff --git a/Gems/Terrain/Code/Source/Components/TerrainWorldDebuggerComponent.cpp b/Gems/Terrain/Code/Source/Components/TerrainWorldDebuggerComponent.cpp index 7233d08281..e140129563 100644 --- a/Gems/Terrain/Code/Source/Components/TerrainWorldDebuggerComponent.cpp +++ b/Gems/Terrain/Code/Source/Components/TerrainWorldDebuggerComponent.cpp @@ -95,6 +95,8 @@ namespace Terrain AzFramework::EntityDebugDisplayEventBus::Handler::BusConnect(GetEntityId()); AzFramework::BoundsRequestBus::Handler::BusConnect(GetEntityId()); AzFramework::Terrain::TerrainDataNotificationBus::Handler::BusConnect(); + + RefreshCachedWireframeGrid(AZ::Aabb::CreateNull()); } void TerrainWorldDebuggerComponent::Deactivate() diff --git a/Gems/Terrain/Code/Source/Components/TerrainWorldRendererComponent.cpp b/Gems/Terrain/Code/Source/Components/TerrainWorldRendererComponent.cpp new file mode 100644 index 0000000000..3bf34fa019 --- /dev/null +++ b/Gems/Terrain/Code/Source/Components/TerrainWorldRendererComponent.cpp @@ -0,0 +1,213 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +namespace Terrain +{ + void TerrainWorldRendererConfig::Reflect(AZ::ReflectContext* context) + { + Terrain::TerrainFeatureProcessor::Reflect(context); + + AZ::SerializeContext* serialize = azrtti_cast(context); + if (serialize) + { + serialize->Class()->Version(1); + + AZ::EditContext* edit = serialize->GetEditContext(); + if (edit) + { + edit->Class("Terrain World Renderer Component", "Enables terrain rendering") + ->ClassElement(AZ::Edit::ClassElements::EditorData, "") + ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZStd::vector({ AZ_CRC_CE("Level") })) + ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly) + ->Attribute(AZ::Edit::Attributes::AutoExpand, true); + } + } + } + + void TerrainWorldRendererComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& services) + { + services.push_back(AZ_CRC_CE("TerrainRendererService")); + } + + void TerrainWorldRendererComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& services) + { + services.push_back(AZ_CRC_CE("TerrainRendererService")); + } + + void TerrainWorldRendererComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& services) + { + services.push_back(AZ_CRC_CE("TerrainService")); + } + + void TerrainWorldRendererComponent::Reflect(AZ::ReflectContext* context) + { + TerrainWorldRendererConfig::Reflect(context); + + AZ::SerializeContext* serialize = azrtti_cast(context); + if (serialize) + { + serialize->Class()->Version(0)->Field( + "Configuration", &TerrainWorldRendererComponent::m_configuration); + } + } + + TerrainWorldRendererComponent::TerrainWorldRendererComponent(const TerrainWorldRendererConfig& configuration) + : m_configuration(configuration) + { + } + + TerrainWorldRendererComponent::~TerrainWorldRendererComponent() + { + if (m_terrainRendererActive) + { + Deactivate(); + } + } + + AZ::RPI::Scene* TerrainWorldRendererComponent::GetScene() const + { + // Find the entity context for the entity ID. + AzFramework::EntityContextId entityContextId = AzFramework::EntityContextId::CreateNull(); + AzFramework::EntityIdContextQueryBus::EventResult( + entityContextId, GetEntityId(), &AzFramework::EntityIdContextQueryBus::Events::GetOwningContextId); + + return AZ::RPI::Scene::GetSceneForEntityContextId(entityContextId); + } + + void TerrainWorldRendererComponent::Activate() + { + // On component activation, register the terrain feature processor with Atom and the scene related to this entity context. + + AZ::RPI::FeatureProcessorFactory::Get()->RegisterFeatureProcessor(); + + if (AZ::RPI::Scene* scene = GetScene(); scene) + { + m_terrainFeatureProcessor = scene->EnableFeatureProcessor(); + } + + AzFramework::Terrain::TerrainDataNotificationBus::Handler::BusConnect(); + m_terrainRendererActive = true; + } + + void TerrainWorldRendererComponent::Deactivate() + { + // On component deactivation, unregister the feature processor and remove it from the default scene. + + m_terrainRendererActive = false; + AzFramework::Terrain::TerrainDataNotificationBus::Handler::BusDisconnect(); + + if (AZ::RPI::Scene* scene = GetScene(); scene) + { + if (scene->GetFeatureProcessor()) + { + scene->DisableFeatureProcessor(); + } + } + m_terrainFeatureProcessor = nullptr; + + AZ::RPI::FeatureProcessorFactory::Get()->UnregisterFeatureProcessor(); + } + + bool TerrainWorldRendererComponent::ReadInConfig(const AZ::ComponentConfig* baseConfig) + { + if (auto config = azrtti_cast(baseConfig)) + { + m_configuration = *config; + return true; + } + return false; + } + + bool TerrainWorldRendererComponent::WriteOutConfig(AZ::ComponentConfig* outBaseConfig) const + { + if (auto config = azrtti_cast(outBaseConfig)) + { + *config = m_configuration; + return true; + } + return false; + } + + void TerrainWorldRendererComponent::OnTerrainDataDestroyBegin() + { + // If the terrain is being destroyed, remove all existing terrain data from the feature processor. + + if (m_terrainFeatureProcessor) + { + m_terrainFeatureProcessor->RemoveTerrainData(); + } + } + + void TerrainWorldRendererComponent::OnTerrainDataChanged([[maybe_unused]] const AZ::Aabb& dirtyRegion, [[maybe_unused]] TerrainDataChangedMask dataChangedMask) + { + // Block other threads from accessing the surface data bus while we are in GetValue (which may call into the SurfaceData bus). + // We lock our surface data mutex *before* checking / setting "isRequestInProgress" so that we prevent race conditions + // that create false detection of cyclic dependencies when multiple requests occur on different threads simultaneously. + // (One case where this was previously able to occur was in rapid updating of the Preview widget on the + // GradientSurfaceDataComponent in the Editor when moving the threshold sliders back and forth rapidly) + auto& surfaceDataContext = SurfaceData::SurfaceDataSystemRequestBus::GetOrCreateContext(false); + typename SurfaceData::SurfaceDataSystemRequestBus::Context::DispatchLockGuard scopeLock(surfaceDataContext.m_contextMutex); + + AZ::Vector2 queryResolution = AZ::Vector2(1.0f); + AzFramework::Terrain::TerrainDataRequestBus::BroadcastResult( + queryResolution, &AzFramework::Terrain::TerrainDataRequests::GetTerrainHeightQueryResolution); + + AZ::Aabb worldBounds = AZ::Aabb::CreateNull(); + AzFramework::Terrain::TerrainDataRequestBus::BroadcastResult( + worldBounds, &AzFramework::Terrain::TerrainDataRequests::GetTerrainAabb); + + + AZ::Transform transform = AZ::Transform::CreateTranslation(worldBounds.GetCenter()); + + uint32_t width = aznumeric_cast( + (float)worldBounds.GetXExtent() / queryResolution.GetX()); + uint32_t height = aznumeric_cast( + (float)worldBounds.GetYExtent() / queryResolution.GetY()); + AZStd::vector pixels; + pixels.resize_no_construct(width * height); + const uint32_t pixelDataSize = width * height * sizeof(float); + memset(pixels.data(), 0, pixelDataSize); + + for (uint32_t y = 0; y < height; y++) + { + for (uint32_t x = 0; x < width; x++) + { + bool terrainExists = true; + float terrainHeight = 0.0f; + AzFramework::Terrain::TerrainDataRequestBus::BroadcastResult( + terrainHeight, &AzFramework::Terrain::TerrainDataRequests::GetHeightFromFloats, + (x * queryResolution.GetX()) + worldBounds.GetMin().GetX(), + (y * queryResolution.GetY()) + worldBounds.GetMin().GetY(), + AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT, + &terrainExists); + + pixels[(y * width) + x] = + (terrainHeight - worldBounds.GetMin().GetZ()) / worldBounds.GetExtents().GetZ(); + } + } + + if (m_terrainFeatureProcessor) + { + m_terrainFeatureProcessor->UpdateTerrainData(transform, worldBounds, queryResolution.GetX(), width, height, pixels); + } + } + +} diff --git a/Gems/Terrain/Code/Source/Components/TerrainWorldRendererComponent.h b/Gems/Terrain/Code/Source/Components/TerrainWorldRendererComponent.h new file mode 100644 index 0000000000..a1e9498be8 --- /dev/null +++ b/Gems/Terrain/Code/Source/Components/TerrainWorldRendererComponent.h @@ -0,0 +1,75 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include +#include +#include + +namespace LmbrCentral +{ + template + class EditorWrappedComponentBase; +} + +namespace AZ::RPI +{ + class Scene; +} + +namespace Terrain +{ + class TerrainFeatureProcessor; + + class TerrainWorldRendererConfig + : public AZ::ComponentConfig + { + public: + AZ_CLASS_ALLOCATOR(TerrainWorldRendererConfig, AZ::SystemAllocator, 0); + AZ_RTTI(TerrainWorldRendererConfig, "{08C5863C-092D-4A69-8226-4978E4F6E343}", AZ::ComponentConfig); + static void Reflect(AZ::ReflectContext* context); + }; + + + class TerrainWorldRendererComponent + : public AZ::Component + , public AzFramework::Terrain::TerrainDataNotificationBus::Handler + { + public: + template + friend class LmbrCentral::EditorWrappedComponentBase; + AZ_COMPONENT(TerrainWorldRendererComponent, "{3B0DB71E-5944-437C-8C88-70F8B405BFC7}"); + static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& services); + static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& services); + static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& services); + static void Reflect(AZ::ReflectContext* context); + + TerrainWorldRendererComponent(const TerrainWorldRendererConfig& configuration); + TerrainWorldRendererComponent() = default; + ~TerrainWorldRendererComponent() override; + + ////////////////////////////////////////////////////////////////////////// + // AZ::Component interface implementation + void Activate() override; + void Deactivate() override; + bool ReadInConfig(const AZ::ComponentConfig* baseConfig) override; + bool WriteOutConfig(AZ::ComponentConfig* outBaseConfig) const override; + + protected: + void OnTerrainDataDestroyBegin() override; + void OnTerrainDataChanged(const AZ::Aabb& dirtyRegion, TerrainDataChangedMask dataChangedMask) override; + + AZ::RPI::Scene* GetScene() const; + + private: + TerrainWorldRendererConfig m_configuration; + bool m_terrainRendererActive{ false }; + TerrainFeatureProcessor* m_terrainFeatureProcessor{ nullptr }; + }; +} diff --git a/Gems/Terrain/Code/Source/EditorComponents/EditorTerrainWorldRendererComponent.cpp b/Gems/Terrain/Code/Source/EditorComponents/EditorTerrainWorldRendererComponent.cpp new file mode 100644 index 0000000000..2d42585fa5 --- /dev/null +++ b/Gems/Terrain/Code/Source/EditorComponents/EditorTerrainWorldRendererComponent.cpp @@ -0,0 +1,56 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include +#include + +namespace Terrain +{ + void EditorTerrainWorldRendererComponent::Reflect(AZ::ReflectContext* context) + { + BaseClassType::Reflect(context); + + AZ::SerializeContext* serializeContext = azrtti_cast(context); + + if (serializeContext) + { + serializeContext->Class() + ->Version(0) + ; + + if (auto editContext = serializeContext->GetEditContext()) + { + editContext->Class( + "Terrain World Renderer", "") + ->ClassElement(AZ::Edit::ClassElements::EditorData, "") + ->Attribute(AZ::Edit::Attributes::Category, "Terrain") + ->Attribute(AZ::Edit::Attributes::Icon, "Editor/Icons/Components/TerrainWorldRenderer.svg") + ->Attribute(AZ::Edit::Attributes::ViewportIcon, "Editor/Icons/Components/Viewport/TerrainWorldRenderer.svg") + ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZStd::vector({ AZ_CRC_CE("Level") })) + ; + } + } + } + + + void EditorTerrainWorldRendererComponent::Init() + { + BaseClassType::Init(); + } + + void EditorTerrainWorldRendererComponent::Activate() + { + BaseClassType::Activate(); + } + + AZ::u32 EditorTerrainWorldRendererComponent::ConfigurationChanged() + { + return BaseClassType::ConfigurationChanged(); + } +} diff --git a/Gems/Terrain/Code/Source/EditorComponents/EditorTerrainWorldRendererComponent.h b/Gems/Terrain/Code/Source/EditorComponents/EditorTerrainWorldRendererComponent.h new file mode 100644 index 0000000000..92ce648988 --- /dev/null +++ b/Gems/Terrain/Code/Source/EditorComponents/EditorTerrainWorldRendererComponent.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include +#include +#include + +namespace Terrain +{ + class EditorTerrainWorldRendererComponent + : public LmbrCentral::EditorWrappedComponentBase + { + public: + using BaseClassType = LmbrCentral::EditorWrappedComponentBase; + AZ_EDITOR_COMPONENT(EditorTerrainWorldRendererComponent, "{7BEFF763-89A6-4EDA-B199-B049A8E757AF}", BaseClassType); + static void Reflect(AZ::ReflectContext* context); + + ////////////////////////////////////////////////////////////////////////// + // AZ::Component interface implementation + void Init() override; + void Activate() override; + AZ::u32 ConfigurationChanged() override; + + protected: + using BaseClassType::m_configuration; + using BaseClassType::m_component; + using BaseClassType::m_visible; + + private: + }; +} diff --git a/Gems/Terrain/Code/Source/EditorTerrainModule.cpp b/Gems/Terrain/Code/Source/EditorTerrainModule.cpp index 0ba416cbb7..5305087694 100644 --- a/Gems/Terrain/Code/Source/EditorTerrainModule.cpp +++ b/Gems/Terrain/Code/Source/EditorTerrainModule.cpp @@ -12,6 +12,7 @@ #include #include #include +#include namespace Terrain { @@ -25,6 +26,7 @@ namespace Terrain Terrain::EditorTerrainSystemComponent::CreateDescriptor(), Terrain::EditorTerrainWorldComponent::CreateDescriptor(), Terrain::EditorTerrainWorldDebuggerComponent::CreateDescriptor(), + Terrain::EditorTerrainWorldRendererComponent::CreateDescriptor(), }); } diff --git a/Gems/Terrain/Code/Source/TerrainModule.cpp b/Gems/Terrain/Code/Source/TerrainModule.cpp index 29b2542472..24b87b4ab3 100644 --- a/Gems/Terrain/Code/Source/TerrainModule.cpp +++ b/Gems/Terrain/Code/Source/TerrainModule.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -26,6 +27,7 @@ namespace Terrain TerrainSystemComponent::CreateDescriptor(), TerrainWorldComponent::CreateDescriptor(), TerrainWorldDebuggerComponent::CreateDescriptor(), + TerrainWorldRendererComponent::CreateDescriptor(), TerrainHeightGradientListComponent::CreateDescriptor(), TerrainLayerSpawnerComponent::CreateDescriptor(), TerrainSurfaceDataSystemComponent::CreateDescriptor(), diff --git a/Gems/Terrain/Code/Source/TerrainSystem/TerrainSystem.cpp b/Gems/Terrain/Code/Source/TerrainSystem/TerrainSystem.cpp index 5ec6af6f37..46ac677dd1 100644 --- a/Gems/Terrain/Code/Source/TerrainSystem/TerrainSystem.cpp +++ b/Gems/Terrain/Code/Source/TerrainSystem/TerrainSystem.cpp @@ -12,10 +12,6 @@ #include #include -#include -#include -#include - using namespace Terrain; bool TerrainLayerPriorityComparator::operator()(const AZ::EntityId& layer1id, const AZ::EntityId& layer2id) const @@ -114,18 +110,6 @@ void TerrainSystem::Deactivate() m_terrainSettingsDirty = true; m_requestedSettings.m_systemActive = false; - if (auto rpi = AZ::RPI::RPISystemInterface::Get(); rpi) - { - if (auto defaultScene = rpi->GetDefaultScene(); defaultScene) - { - const AZ::RPI::Scene* scene = defaultScene.get(); - if (auto terrainFeatureProcessor = scene->GetFeatureProcessor(); terrainFeatureProcessor) - { - terrainFeatureProcessor->RemoveTerrainData(); - } - } - } - AzFramework::Terrain::TerrainDataNotificationBus::Broadcast( &AzFramework::Terrain::TerrainDataNotificationBus::Events::OnTerrainDataDestroyEnd); } @@ -507,60 +491,6 @@ void TerrainSystem::OnTick(float /*deltaTime*/, AZ::ScriptTimePoint /*time*/) m_currentSettings = m_requestedSettings; } - if (m_currentSettings.m_systemActive && m_terrainHeightDirty) - { - AZStd::shared_lock lock(m_areaMutex); - - // Block other threads from accessing the surface data bus while we are in GetValue (which may call into the SurfaceData bus). - // We lock our surface data mutex *before* checking / setting "isRequestInProgress" so that we prevent race conditions - // that create false detection of cyclic dependencies when multiple requests occur on different threads simultaneously. - // (One case where this was previously able to occur was in rapid updating of the Preview widget on the - // GradientSurfaceDataComponent in the Editor when moving the threshold sliders back and forth rapidly) - auto& surfaceDataContext = SurfaceData::SurfaceDataSystemRequestBus::GetOrCreateContext(false); - typename SurfaceData::SurfaceDataSystemRequestBus::Context::DispatchLockGuard scopeLock(surfaceDataContext.m_contextMutex); - - AZ::Transform transform = AZ::Transform::CreateTranslation(m_currentSettings.m_worldBounds.GetCenter()); - - uint32_t width = aznumeric_cast( - (float)m_currentSettings.m_worldBounds.GetXExtent() / m_currentSettings.m_heightQueryResolution.GetX()); - uint32_t height = aznumeric_cast( - (float)m_currentSettings.m_worldBounds.GetYExtent() / m_currentSettings.m_heightQueryResolution.GetY()); - AZStd::vector pixels; - pixels.resize_no_construct(width * height); - const uint32_t pixelDataSize = width * height * sizeof(float); - memset(pixels.data(), 0, pixelDataSize); - - for (uint32_t y = 0; y < height; y++) - { - for (uint32_t x = 0; x < width; x++) - { - bool terrainExists; - float terrainHeight = GetTerrainAreaHeight( - (x * m_currentSettings.m_heightQueryResolution.GetX()) + m_currentSettings.m_worldBounds.GetMin().GetX(), - (y * m_currentSettings.m_heightQueryResolution.GetY()) + m_currentSettings.m_worldBounds.GetMin().GetY(), - terrainExists); - - pixels[(y * width) + x] = - (terrainHeight - m_currentSettings.m_worldBounds.GetMin().GetZ()) / - m_currentSettings.m_worldBounds.GetExtents().GetZ(); - } - } - - if (auto rpi = AZ::RPI::RPISystemInterface::Get(); rpi) - { - if (auto defaultScene = rpi->GetDefaultScene(); defaultScene) - { - const AZ::RPI::Scene* scene = defaultScene.get(); - if (auto terrainFeatureProcessor = scene->GetFeatureProcessor(); terrainFeatureProcessor) - { - terrainFeatureProcessor->UpdateTerrainData( - transform, m_currentSettings.m_worldBounds, m_currentSettings.m_heightQueryResolution.GetX(), width, height, - pixels); - } - } - } - } - if (terrainSettingsChanged || m_terrainHeightDirty) { // Block other threads from accessing the surface data bus while we are in GetValue (which may call into the SurfaceData bus). diff --git a/Gems/Terrain/Code/Tests/TerrainSystemTest.cpp b/Gems/Terrain/Code/Tests/TerrainSystemTest.cpp index 91a9744f0b..e4fb0f348e 100644 --- a/Gems/Terrain/Code/Tests/TerrainSystemTest.cpp +++ b/Gems/Terrain/Code/Tests/TerrainSystemTest.cpp @@ -19,15 +19,31 @@ #include using ::testing::AtLeast; +using ::testing::FloatNear; +using ::testing::FloatEq; +using ::testing::IsFalse; +using ::testing::Ne; using ::testing::NiceMock; using ::testing::Return; class TerrainSystemTest : public ::testing::Test { protected: + // Defines a structure for defining both an XY position and the expected height for that position. + struct HeightTestPoint + { + AZ::Vector2 m_testLocation; + float m_expectedHeight; + }; + AZ::ComponentApplication m_app; AZStd::unique_ptr m_terrainSystem; + AZStd::unique_ptr> m_boxShapeRequests; + AZStd::unique_ptr> m_shapeRequests; + AZStd::unique_ptr> m_terrainAreaHeightRequests; + + void SetUp() override { AZ::ComponentApplication::Descriptor appDesc; @@ -41,6 +57,9 @@ protected: void TearDown() override { m_terrainSystem.reset(); + m_boxShapeRequests.reset(); + m_shapeRequests.reset(); + m_terrainAreaHeightRequests.reset(); m_app.Destroy(); } @@ -71,6 +90,53 @@ protected: m_app.RegisterComponentDescriptor(Component::CreateDescriptor()); return entity->CreateComponent(); } + + // Create a terrain system with reasonable defaults for testing, but with the ability to override the defaults + // on a test-by-test basis. + void CreateAndActivateTerrainSystem( + AZ::Vector2 queryResolution = AZ::Vector2(1.0f), + AZ::Aabb worldBounds = AZ::Aabb::CreateFromMinMax(AZ::Vector3(-128.0f), AZ::Vector3(128.0f))) + { + // Create the terrain system and give it one tick to fully initialize itself. + m_terrainSystem = AZStd::make_unique(); + m_terrainSystem->SetTerrainAabb(worldBounds); + m_terrainSystem->SetTerrainHeightQueryResolution(queryResolution); + m_terrainSystem->Activate(); + AZ::TickBus::Broadcast(&AZ::TickBus::Events::OnTick, 0.f, AZ::ScriptTimePoint{}); + } + + + AZStd::unique_ptr CreateAndActivateMockTerrainLayerSpawner( + const AZ::Aabb& spawnerBox, + const AZStd::function& mockHeights) + { + // Create the base entity with a mock box shape, Terrain Layer Spawner, and height provider. + auto entity = CreateEntity(); + CreateComponent(entity.get()); + CreateComponent(entity.get()); + + m_boxShapeRequests = AZStd::make_unique>(entity->GetId()); + m_shapeRequests = AZStd::make_unique>(entity->GetId()); + + // Set up the box shape to return whatever spawnerBox was passed in. + ON_CALL(*m_shapeRequests, GetEncompassingAabb).WillByDefault(Return(spawnerBox)); + + // Set up a mock height provider to use the passed-in mock height function to generate a height. + m_terrainAreaHeightRequests = AZStd::make_unique>(entity->GetId()); + ON_CALL(*m_terrainAreaHeightRequests, GetHeight) + .WillByDefault( + [mockHeights](const AZ::Vector3& inPosition, AZ::Vector3& outPosition, bool& terrainExists) + { + // By default, set the outPosition to the input position and terrain to always exist. + outPosition = inPosition; + terrainExists = true; + // Let the test function modify these values based on the needs of the specific test. + mockHeights(outPosition, terrainExists); + }); + + ActivateEntity(entity.get()); + return entity; + } }; TEST_F(TerrainSystemTest, TrivialCreateDestroy) @@ -122,10 +188,8 @@ TEST_F(TerrainSystemTest, TerrainDoesNotExistWhenNoTerrainLayerSpawnersAreRegist // will return false for terrainExists, returns a height equal to the min world bounds of the terrain system, and returns // a normal facing up the Z axis. - // Create the terrain system and give it one tick to fully initialize itself. - m_terrainSystem = AZStd::make_unique(); - m_terrainSystem->Activate(); - AZ::TickBus::Broadcast(&AZ::TickBus::Events::OnTick, 0.f, AZ::ScriptTimePoint{}); + // Create and activate the terrain system with our testing defaults for world bounds and query resolution. + CreateAndActivateTerrainSystem(); AZ::Aabb worldBounds = m_terrainSystem->GetTerrainAabb(); @@ -139,7 +203,7 @@ TEST_F(TerrainSystemTest, TerrainDoesNotExistWhenNoTerrainLayerSpawnersAreRegist bool terrainExists = true; float height = m_terrainSystem->GetHeight(position, AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT, &terrainExists); EXPECT_FALSE(terrainExists); - EXPECT_EQ(height, worldBounds.GetMin().GetZ()); + EXPECT_FLOAT_EQ(height, worldBounds.GetMin().GetZ()); terrainExists = true; AZ::Vector3 normal = m_terrainSystem->GetNormal( @@ -162,37 +226,21 @@ TEST_F(TerrainSystemTest, TerrainExistsOnlyWithinTerrainLayerSpawnerBounds) // The terrain system should only query Heights from the TerrainAreaHeightRequest bus within the // TerrainLayerSpawner region, and so those values should only get returned from GetHeight for queries inside that region. - // Create the base entity with a mock Box Shape and a Terrain Layer Spawner. - auto entity = CreateEntity(); - CreateComponent(entity.get()); - CreateComponent(entity.get()); - - // Set up the box shape to return a box from (0,0,5) to (10, 10, 15) - AZ::Aabb spawnerBox = AZ::Aabb::CreateFromMinMaxValues(0.0f, 0.0f, 5.0f, 10.0f, 10.0f, 15.0f); - NiceMock boxShapeRequests(entity->GetId()); - NiceMock shapeRequests(entity->GetId()); - ON_CALL(shapeRequests, GetEncompassingAabb).WillByDefault(Return(spawnerBox)); - - // Set up a mock height provider that always returns 5.0 and a normal of Y-up. - const float spawnerHeight = 5.0f; - NiceMock terrainAreaHeightRequests(entity->GetId()); - ON_CALL(terrainAreaHeightRequests, GetHeight) - .WillByDefault( - [spawnerHeight](const AZ::Vector3& inPosition, AZ::Vector3& outPosition, bool& terrainExists) - { - outPosition = inPosition; - outPosition.SetZ(spawnerHeight); - terrainExists = true; - }); - - ActivateEntity(entity.get()); + // Create a mock terrain layer spawner that uses a box of (0,0,5) - (10,10,15) and always returns a height of 5. + constexpr float spawnerHeight = 5.0f; + const AZ::Aabb spawnerBox = AZ::Aabb::CreateFromMinMaxValues(0.0f, 0.0f, 5.0f, 10.0f, 10.0f, 15.0f); + auto entity = CreateAndActivateMockTerrainLayerSpawner( + spawnerBox, + [](AZ::Vector3& position, bool& terrainExists) + { + position.SetZ(spawnerHeight); + terrainExists = true; + }); // Verify that terrain exists within the layer spawner bounds, and doesn't exist outside of it. - // Create the terrain system and give it one tick to fully initialize itself. - m_terrainSystem = AZStd::make_unique(); - m_terrainSystem->Activate(); - AZ::TickBus::Broadcast(&AZ::TickBus::Events::OnTick, 0.f, AZ::ScriptTimePoint{}); + // Create and activate the terrain system with our testing defaults for world bounds and query resolution. + CreateAndActivateTerrainSystem(); // Create a box that's twice as big as the layer spawner box. Loop through it and verify that points within the layer box contain // terrain and the expected height & normal values, and points outside the layer box don't contain terrain. @@ -215,7 +263,7 @@ TEST_F(TerrainSystemTest, TerrainExistsOnlyWithinTerrainLayerSpawnerBounds) { EXPECT_TRUE(heightQueryTerrainExists); EXPECT_FALSE(isHole); - EXPECT_EQ(height, spawnerHeight); + EXPECT_FLOAT_EQ(height, spawnerHeight); } else { @@ -226,3 +274,202 @@ TEST_F(TerrainSystemTest, TerrainExistsOnlyWithinTerrainLayerSpawnerBounds) } } +TEST_F(TerrainSystemTest, TerrainHeightQueriesWithExactSamplersIgnoreQueryGrid) +{ + // Verify that when using the "EXACT" height sampler, the returned heights come directly from the height provider at the exact + // requested location, instead of the position being quantized to the height query grid. + + // Create a mock terrain layer spawner that uses a box of (0,0,5) - (10,10,15) and generates a height based on a sine wave + // using a frequency of 1m and an amplitude of 10m. i.e. Heights will range between -10 to 10 meters, but will have a value of 0 + // every 0.5 meters. The sine wave value is based on the absolute X position only, for simplicity. + constexpr float amplitudeMeters = 10.0f; + constexpr float frequencyMeters = 1.0f; + const AZ::Aabb spawnerBox = AZ::Aabb::CreateFromMinMaxValues(0.0f, 0.0f, 5.0f, 10.0f, 10.0f, 15.0f); + auto entity = CreateAndActivateMockTerrainLayerSpawner( + spawnerBox, + [](AZ::Vector3& position, bool& terrainExists) + { + position.SetZ(amplitudeMeters * sin(AZ::Constants::TwoPi * (position.GetX() / frequencyMeters))); + terrainExists = true; + }); + + // Create and activate the terrain system with our testing defaults for world bounds, and a query resolution that exactly matches + // the frequency of our sine wave. If our height queries rely on the query resolution, we should always get a value of 0. + const AZ::Vector2 queryResolution(frequencyMeters); + CreateAndActivateTerrainSystem(queryResolution); + + // Test an arbitrary set of points that should all produce non-zero heights with the EXACT sampler. They're not aligned with the + // query resolution, or with the 0 points on the sine wave. + const AZ::Vector2 nonZeroPoints[] = { AZ::Vector2(0.3f), AZ::Vector2(2.8f), AZ::Vector2(5.9f), AZ::Vector2(7.7f) }; + for (auto& nonZeroPoint : nonZeroPoints) + { + AZ::Vector3 position(nonZeroPoint.GetX(), nonZeroPoint.GetY(), 0.0f); + bool heightQueryTerrainExists = false; + float height = + m_terrainSystem->GetHeight(position, AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT, &heightQueryTerrainExists); + + // We've chosen a bunch of places on the sine wave that should return a non-zero positive or negative value. + constexpr float epsilon = 0.0001f; + EXPECT_GT(fabsf(height), epsilon); + } + + // Test an arbitrary set of points that should all produce zero heights with the EXACT sampler, since they align with 0 points on the + // sine wave, regardless of whether or not they align to the query resolution. + const AZ::Vector2 zeroPoints[] = { AZ::Vector2(0.5f), AZ::Vector2(1.0f), AZ::Vector2(5.0f), AZ::Vector2(7.5f) }; + for (auto& zeroPoint : zeroPoints) + { + AZ::Vector3 position(zeroPoint.GetX(), zeroPoint.GetY(), 0.0f); + bool heightQueryTerrainExists = false; + float height = + m_terrainSystem->GetHeight(position, AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT, &heightQueryTerrainExists); + + constexpr float epsilon = 0.0001f; + EXPECT_NEAR(height, 0.0f, epsilon); + } +} + +TEST_F(TerrainSystemTest, TerrainHeightQueriesWithClampSamplersUseQueryGrid) +{ + // Verify that when using the "CLAMP" height sampler, the requested location is quantized to the height query grid before fetching + // the height. + + // Create a mock terrain layer spawner that uses a box of (-10,-10,-5) - (10,10,15) and generates a height equal + // to the X + Y position, so if either one doesn't get clamped we'll get an unexpected result. + const AZ::Aabb spawnerBox = AZ::Aabb::CreateFromMinMaxValues(-10.0f, -10.0f, -5.0f, 10.0f, 10.0f, 15.0f); + auto entity = CreateAndActivateMockTerrainLayerSpawner( + spawnerBox, + [](AZ::Vector3& position, bool& terrainExists) + { + position.SetZ(position.GetX() + position.GetY()); + terrainExists = true; + }); + + // Create and activate the terrain system with our testing defaults for world bounds, and a query resolution at 0.25 meter intervals. + const AZ::Vector2 queryResolution(0.25f); + CreateAndActivateTerrainSystem(queryResolution); + + // Test some points and verify that the results always go "downward", whether they're in positive or negative space. + // (Z contains the the expected result for convenience). + const HeightTestPoint testPoints[] = + { + { AZ::Vector2(0.0f, 0.0f), 0.0f }, // Should return a height of 0.00 + 0.00 + { AZ::Vector2(0.3f, 0.3f), 0.5f }, // Should return a height of 0.25 + 0.25 + { AZ::Vector2(2.8f, 2.8f), 5.5f }, // Should return a height of 2.75 + 2.75 + { AZ::Vector2(5.5f, 5.5f), 11.0f }, // Should return a height of 5.50 + 5.50 + { AZ::Vector2(7.7f, 7.7f), 15.0f }, // Should return a height of 7.50 + 7.50 + + { AZ::Vector2(-0.3f, -0.3f), -1.0f }, // Should return a height of -0.50 + -0.50 + { AZ::Vector2(-2.8f, -2.8f), -6.0f }, // Should return a height of -3.00 + -3.00 + { AZ::Vector2(-5.5f, -5.5f), -11.0f }, // Should return a height of -5.50 + -5.50 + { AZ::Vector2(-7.7f, -7.7f), -15.5f } // Should return a height of -7.75 + -7.75 + }; + for (auto& testPoint : testPoints) + { + const float expectedHeight = testPoint.m_expectedHeight; + + AZ::Vector3 position(testPoint.m_testLocation.GetX(), testPoint.m_testLocation.GetY(), 0.0f); + bool heightQueryTerrainExists = false; + float height = + m_terrainSystem->GetHeight(position, AzFramework::Terrain::TerrainDataRequests::Sampler::CLAMP, &heightQueryTerrainExists); + + constexpr float epsilon = 0.0001f; + EXPECT_NEAR(height, expectedHeight, epsilon); + } +} + +TEST_F(TerrainSystemTest, TerrainHeightQueriesWithBilinearSamplersUseQueryGridToInterpolate) +{ + // Verify that when using the "BILINEAR" height sampler, the heights are interpolated from points sampled from the query grid. + + // Create a mock terrain layer spawner that uses a box of (-10,-10,-5) - (10,10,15) and generates a height equal + // to the X + Y position, so we'll have heights that look like this on our grid: + // 0 *---* 1 + // | | + // 1 *---* 2 + // However, everywhere inside the grid box, we'll generate heights much larger than X + Y. It will have no effect on exact grid + // points, but it will noticeably affect the expected height values if any points get sampled in-between grid points. + + const AZ::Aabb spawnerBox = AZ::Aabb::CreateFromMinMaxValues(-10.0f, -10.0f, -5.0f, 10.0f, 10.0f, 15.0f); + const float amplitudeMeters = 10.0f; + const float frequencyMeters = 1.0f; + auto entity = CreateAndActivateMockTerrainLayerSpawner( + spawnerBox, + [amplitudeMeters, frequencyMeters](AZ::Vector3& position, bool& terrainExists) + { + // Our generated height will be X + Y. + float expectedHeight = position.GetX() + position.GetY(); + + // If either X or Y aren't evenly divisible by the query frequency, add a scaled value to our generated height. + // This will show up as an unexpected height "spike" if it gets used in any bilinear filter queries. + float unexpectedVariance = amplitudeMeters * + (fmodf(position.GetX(), frequencyMeters) + fmodf(position.GetY(), frequencyMeters)); + position.SetZ(expectedHeight + unexpectedVariance); + terrainExists = true; + }); + + // Create and activate the terrain system with our testing defaults for world bounds, and a query resolution at 1 meter intervals. + const AZ::Vector2 queryResolution(frequencyMeters); + CreateAndActivateTerrainSystem(queryResolution); + + // Test some points and verify that the results are the expected bilinear filtered result, + // whether they're in positive or negative space. + // (Z contains the the expected result for convenience). + const HeightTestPoint testPoints[] = { + + // Queries directly on grid points. These should return values of X + Y. + { AZ::Vector2(0.0f, 0.0f), 0.0f }, // Should return a height of 0 + 0 + { AZ::Vector2(1.0f, 0.0f), 1.0f }, // Should return a height of 1 + 0 + { AZ::Vector2(0.0f, 1.0f), 1.0f }, // Should return a height of 0 + 1 + { AZ::Vector2(1.0f, 1.0f), 2.0f }, // Should return a height of 1 + 1 + { AZ::Vector2(3.0f, 5.0f), 8.0f }, // Should return a height of 3 + 5 + + { AZ::Vector2(-1.0f, 0.0f), -1.0f }, // Should return a height of -1 + 0 + { AZ::Vector2(0.0f, -1.0f), -1.0f }, // Should return a height of 0 + -1 + { AZ::Vector2(-1.0f, -1.0f), -2.0f }, // Should return a height of -1 + -1 + { AZ::Vector2(-3.0f, -5.0f), -8.0f }, // Should return a height of -3 + -5 + + // Queries that are on a grid edge (one axis on the grid, the other somewhere in-between). + // These should just be a linear interpolation of the points, so it should still be X + Y. + + { AZ::Vector2(0.25f, 0.0f), 0.25f }, // Should return a height of -0.25 + 0 + { AZ::Vector2(3.75f, 0.0f), 3.75f }, // Should return a height of -3.75 + 0 + { AZ::Vector2(0.0f, 0.25f), 0.25f }, // Should return a height of 0 + -0.25 + { AZ::Vector2(0.0f, 3.75f), 3.75f }, // Should return a height of 0 + -3.75 + + { AZ::Vector2(2.0f, 3.75f), 5.75f }, // Should return a height of -2 + -3.75 + { AZ::Vector2(2.25f, 4.0f), 6.25f }, // Should return a height of -2.25 + -4 + + { AZ::Vector2(-0.25f, 0.0f), -0.25f }, // Should return a height of -0.25 + 0 + { AZ::Vector2(-3.75f, 0.0f), -3.75f }, // Should return a height of -3.75 + 0 + { AZ::Vector2(0.0f, -0.25f), -0.25f }, // Should return a height of 0 + -0.25 + { AZ::Vector2(0.0f, -3.75f), -3.75f }, // Should return a height of 0 + -3.75 + + { AZ::Vector2(-2.0f, -3.75f), -5.75f }, // Should return a height of -2 + -3.75 + { AZ::Vector2(-2.25f, -4.0f), -6.25f }, // Should return a height of -2.25 + -4 + + // Queries inside a grid square (both axes are in-between grid points) + // This is a full bilinear interpolation, but because we're using X + Y for our heights, the interpolated values + // should *still* be X + Y assuming the points were sampled correctly from the grid points. + + { AZ::Vector2(3.25f, 5.25f), 8.5f }, // Should return a height of 3.25 + 5.25 + { AZ::Vector2(7.71f, 9.74f), 17.45f }, // Should return a height of 7.71 + 9.74 + + { AZ::Vector2(-3.25f, -5.25f), -8.5f }, // Should return a height of -3.25 + -5.25 + { AZ::Vector2(-7.71f, -9.74f), -17.45f }, // Should return a height of -7.71 + -9.74 + }; + + // Loop through every test point and validate it. + for (auto& testPoint : testPoints) + { + const float expectedHeight = testPoint.m_expectedHeight; + + AZ::Vector3 position(testPoint.m_testLocation.GetX(), testPoint.m_testLocation.GetY(), 0.0f); + bool heightQueryTerrainExists = false; + float height = + m_terrainSystem->GetHeight(position, AzFramework::Terrain::TerrainDataRequests::Sampler::BILINEAR, &heightQueryTerrainExists); + + // Verify that our height query returned the bilinear filtered result we expect. + constexpr float epsilon = 0.0001f; + EXPECT_NEAR(height, expectedHeight, epsilon); + } +} diff --git a/Gems/Terrain/Code/terrain_editor_shared_files.cmake b/Gems/Terrain/Code/terrain_editor_shared_files.cmake index 9868f7d310..d4719f46d5 100644 --- a/Gems/Terrain/Code/terrain_editor_shared_files.cmake +++ b/Gems/Terrain/Code/terrain_editor_shared_files.cmake @@ -15,6 +15,8 @@ set(FILES Source/EditorComponents/EditorTerrainWorldComponent.h Source/EditorComponents/EditorTerrainWorldDebuggerComponent.cpp Source/EditorComponents/EditorTerrainWorldDebuggerComponent.h + Source/EditorComponents/EditorTerrainWorldRendererComponent.cpp + Source/EditorComponents/EditorTerrainWorldRendererComponent.h Source/EditorComponents/EditorTerrainSystemComponent.cpp Source/EditorComponents/EditorTerrainSystemComponent.h Source/EditorTerrainModule.cpp diff --git a/Gems/Terrain/Code/terrain_files.cmake b/Gems/Terrain/Code/terrain_files.cmake index 6a76dd7141..2c013fa332 100644 --- a/Gems/Terrain/Code/terrain_files.cmake +++ b/Gems/Terrain/Code/terrain_files.cmake @@ -19,6 +19,8 @@ set(FILES Source/Components/TerrainWorldComponent.h Source/Components/TerrainWorldDebuggerComponent.cpp Source/Components/TerrainWorldDebuggerComponent.h + Source/Components/TerrainWorldRendererComponent.cpp + Source/Components/TerrainWorldRendererComponent.h Source/TerrainRenderer/TerrainFeatureProcessor.cpp Source/TerrainRenderer/TerrainFeatureProcessor.h Source/TerrainSystem/TerrainSystem.cpp From d8614948f3c43e11f4c31ce09b2a2a8efc4056bf Mon Sep 17 00:00:00 2001 From: Chris Burel Date: Mon, 27 Sep 2021 13:30:37 -0700 Subject: [PATCH 35/51] Make InputChannelId's constructor `constexpr` (#4220) This allows for all the global static `InputChannelId`s to be `constexpr` as well, which prevents some global constructors, including some CRC32 calculations on application startup. Signed-off-by: Chris Burel --- .../Input/Channels/InputChannelId.cpp | 26 +- .../Input/Channels/InputChannelId.h | 41 +- .../Devices/Gamepad/InputDeviceGamepad.cpp | 85 ----- .../Devices/Gamepad/InputDeviceGamepad.h | 110 ++++-- .../Devices/Keyboard/InputDeviceKeyboard.cpp | 273 ------------- .../Devices/Keyboard/InputDeviceKeyboard.h | 359 ++++++++++++------ .../Devices/Motion/InputDeviceMotion.cpp | 38 -- .../Input/Devices/Motion/InputDeviceMotion.h | 43 ++- .../Input/Devices/Mouse/InputDeviceMouse.cpp | 29 -- .../Input/Devices/Mouse/InputDeviceMouse.h | 34 +- .../Input/Devices/Touch/InputDeviceTouch.cpp | 25 -- .../Input/Devices/Touch/InputDeviceTouch.h | 34 +- .../InputDeviceVirtualKeyboard.cpp | 11 - .../InputDeviceVirtualKeyboard.h | 13 +- 14 files changed, 430 insertions(+), 691 deletions(-) diff --git a/Code/Framework/AzFramework/AzFramework/Input/Channels/InputChannelId.cpp b/Code/Framework/AzFramework/AzFramework/Input/Channels/InputChannelId.cpp index cc6154f20c..496d89d767 100644 --- a/Code/Framework/AzFramework/AzFramework/Input/Channels/InputChannelId.cpp +++ b/Code/Framework/AzFramework/AzFramework/Input/Channels/InputChannelId.cpp @@ -28,34 +28,10 @@ namespace AzFramework } } - //////////////////////////////////////////////////////////////////////////////////////////////// - InputChannelId::InputChannelId(const char* name) - : m_crc32(name) - { - memset(m_name, 0, AZ_ARRAY_SIZE(m_name)); - azstrncpy(m_name, NAME_BUFFER_SIZE, name, MAX_NAME_LENGTH); - } - - //////////////////////////////////////////////////////////////////////////////////////////////// - InputChannelId::InputChannelId(const InputChannelId& other) - : m_crc32(other.m_crc32) - { - memset(m_name, 0, AZ_ARRAY_SIZE(m_name)); - azstrcpy(m_name, NAME_BUFFER_SIZE, other.m_name); - } - - //////////////////////////////////////////////////////////////////////////////////////////////// - InputChannelId& InputChannelId::operator=(const InputChannelId& other) - { - azstrcpy(m_name, NAME_BUFFER_SIZE, other.m_name); - m_crc32 = other.m_crc32; - return *this; - } - //////////////////////////////////////////////////////////////////////////////////////////////// const char* InputChannelId::GetName() const { - return m_name; + return m_name.c_str(); } //////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/Code/Framework/AzFramework/AzFramework/Input/Channels/InputChannelId.h b/Code/Framework/AzFramework/AzFramework/Input/Channels/InputChannelId.h index 5c1c58d6b1..a4d8ac83eb 100644 --- a/Code/Framework/AzFramework/AzFramework/Input/Channels/InputChannelId.h +++ b/Code/Framework/AzFramework/AzFramework/Input/Channels/InputChannelId.h @@ -11,6 +11,7 @@ #include #include #include +#include //////////////////////////////////////////////////////////////////////////////////////////////////// namespace AzFramework @@ -22,8 +23,7 @@ namespace AzFramework public: //////////////////////////////////////////////////////////////////////////////////////////// // Constants - static const int NAME_BUFFER_SIZE = 64; - static const int MAX_NAME_LENGTH = NAME_BUFFER_SIZE - 1; + static constexpr int MAX_NAME_LENGTH = 64; //////////////////////////////////////////////////////////////////////////////////////////// // Allocator @@ -39,21 +39,28 @@ namespace AzFramework //////////////////////////////////////////////////////////////////////////////////////////// //! Constructor - //! \param[in] name Name of the input channel (will be truncated if exceeds MAX_NAME_LENGTH) - explicit InputChannelId(const char* name = ""); + //! \param[in] name Name of the input channel (will be ignored if exceeds MAX_NAME_LENGTH) + explicit constexpr InputChannelId(AZStd::string_view name = "") + : m_name(name) + , m_crc32(name) + { + } - //////////////////////////////////////////////////////////////////////////////////////////// - //! Copy constructor - //! \param[in] other Another instance of the class to copy from - InputChannelId(const InputChannelId& other); - - //////////////////////////////////////////////////////////////////////////////////////////// - //! Copy assignment operator - //! \param[in] other Another instance of the class to copy from - InputChannelId& operator=(const InputChannelId& other); - - //////////////////////////////////////////////////////////////////////////////////////////// - //! Default destructor + constexpr InputChannelId(const InputChannelId& other) = default; + constexpr InputChannelId(InputChannelId&& other) = default; + constexpr InputChannelId& operator=(const InputChannelId& other) + { + m_name = other.m_name; + m_crc32 = other.m_crc32; + return *this; + } + constexpr InputChannelId& operator=(InputChannelId&& other) + { + m_name = AZStd::move(other.m_name); + m_crc32 = AZStd::move(other.m_crc32); + other.m_crc32 = 0; + return *this; + } ~InputChannelId() = default; //////////////////////////////////////////////////////////////////////////////////////////// @@ -77,7 +84,7 @@ namespace AzFramework private: //////////////////////////////////////////////////////////////////////////////////////////// // Variables - char m_name[NAME_BUFFER_SIZE]; //!< Name of the input channel + AZStd::fixed_string m_name; //!< Name of the input channel AZ::Crc32 m_crc32; //!< Crc32 of the input channel }; } // namespace AzFramework diff --git a/Code/Framework/AzFramework/AzFramework/Input/Devices/Gamepad/InputDeviceGamepad.cpp b/Code/Framework/AzFramework/AzFramework/Input/Devices/Gamepad/InputDeviceGamepad.cpp index 95886c6871..51aa008519 100644 --- a/Code/Framework/AzFramework/AzFramework/Input/Devices/Gamepad/InputDeviceGamepad.cpp +++ b/Code/Framework/AzFramework/AzFramework/Input/Devices/Gamepad/InputDeviceGamepad.cpp @@ -28,91 +28,6 @@ namespace AzFramework return (inputDeviceId.GetNameCrc32() == IdForIndex0.GetNameCrc32()); } - //////////////////////////////////////////////////////////////////////////////////////////////// - const InputChannelId InputDeviceGamepad::Button::A("gamepad_button_a"); - const InputChannelId InputDeviceGamepad::Button::B("gamepad_button_b"); - const InputChannelId InputDeviceGamepad::Button::X("gamepad_button_x"); - const InputChannelId InputDeviceGamepad::Button::Y("gamepad_button_y"); - const InputChannelId InputDeviceGamepad::Button::L1("gamepad_button_l1"); - const InputChannelId InputDeviceGamepad::Button::R1("gamepad_button_r1"); - const InputChannelId InputDeviceGamepad::Button::L3("gamepad_button_l3"); - const InputChannelId InputDeviceGamepad::Button::R3("gamepad_button_r3"); - const InputChannelId InputDeviceGamepad::Button::DU("gamepad_button_d_up"); - const InputChannelId InputDeviceGamepad::Button::DD("gamepad_button_d_down"); - const InputChannelId InputDeviceGamepad::Button::DL("gamepad_button_d_left"); - const InputChannelId InputDeviceGamepad::Button::DR("gamepad_button_d_right"); - const InputChannelId InputDeviceGamepad::Button::Start("gamepad_button_start"); - const InputChannelId InputDeviceGamepad::Button::Select("gamepad_button_select"); - const AZStd::array InputDeviceGamepad::Button::All = - {{ - A, - B, - X, - Y, - L1, - R1, - L3, - R3, - DU, - DD, - DL, - DR, - Start, - Select - }}; - - //////////////////////////////////////////////////////////////////////////////////////////////// - const InputChannelId InputDeviceGamepad::Trigger::L2("gamepad_trigger_l2"); - const InputChannelId InputDeviceGamepad::Trigger::R2("gamepad_trigger_r2"); - const AZStd::array InputDeviceGamepad::Trigger::All = - {{ - L2, - R2 - }}; - - //////////////////////////////////////////////////////////////////////////////////////////////// - const InputChannelId InputDeviceGamepad::ThumbStickAxis2D::L("gamepad_thumbstick_l"); - const InputChannelId InputDeviceGamepad::ThumbStickAxis2D::R("gamepad_thumbstick_r"); - const AZStd::array InputDeviceGamepad::ThumbStickAxis2D::All = - {{ - L, - R - }}; - - //////////////////////////////////////////////////////////////////////////////////////////////// - const InputChannelId InputDeviceGamepad::ThumbStickAxis1D::LX("gamepad_thumbstick_l_x"); - const InputChannelId InputDeviceGamepad::ThumbStickAxis1D::LY("gamepad_thumbstick_l_y"); - const InputChannelId InputDeviceGamepad::ThumbStickAxis1D::RX("gamepad_thumbstick_r_x"); - const InputChannelId InputDeviceGamepad::ThumbStickAxis1D::RY("gamepad_thumbstick_r_y"); - const AZStd::array InputDeviceGamepad::ThumbStickAxis1D::All = - {{ - LX, - LY, - RX, - RY - }}; - - //////////////////////////////////////////////////////////////////////////////////////////////// - const InputChannelId InputDeviceGamepad::ThumbStickDirection::LU("gamepad_thumbstick_l_up"); - const InputChannelId InputDeviceGamepad::ThumbStickDirection::LD("gamepad_thumbstick_l_down"); - const InputChannelId InputDeviceGamepad::ThumbStickDirection::LL("gamepad_thumbstick_l_left"); - const InputChannelId InputDeviceGamepad::ThumbStickDirection::LR("gamepad_thumbstick_l_right"); - const InputChannelId InputDeviceGamepad::ThumbStickDirection::RU("gamepad_thumbstick_r_up"); - const InputChannelId InputDeviceGamepad::ThumbStickDirection::RD("gamepad_thumbstick_r_down"); - const InputChannelId InputDeviceGamepad::ThumbStickDirection::RL("gamepad_thumbstick_r_left"); - const InputChannelId InputDeviceGamepad::ThumbStickDirection::RR("gamepad_thumbstick_r_right"); - const AZStd::array InputDeviceGamepad::ThumbStickDirection::All = - {{ - LU, - LD, - LL, - LR, - RU, - RD, - RL, - RR - }}; - //////////////////////////////////////////////////////////////////////////////////////////////// void InputDeviceGamepad::Reflect(AZ::ReflectContext* context) { diff --git a/Code/Framework/AzFramework/AzFramework/Input/Devices/Gamepad/InputDeviceGamepad.h b/Code/Framework/AzFramework/AzFramework/Input/Devices/Gamepad/InputDeviceGamepad.h index f4fddbb24e..286b4f0df3 100644 --- a/Code/Framework/AzFramework/AzFramework/Input/Devices/Gamepad/InputDeviceGamepad.h +++ b/Code/Framework/AzFramework/AzFramework/Input/Devices/Gamepad/InputDeviceGamepad.h @@ -59,75 +59,115 @@ namespace AzFramework //! All the input channel ids that identify game-pad digital button input struct Button { - static const InputChannelId A; //!< The bottom diamond face button - static const InputChannelId B; //!< The right diamond face button - static const InputChannelId X; //!< The left diamond face button - static const InputChannelId Y; //!< The top diamond face button - static const InputChannelId L1; //!< The top-left shoulder bumper button - static const InputChannelId R1; //!< The top-right shoulder bumper button - static const InputChannelId L3; //!< The left thumb-stick click button - static const InputChannelId R3; //!< The right thumb-stick click button - static const InputChannelId DU; //!< The up directional pad button - static const InputChannelId DD; //!< The down directional pad button - static const InputChannelId DL; //!< The left directional pad button - static const InputChannelId DR; //!< The right directional pad button - static const InputChannelId Start; //!< The start/pause/options button - static const InputChannelId Select; //!< The select/back button + static constexpr inline InputChannelId A{"gamepad_button_a"}; //!< The bottom diamond face button + static constexpr inline InputChannelId B{"gamepad_button_b"}; //!< The right diamond face button + static constexpr inline InputChannelId X{"gamepad_button_x"}; //!< The left diamond face button + static constexpr inline InputChannelId Y{"gamepad_button_y"}; //!< The top diamond face button + static constexpr inline InputChannelId L1{"gamepad_button_l1"}; //!< The top-left shoulder bumper button + static constexpr inline InputChannelId R1{"gamepad_button_r1"}; //!< The top-right shoulder bumper button + static constexpr inline InputChannelId L3{"gamepad_button_l3"}; //!< The left thumb-stick click button + static constexpr inline InputChannelId R3{"gamepad_button_r3"}; //!< The right thumb-stick click button + static constexpr inline InputChannelId DU{"gamepad_button_d_up"}; //!< The up directional pad button + static constexpr inline InputChannelId DD{"gamepad_button_d_down"}; //!< The down directional pad button + static constexpr inline InputChannelId DL{"gamepad_button_d_left"}; //!< The left directional pad button + static constexpr inline InputChannelId DR{"gamepad_button_d_right"}; //!< The right directional pad button + static constexpr inline InputChannelId Start{"gamepad_button_start"}; //!< The start/pause/options button + static constexpr inline InputChannelId Select{"gamepad_button_select"}; //!< The select/back button //!< All digital game-pad button ids - static const AZStd::array All; + static constexpr inline AZStd::array All + { + A, + B, + X, + Y, + L1, + R1, + L3, + R3, + DU, + DD, + DL, + DR, + Start, + Select + }; }; //////////////////////////////////////////////////////////////////////////////////////////// //! All the input channel ids that identify game-pad analog trigger input struct Trigger { - static const InputChannelId L2; //!< The bottom-left shoulder trigger - static const InputChannelId R2; //!< The bottom-right shoulder trigger + static constexpr inline InputChannelId L2{"gamepad_trigger_l2"}; //!< The bottom-left shoulder trigger + static constexpr inline InputChannelId R2{"gamepad_trigger_r2"}; //!< The bottom-right shoulder trigger //!< All analog game-pad trigger ids - static const AZStd::array All; + static constexpr inline AZStd::array All + { + L2, + R2 + }; }; //////////////////////////////////////////////////////////////////////////////////////////// //! All the input channel ids that identify game-pad thumb-stick 2D axis input struct ThumbStickAxis2D { - static const InputChannelId L; //!< The left-hand thumb-stick - static const InputChannelId R; //!< The right-hand thumb-stick + static constexpr inline InputChannelId L{"gamepad_thumbstick_l"}; //!< The left-hand thumb-stick + static constexpr inline InputChannelId R{"gamepad_thumbstick_r"}; //!< The right-hand thumb-stick //!< All game-pad thumb-stick 2D axis input channel ids - static const AZStd::array All; + static constexpr inline AZStd::array All + { + L, + R + }; }; //////////////////////////////////////////////////////////////////////////////////////////// //! All the input channel ids that identify game-pad thumb-stick 1D axis input struct ThumbStickAxis1D { - static const InputChannelId LX; //!< X-axis of the left-hand thumb-stick - static const InputChannelId LY; //!< Y-axis of the left-hand thumb-stick - static const InputChannelId RX; //!< X-axis of the right-hand thumb-stick - static const InputChannelId RY; //!< Y-axis of the right-hand thumb-stick + static constexpr inline InputChannelId LX{"gamepad_thumbstick_l_x"}; //!< X-axis of the left-hand thumb-stick + static constexpr inline InputChannelId LY{"gamepad_thumbstick_l_y"}; //!< Y-axis of the left-hand thumb-stick + static constexpr inline InputChannelId RX{"gamepad_thumbstick_r_x"}; //!< X-axis of the right-hand thumb-stick + static constexpr inline InputChannelId RY{"gamepad_thumbstick_r_y"}; //!< Y-axis of the right-hand thumb-stick //!< All game-pad thumb-stick 1D axis input channel ids - static const AZStd::array All; + static constexpr inline AZStd::array All + { + LX, + LY, + RX, + RY + }; }; //////////////////////////////////////////////////////////////////////////////////////////// //! All the input channel ids that identify game-pad thumb-stick directional input struct ThumbStickDirection { - static const InputChannelId LU; //!< Up on the left-hand thumb-stick - static const InputChannelId LD; //!< Down on the left-hand thumb-stick - static const InputChannelId LL; //!< Left on the left-hand thumb-stick - static const InputChannelId LR; //!< Right on the left-hand thumb-stick - static const InputChannelId RU; //!< Up on the left-hand thumb-stick - static const InputChannelId RD; //!< Down on the left-hand thumb-stick - static const InputChannelId RL; //!< Left on the left-hand thumb-stick - static const InputChannelId RR; //!< Right on the left-hand thumb-stick + static constexpr inline InputChannelId LU{"gamepad_thumbstick_l_up"}; //!< Up on the left-hand thumb-stick + static constexpr inline InputChannelId LD{"gamepad_thumbstick_l_down"}; //!< Down on the left-hand thumb-stick + static constexpr inline InputChannelId LL{"gamepad_thumbstick_l_left"}; //!< Left on the left-hand thumb-stick + static constexpr inline InputChannelId LR{"gamepad_thumbstick_l_right"}; //!< Right on the left-hand thumb-stick + static constexpr inline InputChannelId RU{"gamepad_thumbstick_r_up"}; //!< Up on the left-hand thumb-stick + static constexpr inline InputChannelId RD{"gamepad_thumbstick_r_down"}; //!< Down on the left-hand thumb-stick + static constexpr inline InputChannelId RL{"gamepad_thumbstick_r_left"}; //!< Left on the left-hand thumb-stick + static constexpr inline InputChannelId RR{"gamepad_thumbstick_r_right"}; //!< Right on the left-hand thumb-stick //!< All game-pad thumb-stick directional input channel ids - static const AZStd::array All; + static constexpr inline AZStd::array All + { + LU, + LD, + LL, + LR, + RU, + RD, + RL, + RR + }; }; //////////////////////////////////////////////////////////////////////////////////////////// diff --git a/Code/Framework/AzFramework/AzFramework/Input/Devices/Keyboard/InputDeviceKeyboard.cpp b/Code/Framework/AzFramework/AzFramework/Input/Devices/Keyboard/InputDeviceKeyboard.cpp index dd84fd9633..d1117ea895 100644 --- a/Code/Framework/AzFramework/AzFramework/Input/Devices/Keyboard/InputDeviceKeyboard.cpp +++ b/Code/Framework/AzFramework/AzFramework/Input/Devices/Keyboard/InputDeviceKeyboard.cpp @@ -24,279 +24,6 @@ namespace AzFramework return (inputDeviceId.GetNameCrc32() == Id.GetNameCrc32()); } - //////////////////////////////////////////////////////////////////////////////////////////////// - // Alphanumeric Keys - const InputChannelId InputDeviceKeyboard::Key::Alphanumeric0("keyboard_key_alphanumeric_0"); - const InputChannelId InputDeviceKeyboard::Key::Alphanumeric1("keyboard_key_alphanumeric_1"); - const InputChannelId InputDeviceKeyboard::Key::Alphanumeric2("keyboard_key_alphanumeric_2"); - const InputChannelId InputDeviceKeyboard::Key::Alphanumeric3("keyboard_key_alphanumeric_3"); - const InputChannelId InputDeviceKeyboard::Key::Alphanumeric4("keyboard_key_alphanumeric_4"); - const InputChannelId InputDeviceKeyboard::Key::Alphanumeric5("keyboard_key_alphanumeric_5"); - const InputChannelId InputDeviceKeyboard::Key::Alphanumeric6("keyboard_key_alphanumeric_6"); - const InputChannelId InputDeviceKeyboard::Key::Alphanumeric7("keyboard_key_alphanumeric_7"); - const InputChannelId InputDeviceKeyboard::Key::Alphanumeric8("keyboard_key_alphanumeric_8"); - const InputChannelId InputDeviceKeyboard::Key::Alphanumeric9("keyboard_key_alphanumeric_9"); - const InputChannelId InputDeviceKeyboard::Key::AlphanumericA("keyboard_key_alphanumeric_A"); - const InputChannelId InputDeviceKeyboard::Key::AlphanumericB("keyboard_key_alphanumeric_B"); - const InputChannelId InputDeviceKeyboard::Key::AlphanumericC("keyboard_key_alphanumeric_C"); - const InputChannelId InputDeviceKeyboard::Key::AlphanumericD("keyboard_key_alphanumeric_D"); - const InputChannelId InputDeviceKeyboard::Key::AlphanumericE("keyboard_key_alphanumeric_E"); - const InputChannelId InputDeviceKeyboard::Key::AlphanumericF("keyboard_key_alphanumeric_F"); - const InputChannelId InputDeviceKeyboard::Key::AlphanumericG("keyboard_key_alphanumeric_G"); - const InputChannelId InputDeviceKeyboard::Key::AlphanumericH("keyboard_key_alphanumeric_H"); - const InputChannelId InputDeviceKeyboard::Key::AlphanumericI("keyboard_key_alphanumeric_I"); - const InputChannelId InputDeviceKeyboard::Key::AlphanumericJ("keyboard_key_alphanumeric_J"); - const InputChannelId InputDeviceKeyboard::Key::AlphanumericK("keyboard_key_alphanumeric_K"); - const InputChannelId InputDeviceKeyboard::Key::AlphanumericL("keyboard_key_alphanumeric_L"); - const InputChannelId InputDeviceKeyboard::Key::AlphanumericM("keyboard_key_alphanumeric_M"); - const InputChannelId InputDeviceKeyboard::Key::AlphanumericN("keyboard_key_alphanumeric_N"); - const InputChannelId InputDeviceKeyboard::Key::AlphanumericO("keyboard_key_alphanumeric_O"); - const InputChannelId InputDeviceKeyboard::Key::AlphanumericP("keyboard_key_alphanumeric_P"); - const InputChannelId InputDeviceKeyboard::Key::AlphanumericQ("keyboard_key_alphanumeric_Q"); - const InputChannelId InputDeviceKeyboard::Key::AlphanumericR("keyboard_key_alphanumeric_R"); - const InputChannelId InputDeviceKeyboard::Key::AlphanumericS("keyboard_key_alphanumeric_S"); - const InputChannelId InputDeviceKeyboard::Key::AlphanumericT("keyboard_key_alphanumeric_T"); - const InputChannelId InputDeviceKeyboard::Key::AlphanumericU("keyboard_key_alphanumeric_U"); - const InputChannelId InputDeviceKeyboard::Key::AlphanumericV("keyboard_key_alphanumeric_V"); - const InputChannelId InputDeviceKeyboard::Key::AlphanumericW("keyboard_key_alphanumeric_W"); - const InputChannelId InputDeviceKeyboard::Key::AlphanumericX("keyboard_key_alphanumeric_X"); - const InputChannelId InputDeviceKeyboard::Key::AlphanumericY("keyboard_key_alphanumeric_Y"); - const InputChannelId InputDeviceKeyboard::Key::AlphanumericZ("keyboard_key_alphanumeric_Z"); - - //////////////////////////////////////////////////////////////////////////////////////////////// - // Edit (and escape) Keys - const InputChannelId InputDeviceKeyboard::Key::EditBackspace("keyboard_key_edit_backspace"); - const InputChannelId InputDeviceKeyboard::Key::EditCapsLock("keyboard_key_edit_capslock"); - const InputChannelId InputDeviceKeyboard::Key::EditEnter("keyboard_key_edit_enter"); - const InputChannelId InputDeviceKeyboard::Key::EditSpace("keyboard_key_edit_space"); - const InputChannelId InputDeviceKeyboard::Key::EditTab("keyboard_key_edit_tab"); - const InputChannelId InputDeviceKeyboard::Key::Escape("keyboard_key_escape"); - - //////////////////////////////////////////////////////////////////////////////////////////////// - // Function Keys - const InputChannelId InputDeviceKeyboard::Key::Function01("keyboard_key_function_F01"); - const InputChannelId InputDeviceKeyboard::Key::Function02("keyboard_key_function_F02"); - const InputChannelId InputDeviceKeyboard::Key::Function03("keyboard_key_function_F03"); - const InputChannelId InputDeviceKeyboard::Key::Function04("keyboard_key_function_F04"); - const InputChannelId InputDeviceKeyboard::Key::Function05("keyboard_key_function_F05"); - const InputChannelId InputDeviceKeyboard::Key::Function06("keyboard_key_function_F06"); - const InputChannelId InputDeviceKeyboard::Key::Function07("keyboard_key_function_F07"); - const InputChannelId InputDeviceKeyboard::Key::Function08("keyboard_key_function_F08"); - const InputChannelId InputDeviceKeyboard::Key::Function09("keyboard_key_function_F09"); - const InputChannelId InputDeviceKeyboard::Key::Function10("keyboard_key_function_F10"); - const InputChannelId InputDeviceKeyboard::Key::Function11("keyboard_key_function_F11"); - const InputChannelId InputDeviceKeyboard::Key::Function12("keyboard_key_function_F12"); - const InputChannelId InputDeviceKeyboard::Key::Function13("keyboard_key_function_F13"); - const InputChannelId InputDeviceKeyboard::Key::Function14("keyboard_key_function_F14"); - const InputChannelId InputDeviceKeyboard::Key::Function15("keyboard_key_function_F15"); - const InputChannelId InputDeviceKeyboard::Key::Function16("keyboard_key_function_F16"); - const InputChannelId InputDeviceKeyboard::Key::Function17("keyboard_key_function_F17"); - const InputChannelId InputDeviceKeyboard::Key::Function18("keyboard_key_function_F18"); - const InputChannelId InputDeviceKeyboard::Key::Function19("keyboard_key_function_F19"); - const InputChannelId InputDeviceKeyboard::Key::Function20("keyboard_key_function_F20"); - - //////////////////////////////////////////////////////////////////////////////////////////////// - // Modifier Keys - const InputChannelId InputDeviceKeyboard::Key::ModifierAltL("keyboard_key_modifier_alt_l"); - const InputChannelId InputDeviceKeyboard::Key::ModifierAltR("keyboard_key_modifier_alt_r"); - const InputChannelId InputDeviceKeyboard::Key::ModifierCtrlL("keyboard_key_modifier_ctrl_l"); - const InputChannelId InputDeviceKeyboard::Key::ModifierCtrlR("keyboard_key_modifier_ctrl_r"); - const InputChannelId InputDeviceKeyboard::Key::ModifierShiftL("keyboard_key_modifier_shift_l"); - const InputChannelId InputDeviceKeyboard::Key::ModifierShiftR("keyboard_key_modifier_shift_r"); - const InputChannelId InputDeviceKeyboard::Key::ModifierSuperL("keyboard_key_modifier_super_l"); - const InputChannelId InputDeviceKeyboard::Key::ModifierSuperR("keyboard_key_modifier_super_r"); - - //////////////////////////////////////////////////////////////////////////////////////////////// - // Navigation Keys - const InputChannelId InputDeviceKeyboard::Key::NavigationArrowDown("keyboard_key_navigation_arrow_down"); - const InputChannelId InputDeviceKeyboard::Key::NavigationArrowLeft("keyboard_key_navigation_arrow_left"); - const InputChannelId InputDeviceKeyboard::Key::NavigationArrowRight("keyboard_key_navigation_arrow_right"); - const InputChannelId InputDeviceKeyboard::Key::NavigationArrowUp("keyboard_key_navigation_arrow_up"); - const InputChannelId InputDeviceKeyboard::Key::NavigationDelete("keyboard_key_navigation_delete"); - const InputChannelId InputDeviceKeyboard::Key::NavigationEnd("keyboard_key_navigation_end"); - const InputChannelId InputDeviceKeyboard::Key::NavigationHome("keyboard_key_navigation_home"); - const InputChannelId InputDeviceKeyboard::Key::NavigationInsert("keyboard_key_navigation_insert"); - const InputChannelId InputDeviceKeyboard::Key::NavigationPageDown("keyboard_key_navigation_page_down"); - const InputChannelId InputDeviceKeyboard::Key::NavigationPageUp("keyboard_key_navigation_page_up"); - - //////////////////////////////////////////////////////////////////////////////////////////////// - // Numpad Keys - const InputChannelId InputDeviceKeyboard::Key::NumLock("keyboard_key_num_lock"); - const InputChannelId InputDeviceKeyboard::Key::NumPad0("keyboard_key_numpad_0"); - const InputChannelId InputDeviceKeyboard::Key::NumPad1("keyboard_key_numpad_1"); - const InputChannelId InputDeviceKeyboard::Key::NumPad2("keyboard_key_numpad_2"); - const InputChannelId InputDeviceKeyboard::Key::NumPad3("keyboard_key_numpad_3"); - const InputChannelId InputDeviceKeyboard::Key::NumPad4("keyboard_key_numpad_4"); - const InputChannelId InputDeviceKeyboard::Key::NumPad5("keyboard_key_numpad_5"); - const InputChannelId InputDeviceKeyboard::Key::NumPad6("keyboard_key_numpad_6"); - const InputChannelId InputDeviceKeyboard::Key::NumPad7("keyboard_key_numpad_7"); - const InputChannelId InputDeviceKeyboard::Key::NumPad8("keyboard_key_numpad_8"); - const InputChannelId InputDeviceKeyboard::Key::NumPad9("keyboard_key_numpad_9"); - const InputChannelId InputDeviceKeyboard::Key::NumPadAdd("keyboard_key_numpad_add"); - const InputChannelId InputDeviceKeyboard::Key::NumPadDecimal("keyboard_key_numpad_decimal"); - const InputChannelId InputDeviceKeyboard::Key::NumPadDivide("keyboard_key_numpad_divide"); - const InputChannelId InputDeviceKeyboard::Key::NumPadEnter("keyboard_key_numpad_enter"); - const InputChannelId InputDeviceKeyboard::Key::NumPadMultiply("keyboard_key_numpad_multiply"); - const InputChannelId InputDeviceKeyboard::Key::NumPadSubtract("keyboard_key_numpad_subtract"); - - //////////////////////////////////////////////////////////////////////////////////////////////// - // Punctuation Keys - const InputChannelId InputDeviceKeyboard::Key::PunctuationApostrophe("keyboard_key_punctuation_apostrophe"); - const InputChannelId InputDeviceKeyboard::Key::PunctuationBackslash("keyboard_key_punctuation_backslash"); - const InputChannelId InputDeviceKeyboard::Key::PunctuationBracketL("keyboard_key_punctuation_bracket_l"); - const InputChannelId InputDeviceKeyboard::Key::PunctuationBracketR("keyboard_key_punctuation_bracket_r"); - const InputChannelId InputDeviceKeyboard::Key::PunctuationComma("keyboard_key_punctuation_comma"); - const InputChannelId InputDeviceKeyboard::Key::PunctuationEquals("keyboard_key_punctuation_equals"); - const InputChannelId InputDeviceKeyboard::Key::PunctuationHyphen("keyboard_key_punctuation_hyphen"); - const InputChannelId InputDeviceKeyboard::Key::PunctuationPeriod("keyboard_key_punctuation_period"); - const InputChannelId InputDeviceKeyboard::Key::PunctuationSemicolon("keyboard_key_punctuation_semicolon"); - const InputChannelId InputDeviceKeyboard::Key::PunctuationSlash("keyboard_key_punctuation_slash"); - const InputChannelId InputDeviceKeyboard::Key::PunctuationTilde("keyboard_key_punctuation_tilde"); - - //////////////////////////////////////////////////////////////////////////////////////////////// - // Supplementary ISO Key - const InputChannelId InputDeviceKeyboard::Key::SupplementaryISO("keyboard_key_supplementary_iso"); - - //////////////////////////////////////////////////////////////////////////////////////////////// - // Windows System Keys - const InputChannelId InputDeviceKeyboard::Key::WindowsSystemPause("keyboard_key_windows_system_pause"); - const InputChannelId InputDeviceKeyboard::Key::WindowsSystemPrint("keyboard_key_windows_system_print"); - const InputChannelId InputDeviceKeyboard::Key::WindowsSystemScrollLock("keyboard_key_windows_system_scroll_lock"); - - //////////////////////////////////////////////////////////////////////////////////////////////// - const AZStd::array InputDeviceKeyboard::Key::All = - {{ - // Alphanumeric Keys - Alphanumeric0, - Alphanumeric1, - Alphanumeric2, - Alphanumeric3, - Alphanumeric4, - Alphanumeric5, - Alphanumeric6, - Alphanumeric7, - Alphanumeric8, - Alphanumeric9, - AlphanumericA, - AlphanumericB, - AlphanumericC, - AlphanumericD, - AlphanumericE, - AlphanumericF, - AlphanumericG, - AlphanumericH, - AlphanumericI, - AlphanumericJ, - AlphanumericK, - AlphanumericL, - AlphanumericM, - AlphanumericN, - AlphanumericO, - AlphanumericP, - AlphanumericQ, - AlphanumericR, - AlphanumericS, - AlphanumericT, - AlphanumericU, - AlphanumericV, - AlphanumericW, - AlphanumericX, - AlphanumericY, - AlphanumericZ, - - // Edit (and escape) Keys - EditBackspace, - EditCapsLock, - EditEnter, - EditSpace, - EditTab, - Escape, - - // Function Keys - Function01, - Function02, - Function03, - Function04, - Function05, - Function06, - Function07, - Function08, - Function09, - Function10, - Function11, - Function12, - Function13, - Function14, - Function15, - Function16, - Function17, - Function18, - Function19, - Function20, - - // Modifier Keys - ModifierAltL, - ModifierAltR, - ModifierCtrlL, - ModifierCtrlR, - ModifierShiftL, - ModifierShiftR, - ModifierSuperL, - ModifierSuperR, - - // Navigation Keys - NavigationArrowDown, - NavigationArrowLeft, - NavigationArrowRight, - NavigationArrowUp, - NavigationDelete, - NavigationEnd, - NavigationHome, - NavigationInsert, - NavigationPageDown, - NavigationPageUp, - - // Numpad Keys - NumLock, - NumPad0, - NumPad1, - NumPad2, - NumPad3, - NumPad4, - NumPad5, - NumPad6, - NumPad7, - NumPad8, - NumPad9, - NumPadAdd, - NumPadDecimal, - NumPadDivide, - NumPadEnter, - NumPadMultiply, - NumPadSubtract, - - // Punctuation Keys - PunctuationApostrophe, - PunctuationBackslash, - PunctuationBracketL, - PunctuationBracketR, - PunctuationComma, - PunctuationEquals, - PunctuationHyphen, - PunctuationPeriod, - PunctuationSemicolon, - PunctuationSlash, - PunctuationTilde, - - // Supplementary ISO Key - SupplementaryISO, - - // Windows System Keys - WindowsSystemPause, - WindowsSystemPrint, - WindowsSystemScrollLock - }}; - //////////////////////////////////////////////////////////////////////////////////////////////// ModifierKeyMask GetCorrespondingModifierKeyMask(const InputChannelId& channelId) { diff --git a/Code/Framework/AzFramework/AzFramework/Input/Devices/Keyboard/InputDeviceKeyboard.h b/Code/Framework/AzFramework/AzFramework/Input/Devices/Keyboard/InputDeviceKeyboard.h index 5588f94d69..e3c21ec326 100644 --- a/Code/Framework/AzFramework/AzFramework/Input/Devices/Keyboard/InputDeviceKeyboard.h +++ b/Code/Framework/AzFramework/AzFramework/Input/Devices/Keyboard/InputDeviceKeyboard.h @@ -94,137 +94,268 @@ namespace AzFramework struct Key { // Alphanumeric Keys - static const InputChannelId Alphanumeric0; //!< The 0 key - static const InputChannelId Alphanumeric1; //!< The 1 key - static const InputChannelId Alphanumeric2; //!< The 2 key - static const InputChannelId Alphanumeric3; //!< The 3 key - static const InputChannelId Alphanumeric4; //!< The 4 key - static const InputChannelId Alphanumeric5; //!< The 5 key - static const InputChannelId Alphanumeric6; //!< The 6 key - static const InputChannelId Alphanumeric7; //!< The 7 key - static const InputChannelId Alphanumeric8; //!< The 8 key - static const InputChannelId Alphanumeric9; //!< The 9 key - static const InputChannelId AlphanumericA; //!< The A key - static const InputChannelId AlphanumericB; //!< The B key - static const InputChannelId AlphanumericC; //!< The C key - static const InputChannelId AlphanumericD; //!< The D key - static const InputChannelId AlphanumericE; //!< The E key - static const InputChannelId AlphanumericF; //!< The F key - static const InputChannelId AlphanumericG; //!< The G key - static const InputChannelId AlphanumericH; //!< The H key - static const InputChannelId AlphanumericI; //!< The I key - static const InputChannelId AlphanumericJ; //!< The J key - static const InputChannelId AlphanumericK; //!< The K key - static const InputChannelId AlphanumericL; //!< The L key - static const InputChannelId AlphanumericM; //!< The M key - static const InputChannelId AlphanumericN; //!< The N key - static const InputChannelId AlphanumericO; //!< The O key - static const InputChannelId AlphanumericP; //!< The P key - static const InputChannelId AlphanumericQ; //!< The Q key - static const InputChannelId AlphanumericR; //!< The R key - static const InputChannelId AlphanumericS; //!< The S key - static const InputChannelId AlphanumericT; //!< The T key - static const InputChannelId AlphanumericU; //!< The U key - static const InputChannelId AlphanumericV; //!< The V key - static const InputChannelId AlphanumericW; //!< The W key - static const InputChannelId AlphanumericX; //!< The X key - static const InputChannelId AlphanumericY; //!< The Y key - static const InputChannelId AlphanumericZ; //!< The Z key + static constexpr inline InputChannelId Alphanumeric0{"keyboard_key_alphanumeric_0"}; //!< The 0 key + static constexpr inline InputChannelId Alphanumeric1{"keyboard_key_alphanumeric_1"}; //!< The 1 key + static constexpr inline InputChannelId Alphanumeric2{"keyboard_key_alphanumeric_2"}; //!< The 2 key + static constexpr inline InputChannelId Alphanumeric3{"keyboard_key_alphanumeric_3"}; //!< The 3 key + static constexpr inline InputChannelId Alphanumeric4{"keyboard_key_alphanumeric_4"}; //!< The 4 key + static constexpr inline InputChannelId Alphanumeric5{"keyboard_key_alphanumeric_5"}; //!< The 5 key + static constexpr inline InputChannelId Alphanumeric6{"keyboard_key_alphanumeric_6"}; //!< The 6 key + static constexpr inline InputChannelId Alphanumeric7{"keyboard_key_alphanumeric_7"}; //!< The 7 key + static constexpr inline InputChannelId Alphanumeric8{"keyboard_key_alphanumeric_8"}; //!< The 8 key + static constexpr inline InputChannelId Alphanumeric9{"keyboard_key_alphanumeric_9"}; //!< The 9 key + static constexpr inline InputChannelId AlphanumericA{"keyboard_key_alphanumeric_A"}; //!< The A key + static constexpr inline InputChannelId AlphanumericB{"keyboard_key_alphanumeric_B"}; //!< The B key + static constexpr inline InputChannelId AlphanumericC{"keyboard_key_alphanumeric_C"}; //!< The C key + static constexpr inline InputChannelId AlphanumericD{"keyboard_key_alphanumeric_D"}; //!< The D key + static constexpr inline InputChannelId AlphanumericE{"keyboard_key_alphanumeric_E"}; //!< The E key + static constexpr inline InputChannelId AlphanumericF{"keyboard_key_alphanumeric_F"}; //!< The F key + static constexpr inline InputChannelId AlphanumericG{"keyboard_key_alphanumeric_G"}; //!< The G key + static constexpr inline InputChannelId AlphanumericH{"keyboard_key_alphanumeric_H"}; //!< The H key + static constexpr inline InputChannelId AlphanumericI{"keyboard_key_alphanumeric_I"}; //!< The I key + static constexpr inline InputChannelId AlphanumericJ{"keyboard_key_alphanumeric_J"}; //!< The J key + static constexpr inline InputChannelId AlphanumericK{"keyboard_key_alphanumeric_K"}; //!< The K key + static constexpr inline InputChannelId AlphanumericL{"keyboard_key_alphanumeric_L"}; //!< The L key + static constexpr inline InputChannelId AlphanumericM{"keyboard_key_alphanumeric_M"}; //!< The M key + static constexpr inline InputChannelId AlphanumericN{"keyboard_key_alphanumeric_N"}; //!< The N key + static constexpr inline InputChannelId AlphanumericO{"keyboard_key_alphanumeric_O"}; //!< The O key + static constexpr inline InputChannelId AlphanumericP{"keyboard_key_alphanumeric_P"}; //!< The P key + static constexpr inline InputChannelId AlphanumericQ{"keyboard_key_alphanumeric_Q"}; //!< The Q key + static constexpr inline InputChannelId AlphanumericR{"keyboard_key_alphanumeric_R"}; //!< The R key + static constexpr inline InputChannelId AlphanumericS{"keyboard_key_alphanumeric_S"}; //!< The S key + static constexpr inline InputChannelId AlphanumericT{"keyboard_key_alphanumeric_T"}; //!< The T key + static constexpr inline InputChannelId AlphanumericU{"keyboard_key_alphanumeric_U"}; //!< The U key + static constexpr inline InputChannelId AlphanumericV{"keyboard_key_alphanumeric_V"}; //!< The V key + static constexpr inline InputChannelId AlphanumericW{"keyboard_key_alphanumeric_W"}; //!< The W key + static constexpr inline InputChannelId AlphanumericX{"keyboard_key_alphanumeric_X"}; //!< The X key + static constexpr inline InputChannelId AlphanumericY{"keyboard_key_alphanumeric_Y"}; //!< The Y key + static constexpr inline InputChannelId AlphanumericZ{"keyboard_key_alphanumeric_Z"}; //!< The Z key - // Edit (and escape) Keys - static const InputChannelId EditBackspace; //!< The backspace key - static const InputChannelId EditCapsLock; //!< The caps lock key - static const InputChannelId EditEnter; //!< The enter/return key - static const InputChannelId EditSpace; //!< The spacebar key - static const InputChannelId EditTab; //!< The tab key - static const InputChannelId Escape; //!< The escape key + // Edit {and escape} Keys + static constexpr inline InputChannelId EditBackspace{"keyboard_key_edit_backspace"}; //!< The backspace key + static constexpr inline InputChannelId EditCapsLock{"keyboard_key_edit_capslock"}; //!< The caps lock key + static constexpr inline InputChannelId EditEnter{"keyboard_key_edit_enter"}; //!< The enter/return key + static constexpr inline InputChannelId EditSpace{"keyboard_key_edit_space"}; //!< The spacebar key + static constexpr inline InputChannelId EditTab{"keyboard_key_edit_tab"}; //!< The tab key + static constexpr inline InputChannelId Escape{"keyboard_key_escape"}; //!< The escape key // Function Keys - static const InputChannelId Function01; //!< The F1 key - static const InputChannelId Function02; //!< The F2 key - static const InputChannelId Function03; //!< The F3 key - static const InputChannelId Function04; //!< The F4 key - static const InputChannelId Function05; //!< The F5 key - static const InputChannelId Function06; //!< The F6 key - static const InputChannelId Function07; //!< The F7 key - static const InputChannelId Function08; //!< The F8 key - static const InputChannelId Function09; //!< The F9 key - static const InputChannelId Function10; //!< The F10 key - static const InputChannelId Function11; //!< The F11 key - static const InputChannelId Function12; //!< The F12 key - static const InputChannelId Function13; //!< The F13 key - static const InputChannelId Function14; //!< The F14 key - static const InputChannelId Function15; //!< The F15 key - static const InputChannelId Function16; //!< The F16 key - static const InputChannelId Function17; //!< The F17 key - static const InputChannelId Function18; //!< The F18 key - static const InputChannelId Function19; //!< The F19 key - static const InputChannelId Function20; //!< The F20 key + static constexpr inline InputChannelId Function01{"keyboard_key_function_F01"}; //!< The F1 key + static constexpr inline InputChannelId Function02{"keyboard_key_function_F02"}; //!< The F2 key + static constexpr inline InputChannelId Function03{"keyboard_key_function_F03"}; //!< The F3 key + static constexpr inline InputChannelId Function04{"keyboard_key_function_F04"}; //!< The F4 key + static constexpr inline InputChannelId Function05{"keyboard_key_function_F05"}; //!< The F5 key + static constexpr inline InputChannelId Function06{"keyboard_key_function_F06"}; //!< The F6 key + static constexpr inline InputChannelId Function07{"keyboard_key_function_F07"}; //!< The F7 key + static constexpr inline InputChannelId Function08{"keyboard_key_function_F08"}; //!< The F8 key + static constexpr inline InputChannelId Function09{"keyboard_key_function_F09"}; //!< The F9 key + static constexpr inline InputChannelId Function10{"keyboard_key_function_F10"}; //!< The F10 key + static constexpr inline InputChannelId Function11{"keyboard_key_function_F11"}; //!< The F11 key + static constexpr inline InputChannelId Function12{"keyboard_key_function_F12"}; //!< The F12 key + static constexpr inline InputChannelId Function13{"keyboard_key_function_F13"}; //!< The F13 key + static constexpr inline InputChannelId Function14{"keyboard_key_function_F14"}; //!< The F14 key + static constexpr inline InputChannelId Function15{"keyboard_key_function_F15"}; //!< The F15 key + static constexpr inline InputChannelId Function16{"keyboard_key_function_F16"}; //!< The F16 key + static constexpr inline InputChannelId Function17{"keyboard_key_function_F17"}; //!< The F17 key + static constexpr inline InputChannelId Function18{"keyboard_key_function_F18"}; //!< The F18 key + static constexpr inline InputChannelId Function19{"keyboard_key_function_F19"}; //!< The F19 key + static constexpr inline InputChannelId Function20{"keyboard_key_function_F20"}; //!< The F20 key // Modifier Keys - static const InputChannelId ModifierAltL; //!< The left alt/option key - static const InputChannelId ModifierAltR; //!< The right alt/option key - static const InputChannelId ModifierCtrlL; //!< The left control key - static const InputChannelId ModifierCtrlR; //!< The right control key - static const InputChannelId ModifierShiftL; //!< The left shift key - static const InputChannelId ModifierShiftR; //!< The right shift key - static const InputChannelId ModifierSuperL; //!< The left super (windows or apple) key - static const InputChannelId ModifierSuperR; //!< The right super (windows or apple) key + static constexpr inline InputChannelId ModifierAltL{"keyboard_key_modifier_alt_l"}; //!< The left alt/option key + static constexpr inline InputChannelId ModifierAltR{"keyboard_key_modifier_alt_r"}; //!< The right alt/option key + static constexpr inline InputChannelId ModifierCtrlL{"keyboard_key_modifier_ctrl_l"}; //!< The left control key + static constexpr inline InputChannelId ModifierCtrlR{"keyboard_key_modifier_ctrl_r"}; //!< The right control key + static constexpr inline InputChannelId ModifierShiftL{"keyboard_key_modifier_shift_l"}; //!< The left shift key + static constexpr inline InputChannelId ModifierShiftR{"keyboard_key_modifier_shift_r"}; //!< The right shift key + static constexpr inline InputChannelId ModifierSuperL{"keyboard_key_modifier_super_l"}; //!< The left super {windows or apple} key + static constexpr inline InputChannelId ModifierSuperR{"keyboard_key_modifier_super_r"}; //!< The right super {windows or apple} key // Navigation Keys - static const InputChannelId NavigationArrowDown; //!< The down arrow key - static const InputChannelId NavigationArrowLeft; //!< The left arrow key - static const InputChannelId NavigationArrowRight; //!< The right arrow key - static const InputChannelId NavigationArrowUp; //!< The up arrow key - static const InputChannelId NavigationDelete; //!< The delete key - static const InputChannelId NavigationEnd; //!< The end key - static const InputChannelId NavigationHome; //!< The home key - static const InputChannelId NavigationInsert; //!< The insert key - static const InputChannelId NavigationPageDown; //!< The page down key - static const InputChannelId NavigationPageUp; //!< The page up key + static constexpr inline InputChannelId NavigationArrowDown{"keyboard_key_navigation_arrow_down"}; //!< The down arrow key + static constexpr inline InputChannelId NavigationArrowLeft{"keyboard_key_navigation_arrow_left"}; //!< The left arrow key + static constexpr inline InputChannelId NavigationArrowRight{"keyboard_key_navigation_arrow_right"}; //!< The right arrow key + static constexpr inline InputChannelId NavigationArrowUp{"keyboard_key_navigation_arrow_up"}; //!< The up arrow key + static constexpr inline InputChannelId NavigationDelete{"keyboard_key_navigation_delete"}; //!< The delete key + static constexpr inline InputChannelId NavigationEnd{"keyboard_key_navigation_end"}; //!< The end key + static constexpr inline InputChannelId NavigationHome{"keyboard_key_navigation_home"}; //!< The home key + static constexpr inline InputChannelId NavigationInsert{"keyboard_key_navigation_insert"}; //!< The insert key + static constexpr inline InputChannelId NavigationPageDown{"keyboard_key_navigation_page_down"}; //!< The page down key + static constexpr inline InputChannelId NavigationPageUp{"keyboard_key_navigation_page_up"}; //!< The page up key // Numpad Keys - static const InputChannelId NumLock; //!< The num lock key (the clear key on apple keyboards) - static const InputChannelId NumPad0; //!< The numpad 0 key - static const InputChannelId NumPad1; //!< The numpad 1 key - static const InputChannelId NumPad2; //!< The numpad 2 key - static const InputChannelId NumPad3; //!< The numpad 3 key - static const InputChannelId NumPad4; //!< The numpad 4 key - static const InputChannelId NumPad5; //!< The numpad 5 key - static const InputChannelId NumPad6; //!< The numpad 6 key - static const InputChannelId NumPad7; //!< The numpad 7 key - static const InputChannelId NumPad8; //!< The numpad 8 key - static const InputChannelId NumPad9; //!< The numpad 9 key - static const InputChannelId NumPadAdd; //!< The numpad add key - static const InputChannelId NumPadDecimal; //!< The numpad decimal key - static const InputChannelId NumPadDivide; //!< The numpad divide key - static const InputChannelId NumPadEnter; //!< The numpad enter key - static const InputChannelId NumPadMultiply; //!< The numpad multiply key - static const InputChannelId NumPadSubtract; //!< The numpad subtract key + static constexpr inline InputChannelId NumLock{"keyboard_key_num_lock"}; //!< The num lock key {the clear key on apple keyboards} + static constexpr inline InputChannelId NumPad0{"keyboard_key_numpad_0"}; //!< The numpad 0 key + static constexpr inline InputChannelId NumPad1{"keyboard_key_numpad_1"}; //!< The numpad 1 key + static constexpr inline InputChannelId NumPad2{"keyboard_key_numpad_2"}; //!< The numpad 2 key + static constexpr inline InputChannelId NumPad3{"keyboard_key_numpad_3"}; //!< The numpad 3 key + static constexpr inline InputChannelId NumPad4{"keyboard_key_numpad_4"}; //!< The numpad 4 key + static constexpr inline InputChannelId NumPad5{"keyboard_key_numpad_5"}; //!< The numpad 5 key + static constexpr inline InputChannelId NumPad6{"keyboard_key_numpad_6"}; //!< The numpad 6 key + static constexpr inline InputChannelId NumPad7{"keyboard_key_numpad_7"}; //!< The numpad 7 key + static constexpr inline InputChannelId NumPad8{"keyboard_key_numpad_8"}; //!< The numpad 8 key + static constexpr inline InputChannelId NumPad9{"keyboard_key_numpad_9"}; //!< The numpad 9 key + static constexpr inline InputChannelId NumPadAdd{"keyboard_key_numpad_add"}; //!< The numpad add key + static constexpr inline InputChannelId NumPadDecimal{"keyboard_key_numpad_decimal"}; //!< The numpad decimal key + static constexpr inline InputChannelId NumPadDivide{"keyboard_key_numpad_divide"}; //!< The numpad divide key + static constexpr inline InputChannelId NumPadEnter{"keyboard_key_numpad_enter"}; //!< The numpad enter key + static constexpr inline InputChannelId NumPadMultiply{"keyboard_key_numpad_multiply"}; //!< The numpad multiply key + static constexpr inline InputChannelId NumPadSubtract{"keyboard_key_numpad_subtract"}; //!< The numpad subtract key // Punctuation Keys - static const InputChannelId PunctuationApostrophe; //!< The apostrophe key - static const InputChannelId PunctuationBackslash; //!< The backslash key - static const InputChannelId PunctuationBracketL; //!< The left bracket key - static const InputChannelId PunctuationBracketR; //!< The right bracket key - static const InputChannelId PunctuationComma; //!< The comma key - static const InputChannelId PunctuationEquals; //!< The equals key - static const InputChannelId PunctuationHyphen; //!< The hyphen/underscore key - static const InputChannelId PunctuationPeriod; //!< The period key - static const InputChannelId PunctuationSemicolon; //!< The semicolon key - static const InputChannelId PunctuationSlash; //!< The (forward) slash key - static const InputChannelId PunctuationTilde; //!< The tilde/grave key + static constexpr inline InputChannelId PunctuationApostrophe{"keyboard_key_punctuation_apostrophe"}; //!< The apostrophe key + static constexpr inline InputChannelId PunctuationBackslash{"keyboard_key_punctuation_backslash"}; //!< The backslash key + static constexpr inline InputChannelId PunctuationBracketL{"keyboard_key_punctuation_bracket_l"}; //!< The left bracket key + static constexpr inline InputChannelId PunctuationBracketR{"keyboard_key_punctuation_bracket_r"}; //!< The right bracket key + static constexpr inline InputChannelId PunctuationComma{"keyboard_key_punctuation_comma"}; //!< The comma key + static constexpr inline InputChannelId PunctuationEquals{"keyboard_key_punctuation_equals"}; //!< The equals key + static constexpr inline InputChannelId PunctuationHyphen{"keyboard_key_punctuation_hyphen"}; //!< The hyphen/underscore key + static constexpr inline InputChannelId PunctuationPeriod{"keyboard_key_punctuation_period"}; //!< The period key + static constexpr inline InputChannelId PunctuationSemicolon{"keyboard_key_punctuation_semicolon"}; //!< The semicolon key + static constexpr inline InputChannelId PunctuationSlash{"keyboard_key_punctuation_slash"}; //!< The {forward} slash key + static constexpr inline InputChannelId PunctuationTilde{"keyboard_key_punctuation_tilde"}; //!< The tilde/grave key // Supplementary ISO Key - static const InputChannelId SupplementaryISO; //!< The supplementary ISO layout key + static constexpr inline InputChannelId SupplementaryISO{"keyboard_key_supplementary_iso"}; //!< The supplementary ISO layout key // Windows System Keys - static const InputChannelId WindowsSystemPause; //!< The windows pause key - static const InputChannelId WindowsSystemPrint; //!< The windows print key - static const InputChannelId WindowsSystemScrollLock; //!< The windows scroll lock key + static constexpr inline InputChannelId WindowsSystemPause{"keyboard_key_windows_system_pause"}; //!< The windows pause key + static constexpr inline InputChannelId WindowsSystemPrint{"keyboard_key_windows_system_print"}; //!< The windows print key + static constexpr inline InputChannelId WindowsSystemScrollLock{"keyboard_key_windows_system_scroll_lock"}; //!< The windows scroll lock key //!< All keyboard key ids - static const AZStd::array All; + static constexpr inline AZStd::array All + { + // Alphanumeric Keys + Alphanumeric0, + Alphanumeric1, + Alphanumeric2, + Alphanumeric3, + Alphanumeric4, + Alphanumeric5, + Alphanumeric6, + Alphanumeric7, + Alphanumeric8, + Alphanumeric9, + AlphanumericA, + AlphanumericB, + AlphanumericC, + AlphanumericD, + AlphanumericE, + AlphanumericF, + AlphanumericG, + AlphanumericH, + AlphanumericI, + AlphanumericJ, + AlphanumericK, + AlphanumericL, + AlphanumericM, + AlphanumericN, + AlphanumericO, + AlphanumericP, + AlphanumericQ, + AlphanumericR, + AlphanumericS, + AlphanumericT, + AlphanumericU, + AlphanumericV, + AlphanumericW, + AlphanumericX, + AlphanumericY, + AlphanumericZ, + + // Edit (and escape) Keys + EditBackspace, + EditCapsLock, + EditEnter, + EditSpace, + EditTab, + Escape, + + // Function Keys + Function01, + Function02, + Function03, + Function04, + Function05, + Function06, + Function07, + Function08, + Function09, + Function10, + Function11, + Function12, + Function13, + Function14, + Function15, + Function16, + Function17, + Function18, + Function19, + Function20, + + // Modifier Keys + ModifierAltL, + ModifierAltR, + ModifierCtrlL, + ModifierCtrlR, + ModifierShiftL, + ModifierShiftR, + ModifierSuperL, + ModifierSuperR, + + // Navigation Keys + NavigationArrowDown, + NavigationArrowLeft, + NavigationArrowRight, + NavigationArrowUp, + NavigationDelete, + NavigationEnd, + NavigationHome, + NavigationInsert, + NavigationPageDown, + NavigationPageUp, + + // Numpad Keys + NumLock, + NumPad0, + NumPad1, + NumPad2, + NumPad3, + NumPad4, + NumPad5, + NumPad6, + NumPad7, + NumPad8, + NumPad9, + NumPadAdd, + NumPadDecimal, + NumPadDivide, + NumPadEnter, + NumPadMultiply, + NumPadSubtract, + + // Punctuation Keys + PunctuationApostrophe, + PunctuationBackslash, + PunctuationBracketL, + PunctuationBracketR, + PunctuationComma, + PunctuationEquals, + PunctuationHyphen, + PunctuationPeriod, + PunctuationSemicolon, + PunctuationSlash, + PunctuationTilde, + + // Supplementary ISO Key + SupplementaryISO, + + // Windows System Keys + WindowsSystemPause, + WindowsSystemPrint, + WindowsSystemScrollLock + }; }; //////////////////////////////////////////////////////////////////////////////////////////// diff --git a/Code/Framework/AzFramework/AzFramework/Input/Devices/Motion/InputDeviceMotion.cpp b/Code/Framework/AzFramework/AzFramework/Input/Devices/Motion/InputDeviceMotion.cpp index 34c72f20d2..c144f63d2c 100644 --- a/Code/Framework/AzFramework/AzFramework/Input/Devices/Motion/InputDeviceMotion.cpp +++ b/Code/Framework/AzFramework/AzFramework/Input/Devices/Motion/InputDeviceMotion.cpp @@ -23,44 +23,6 @@ namespace AzFramework return (inputDeviceId.GetNameCrc32() == Id.GetNameCrc32()); } - //////////////////////////////////////////////////////////////////////////////////////////////// - const InputChannelId InputDeviceMotion::Acceleration::Gravity("motion_acceleration_gravity"); - const InputChannelId InputDeviceMotion::Acceleration::Raw("motion_acceleration_raw"); - const InputChannelId InputDeviceMotion::Acceleration::User("motion_acceleration_user"); - const AZStd::array InputDeviceMotion::Acceleration::All = - {{ - Gravity, - Raw, - User - }}; - - //////////////////////////////////////////////////////////////////////////////////////////////// - const InputChannelId InputDeviceMotion::RotationRate::Raw("motion_rotation_rate_raw"); - const InputChannelId InputDeviceMotion::RotationRate::Unbiased("motion_rotation_rate_unbiased"); - const AZStd::array InputDeviceMotion::RotationRate::All = - {{ - Raw, - Unbiased - }}; - - //////////////////////////////////////////////////////////////////////////////////////////////// - const InputChannelId InputDeviceMotion::MagneticField::North("motion_magnetic_field_north"); - const InputChannelId InputDeviceMotion::MagneticField::Raw("motion_magnetic_field_raw"); - const InputChannelId InputDeviceMotion::MagneticField::Unbiased("motion_magnetic_field_unbiased"); - const AZStd::array InputDeviceMotion::MagneticField::All = - {{ - North, - Raw, - Unbiased - }}; - - //////////////////////////////////////////////////////////////////////////////////////////////// - const InputChannelId InputDeviceMotion::Orientation::Current("motion_orientation_current"); - const AZStd::array InputDeviceMotion::Orientation::All = - {{ - Current - }}; - //////////////////////////////////////////////////////////////////////////////////////////////// void InputDeviceMotion::Reflect(AZ::ReflectContext* context) { diff --git a/Code/Framework/AzFramework/AzFramework/Input/Devices/Motion/InputDeviceMotion.h b/Code/Framework/AzFramework/AzFramework/Input/Devices/Motion/InputDeviceMotion.h index 1e020a6e02..872bd1cfa0 100644 --- a/Code/Framework/AzFramework/AzFramework/Input/Devices/Motion/InputDeviceMotion.h +++ b/Code/Framework/AzFramework/AzFramework/Input/Devices/Motion/InputDeviceMotion.h @@ -44,12 +44,17 @@ namespace AzFramework //! - InputMotionSensorRequests::SetInputChannelEnabled struct Acceleration { - static const InputChannelId Gravity; - static const InputChannelId Raw; - static const InputChannelId User; + static constexpr inline InputChannelId Gravity{"motion_acceleration_gravity"}; + static constexpr inline InputChannelId Raw{"motion_acceleration_raw"}; + static constexpr inline InputChannelId User{"motion_acceleration_user"}; //!< All acceleration input channel ids - static const AZStd::array All; + static constexpr inline AZStd::array All + { + Gravity, + Raw, + User + }; }; //////////////////////////////////////////////////////////////////////////////////////////// @@ -60,11 +65,15 @@ namespace AzFramework //! - InputMotionSensorRequests::SetInputChannelEnabled struct RotationRate { - static const InputChannelId Raw; - static const InputChannelId Unbiased; + static constexpr inline InputChannelId Raw{"motion_rotation_rate_raw"}; + static constexpr inline InputChannelId Unbiased{"motion_rotation_rate_unbiased"}; //!< All rotation rate input channel ids - static const AZStd::array All; + static constexpr inline AZStd::array All + { + Raw, + Unbiased + }; }; //////////////////////////////////////////////////////////////////////////////////////////// @@ -75,12 +84,17 @@ namespace AzFramework //! - InputMotionSensorRequests::SetInputChannelEnabled struct MagneticField { - static const InputChannelId North; - static const InputChannelId Raw; - static const InputChannelId Unbiased; + static constexpr inline InputChannelId North{"motion_magnetic_field_north"}; + static constexpr inline InputChannelId Raw{"motion_magnetic_field_raw"}; + static constexpr inline InputChannelId Unbiased{"motion_magnetic_field_unbiased"}; //!< All magnetic field input channel ids - static const AZStd::array All; + static constexpr inline AZStd::array All + { + North, + Raw, + Unbiased + }; }; //////////////////////////////////////////////////////////////////////////////////////////// @@ -91,10 +105,13 @@ namespace AzFramework //! - InputMotionSensorRequests::SetInputChannelEnabled struct Orientation { - static const InputChannelId Current; + static constexpr inline InputChannelId Current{"motion_orientation_current"}; //!< All orientation input channel ids - static const AZStd::array All; + static constexpr inline AZStd::array All + { + Current + }; }; //////////////////////////////////////////////////////////////////////////////////////////// diff --git a/Code/Framework/AzFramework/AzFramework/Input/Devices/Mouse/InputDeviceMouse.cpp b/Code/Framework/AzFramework/AzFramework/Input/Devices/Mouse/InputDeviceMouse.cpp index 2cc4573bce..e9af4cc4ce 100644 --- a/Code/Framework/AzFramework/AzFramework/Input/Devices/Mouse/InputDeviceMouse.cpp +++ b/Code/Framework/AzFramework/AzFramework/Input/Devices/Mouse/InputDeviceMouse.cpp @@ -33,35 +33,6 @@ namespace AzFramework return (inputDeviceId.GetNameCrc32() == Id.GetNameCrc32()); } - //////////////////////////////////////////////////////////////////////////////////////////////// - const InputChannelId InputDeviceMouse::Button::Left("mouse_button_left"); - const InputChannelId InputDeviceMouse::Button::Right("mouse_button_right"); - const InputChannelId InputDeviceMouse::Button::Middle("mouse_button_middle"); - const InputChannelId InputDeviceMouse::Button::Other1("mouse_button_other1"); - const InputChannelId InputDeviceMouse::Button::Other2("mouse_button_other2"); - const AZStd::array InputDeviceMouse::Button::All = - {{ - Left, - Right, - Middle, - Other1, - Other2 - }}; - - //////////////////////////////////////////////////////////////////////////////////////////////// - const InputChannelId InputDeviceMouse::Movement::X("mouse_delta_x"); - const InputChannelId InputDeviceMouse::Movement::Y("mouse_delta_y"); - const InputChannelId InputDeviceMouse::Movement::Z("mouse_delta_z"); - const AZStd::array InputDeviceMouse::Movement::All = - {{ - X, - Y, - Z - }}; - - //////////////////////////////////////////////////////////////////////////////////////////////// - const InputChannelId InputDeviceMouse::SystemCursorPosition("mouse_system_cursor_position"); - //////////////////////////////////////////////////////////////////////////////////////////////// void InputDeviceMouse::Reflect(AZ::ReflectContext* context) { diff --git a/Code/Framework/AzFramework/AzFramework/Input/Devices/Mouse/InputDeviceMouse.h b/Code/Framework/AzFramework/AzFramework/Input/Devices/Mouse/InputDeviceMouse.h index 99fe5b820c..3b35e03f1b 100644 --- a/Code/Framework/AzFramework/AzFramework/Input/Devices/Mouse/InputDeviceMouse.h +++ b/Code/Framework/AzFramework/AzFramework/Input/Devices/Mouse/InputDeviceMouse.h @@ -66,14 +66,21 @@ namespace AzFramework //! been implemented for windows simply to provide for backwards compatibility with CryInput. struct Button { - static const InputChannelId Left; //!< The left mouse button - static const InputChannelId Right; //!< The right mouse button - static const InputChannelId Middle; //!< The middle mouse button - static const InputChannelId Other1; //!< DEPRECATED: the x1 mouse button - static const InputChannelId Other2; //!< DEPRECATED: the x2 mouse button + static constexpr inline InputChannelId Left{"mouse_button_left"}; //!< The left mouse button + static constexpr inline InputChannelId Right{"mouse_button_right"}; //!< The right mouse button + static constexpr inline InputChannelId Middle{"mouse_button_middle"}; //!< The middle mouse button + static constexpr inline InputChannelId Other1{"mouse_button_other1"}; //!< DEPRECATED: the x1 mouse button + static constexpr inline InputChannelId Other2{"mouse_button_other2"}; //!< DEPRECATED: the x2 mouse button //!< All mouse button ids - static const AZStd::array All; + static constexpr inline AZStd::array All + { + Left, + Right, + Middle, + Other1, + Other2 + }; }; //////////////////////////////////////////////////////////////////////////////////////////// @@ -82,12 +89,17 @@ namespace AzFramework //! directly correlate to the mouse position (which is queried directly from the system). struct Movement { - static const InputChannelId X; //!< Raw horizontal mouse movement over the last frame - static const InputChannelId Y; //!< Raw vertical mouse movement over the last frame - static const InputChannelId Z; //!< Raw mouse wheel movement over the last frame + static constexpr inline InputChannelId X{"mouse_delta_x"}; //!< Raw horizontal mouse movement over the last frame + static constexpr inline InputChannelId Y{"mouse_delta_y"}; //!< Raw vertical mouse movement over the last frame + static constexpr inline InputChannelId Z{"mouse_delta_z"}; //!< Raw mouse wheel movement over the last frame //!< All mouse movement ids - static const AZStd::array All; + static constexpr inline AZStd::array All + { + X, + Y, + Z + }; }; //////////////////////////////////////////////////////////////////////////////////////////// @@ -96,7 +108,7 @@ namespace AzFramework //! the system cursor is hidden or visible. When the system cursor has been constrained to //! the active window values will be in the [0.0, 1.0] range, but not when unconstrained. //! See also InputSystemCursorRequests::SetSystemCursorState and GetSystemCursorState. - static const InputChannelId SystemCursorPosition; + static constexpr inline InputChannelId SystemCursorPosition{"mouse_system_cursor_position"}; //////////////////////////////////////////////////////////////////////////////////////////// // Allocator diff --git a/Code/Framework/AzFramework/AzFramework/Input/Devices/Touch/InputDeviceTouch.cpp b/Code/Framework/AzFramework/AzFramework/Input/Devices/Touch/InputDeviceTouch.cpp index 07d10090a3..be850e9289 100644 --- a/Code/Framework/AzFramework/AzFramework/Input/Devices/Touch/InputDeviceTouch.cpp +++ b/Code/Framework/AzFramework/AzFramework/Input/Devices/Touch/InputDeviceTouch.cpp @@ -24,31 +24,6 @@ namespace AzFramework return (inputDeviceId.GetNameCrc32() == Id.GetNameCrc32()); } - //////////////////////////////////////////////////////////////////////////////////////////////// - const InputChannelId InputDeviceTouch::Touch::Index0("touch_index_0"); - const InputChannelId InputDeviceTouch::Touch::Index1("touch_index_1"); - const InputChannelId InputDeviceTouch::Touch::Index2("touch_index_2"); - const InputChannelId InputDeviceTouch::Touch::Index3("touch_index_3"); - const InputChannelId InputDeviceTouch::Touch::Index4("touch_index_4"); - const InputChannelId InputDeviceTouch::Touch::Index5("touch_index_5"); - const InputChannelId InputDeviceTouch::Touch::Index6("touch_index_6"); - const InputChannelId InputDeviceTouch::Touch::Index7("touch_index_7"); - const InputChannelId InputDeviceTouch::Touch::Index8("touch_index_8"); - const InputChannelId InputDeviceTouch::Touch::Index9("touch_index_9"); - const AZStd::array InputDeviceTouch::Touch::All = - {{ - Index0, - Index1, - Index2, - Index3, - Index4, - Index5, - Index6, - Index7, - Index8, - Index9 - }}; - //////////////////////////////////////////////////////////////////////////////////////////////// void InputDeviceTouch::Reflect(AZ::ReflectContext* context) { diff --git a/Code/Framework/AzFramework/AzFramework/Input/Devices/Touch/InputDeviceTouch.h b/Code/Framework/AzFramework/AzFramework/Input/Devices/Touch/InputDeviceTouch.h index 6e069c44ba..d21834c22a 100644 --- a/Code/Framework/AzFramework/AzFramework/Input/Devices/Touch/InputDeviceTouch.h +++ b/Code/Framework/AzFramework/AzFramework/Input/Devices/Touch/InputDeviceTouch.h @@ -38,19 +38,31 @@ namespace AzFramework //! track is arbitrary, but ten seems to be more than sufficient for most game applications. struct Touch { - static const InputChannelId Index0; //!< Touch index 0 - static const InputChannelId Index1; //!< Touch index 1 - static const InputChannelId Index2; //!< Touch index 2 - static const InputChannelId Index3; //!< Touch index 3 - static const InputChannelId Index4; //!< Touch index 4 - static const InputChannelId Index5; //!< Touch index 5 - static const InputChannelId Index6; //!< Touch index 6 - static const InputChannelId Index7; //!< Touch index 7 - static const InputChannelId Index8; //!< Touch index 8 - static const InputChannelId Index9; //!< Touch index 9 + static constexpr inline InputChannelId Index0{"touch_index_0"}; //!< Touch index 0 + static constexpr inline InputChannelId Index1{"touch_index_1"}; //!< Touch index 1 + static constexpr inline InputChannelId Index2{"touch_index_2"}; //!< Touch index 2 + static constexpr inline InputChannelId Index3{"touch_index_3"}; //!< Touch index 3 + static constexpr inline InputChannelId Index4{"touch_index_4"}; //!< Touch index 4 + static constexpr inline InputChannelId Index5{"touch_index_5"}; //!< Touch index 5 + static constexpr inline InputChannelId Index6{"touch_index_6"}; //!< Touch index 6 + static constexpr inline InputChannelId Index7{"touch_index_7"}; //!< Touch index 7 + static constexpr inline InputChannelId Index8{"touch_index_8"}; //!< Touch index 8 + static constexpr inline InputChannelId Index9{"touch_index_9"}; //!< Touch index 9 //!< All touch input channel ids - static const AZStd::array All; + static constexpr inline AZStd::array All + { + Index0, + Index1, + Index2, + Index3, + Index4, + Index5, + Index6, + Index7, + Index8, + Index9 + }; }; //////////////////////////////////////////////////////////////////////////////////////////// diff --git a/Code/Framework/AzFramework/AzFramework/Input/Devices/VirtualKeyboard/InputDeviceVirtualKeyboard.cpp b/Code/Framework/AzFramework/AzFramework/Input/Devices/VirtualKeyboard/InputDeviceVirtualKeyboard.cpp index bc23826ebc..f3024f11ab 100644 --- a/Code/Framework/AzFramework/AzFramework/Input/Devices/VirtualKeyboard/InputDeviceVirtualKeyboard.cpp +++ b/Code/Framework/AzFramework/AzFramework/Input/Devices/VirtualKeyboard/InputDeviceVirtualKeyboard.cpp @@ -23,17 +23,6 @@ namespace AzFramework return (inputDeviceId.GetNameCrc32() == Id.GetNameCrc32()); } - //////////////////////////////////////////////////////////////////////////////////////////////// - const InputChannelId InputDeviceVirtualKeyboard::Command::EditEnter("virtual_keyboard_edit_enter"); - const InputChannelId InputDeviceVirtualKeyboard::Command::EditClear("virtual_keyboard_edit_clear"); - const InputChannelId InputDeviceVirtualKeyboard::Command::NavigationBack("virtual_keyboard_navigation_back"); - const AZStd::array InputDeviceVirtualKeyboard::Command::All = - {{ - EditClear, - EditEnter, - NavigationBack - }}; - //////////////////////////////////////////////////////////////////////////////////////////////// void InputDeviceVirtualKeyboard::Reflect(AZ::ReflectContext* context) { diff --git a/Code/Framework/AzFramework/AzFramework/Input/Devices/VirtualKeyboard/InputDeviceVirtualKeyboard.h b/Code/Framework/AzFramework/AzFramework/Input/Devices/VirtualKeyboard/InputDeviceVirtualKeyboard.h index 43e2815875..6f62c3ec61 100644 --- a/Code/Framework/AzFramework/AzFramework/Input/Devices/VirtualKeyboard/InputDeviceVirtualKeyboard.h +++ b/Code/Framework/AzFramework/AzFramework/Input/Devices/VirtualKeyboard/InputDeviceVirtualKeyboard.h @@ -39,17 +39,22 @@ namespace AzFramework struct Command { //!< The clear command used to indicate the user wants to clear the active text field - static const InputChannelId EditClear; + static constexpr inline InputChannelId EditClear{"virtual_keyboard_edit_enter"}; //!< The enter/return/close command used to indicate the user has finished text editing - static const InputChannelId EditEnter; + static constexpr inline InputChannelId EditEnter{"virtual_keyboard_edit_clear"}; //!< The back command used to indicate the user wants to navigate 'backwards'. //!< This is specific to android devices, and does not have an ios equivalent. - static const InputChannelId NavigationBack; + static constexpr inline InputChannelId NavigationBack{"virtual_keyboard_navigation_back"}; //!< All virtual keyboard command ids - static const AZStd::array All; + static constexpr inline AZStd::array All + { + EditClear, + EditEnter, + NavigationBack + }; }; //////////////////////////////////////////////////////////////////////////////////////////// From a3280afef369a765f8d1050fc9c67dae17dbbd18 Mon Sep 17 00:00:00 2001 From: Junbo Liang <68558268+junbo75@users.noreply.github.com> Date: Mon, 27 Sep 2021 13:36:35 -0700 Subject: [PATCH 36/51] [AWSMetrics][cdk] Add the athena work group as a dependency of named queries (#4354) Signed-off-by: Junbo Liang --- Gems/AWSMetrics/cdk/aws_metrics/batch_analytics.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Gems/AWSMetrics/cdk/aws_metrics/batch_analytics.py b/Gems/AWSMetrics/cdk/aws_metrics/batch_analytics.py index eb36d8a5da..709335f9ce 100755 --- a/Gems/AWSMetrics/cdk/aws_metrics/batch_analytics.py +++ b/Gems/AWSMetrics/cdk/aws_metrics/batch_analytics.py @@ -122,6 +122,9 @@ class BatchAnalytics: ) ] + for named_query in self._named_queries: + named_query.node.add_dependency(self._athena_work_group) + @property def athena_work_group_name(self) -> athena.CfnWorkGroup.name: return self._athena_work_group.name From 4998f44246841002b64ecadce9872f1eadb244f2 Mon Sep 17 00:00:00 2001 From: evanchia Date: Mon, 27 Sep 2021 14:25:43 -0700 Subject: [PATCH 37/51] adding process_utils linux integ test Signed-off-by: evanchia --- .../tests/integ/test_process_utils_linux.py | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 Tools/LyTestTools/tests/integ/test_process_utils_linux.py diff --git a/Tools/LyTestTools/tests/integ/test_process_utils_linux.py b/Tools/LyTestTools/tests/integ/test_process_utils_linux.py new file mode 100644 index 0000000000..23ad8f6653 --- /dev/null +++ b/Tools/LyTestTools/tests/integ/test_process_utils_linux.py @@ -0,0 +1,39 @@ +""" +Copyright (c) Contributors to the Open 3D Engine Project. +For complete copyright and license terms please see the LICENSE at the root of this distribution. + +SPDX-License-Identifier: Apache-2.0 OR MIT +""" +import subprocess +import pytest + +import ly_test_tools.environment.process_utils as process_utils +import ly_test_tools.environment.waiter as waiter +from ly_test_tools import LINUX + +if LINUX: + pytestmark = pytest.mark.SUITE_smoke +else: + pytestmark = pytest.mark.skipif(not LINUX, reason="Only runs on Linux") + + +class TestProcessUtils(object): + + def test_KillLinuxProcess_ProcessStarted_KilledSuccessfully(self): + # Construct a simple timeout command + linux_executable = 'timeout' + command = [linux_executable, '5s', 'echo'] + + # Verification function for the waiter to call + def process_killed(): + return not process_utils.process_exists(linux_executable, ignore_extensions=True) + + # Create a new process with no output in a new session + with subprocess.Popen(command, stdout=subprocess.DEVNULL, start_new_session=True): + # Ensure that the process was started + assert process_utils.process_exists(linux_executable, ignore_extensions=True), \ + f"Process '{linux_executable}' was expected to exist, but could not be found." + # Kill the process using the process_utils module + process_utils.kill_processes_named(linux_executable, ignore_extensions=True) + # Verify that the process was killed + waiter.wait_for(process_killed, timeout=2) # Raises exception if the process is alive. From bcf024edf9eec8e42bfe6dec91b9fd8000321058 Mon Sep 17 00:00:00 2001 From: evanchia Date: Mon, 27 Sep 2021 14:33:08 -0700 Subject: [PATCH 38/51] registering linux integ test to cmake Signed-off-by: evanchia --- Tools/LyTestTools/tests/CMakeLists.txt | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Tools/LyTestTools/tests/CMakeLists.txt b/Tools/LyTestTools/tests/CMakeLists.txt index a6d7a0734c..31766f3080 100644 --- a/Tools/LyTestTools/tests/CMakeLists.txt +++ b/Tools/LyTestTools/tests/CMakeLists.txt @@ -39,6 +39,14 @@ if(PAL_TRAIT_BUILD_HOST_TOOLS AND PAL_TRAIT_BUILD_TESTS_SUPPORTED AND AutomatedT TEST_SUITE smoke COMPONENT TestTools ) + + ly_add_pytest( + NAME LyTestTools_IntegTests_ProcessUtilsLinux_smoke_no_gpu + PATH ${CMAKE_CURRENT_LIST_DIR}/integ/test_process_utils_linux.py + TEST_SERIAL + TEST_SUITE smoke + COMPONENT TestTools + ) # Regression tests. ly_add_pytest( From 133c85c641c692be215ea3fbb185d8549a5c393c Mon Sep 17 00:00:00 2001 From: galibzon <66021303+galibzon@users.noreply.github.com> Date: Mon, 27 Sep 2021 18:22:05 -0500 Subject: [PATCH 39/51] MCPP should not add new line (#4353) MCPP should not add new line characters at the end of comment lines Update for windows, mac and linux of mcpp package to 'mcpp-2.7.2-az.2-rev1' Signed-off-by: garrieta --- cmake/3rdParty/Platform/Linux/BuiltInPackages_linux.cmake | 2 +- cmake/3rdParty/Platform/Mac/BuiltInPackages_mac.cmake | 2 +- cmake/3rdParty/Platform/Windows/BuiltInPackages_windows.cmake | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cmake/3rdParty/Platform/Linux/BuiltInPackages_linux.cmake b/cmake/3rdParty/Platform/Linux/BuiltInPackages_linux.cmake index 816ef6db30..65e046c8ba 100644 --- a/cmake/3rdParty/Platform/Linux/BuiltInPackages_linux.cmake +++ b/cmake/3rdParty/Platform/Linux/BuiltInPackages_linux.cmake @@ -30,7 +30,7 @@ ly_associate_package(PACKAGE_NAME AWSNativeSDK-1.7.167-rev6-linux ly_associate_package(PACKAGE_NAME Lua-5.3.5-rev5-linux TARGETS Lua PACKAGE_HASH 1adc812abe3dd0dbb2ca9756f81d8f0e0ba45779ac85bf1d8455b25c531a38b0) ly_associate_package(PACKAGE_NAME PhysX-4.1.2.29882248-rev3-linux TARGETS PhysX PACKAGE_HASH a110249cbef4f266b0002c4ee9a71f59f373040cefbe6b82f1e1510c811edde6) ly_associate_package(PACKAGE_NAME etc2comp-9cd0f9cae0-rev1-linux TARGETS etc2comp PACKAGE_HASH 9283aa5db5bb7fb90a0ddb7a9f3895317c8ebe8044943124bbb3673a41407430) -ly_associate_package(PACKAGE_NAME mcpp-2.7.2_az.1-rev1-linux TARGETS mcpp PACKAGE_HASH 0aa713f3f2c156cb2f17d9b800aed8acf9df5ab167c48b679853ecb040da9a67) +ly_associate_package(PACKAGE_NAME mcpp-2.7.2_az.2-rev1-linux TARGETS mcpp PACKAGE_HASH df7a998d0bc3fedf44b5bdebaf69ddad6033355b71a590e8642445ec77bc6c41) ly_associate_package(PACKAGE_NAME mikkelsen-1.0.0.4-linux TARGETS mikkelsen PACKAGE_HASH 5973b1e71a64633588eecdb5b5c06ca0081f7be97230f6ef64365cbda315b9c8) ly_associate_package(PACKAGE_NAME googletest-1.8.1-rev4-linux TARGETS googletest PACKAGE_HASH 7b7ad330f369450c316a4c4592d17fbb4c14c731c95bd8f37757203e8c2bbc1b) ly_associate_package(PACKAGE_NAME googlebenchmark-1.5.0-rev2-linux TARGETS GoogleBenchmark PACKAGE_HASH 4038878f337fc7e0274f0230f71851b385b2e0327c495fc3dd3d1c18a807928d) diff --git a/cmake/3rdParty/Platform/Mac/BuiltInPackages_mac.cmake b/cmake/3rdParty/Platform/Mac/BuiltInPackages_mac.cmake index 58cafdd4b8..acddb879d4 100644 --- a/cmake/3rdParty/Platform/Mac/BuiltInPackages_mac.cmake +++ b/cmake/3rdParty/Platform/Mac/BuiltInPackages_mac.cmake @@ -32,7 +32,7 @@ ly_associate_package(PACKAGE_NAME AWSNativeSDK-1.7.167-rev5-mac ly_associate_package(PACKAGE_NAME Lua-5.3.5-rev6-mac TARGETS Lua PACKAGE_HASH b9079fd35634774c9269028447562c6b712dbc83b9c64975c095fd423ff04c08) ly_associate_package(PACKAGE_NAME PhysX-4.1.2.29882248-rev3-mac TARGETS PhysX PACKAGE_HASH 5e092a11d5c0a50c4dd99bb681a04b566a4f6f29aa08443d9bffc8dc12c27c8e) ly_associate_package(PACKAGE_NAME etc2comp-9cd0f9cae0-rev1-mac TARGETS etc2comp PACKAGE_HASH 1966ab101c89db7ecf30984917e0a48c0d02ee0e4d65b798743842b9469c0818) -ly_associate_package(PACKAGE_NAME mcpp-2.7.2_az.1-rev1-mac TARGETS mcpp PACKAGE_HASH 48a9c5197bf72843fb9ac44825501ee16bbe3e72e086a32b8c9c05bf47db12ab) +ly_associate_package(PACKAGE_NAME mcpp-2.7.2_az.2-rev1-mac TARGETS mcpp PACKAGE_HASH be9558905c9c49179ef3d7d84f0a5472415acdf7fe2d76eb060d9431723ddf2e) ly_associate_package(PACKAGE_NAME mikkelsen-1.0.0.4-mac TARGETS mikkelsen PACKAGE_HASH 83af99ca8bee123684ad254263add556f0cf49486c0b3e32e6d303535714e505) ly_associate_package(PACKAGE_NAME googletest-1.8.1-rev4-mac TARGETS googletest PACKAGE_HASH cbf020d5ef976c5db8b6e894c6c63151ade85ed98e7c502729dd20172acae5a8) ly_associate_package(PACKAGE_NAME googlebenchmark-1.5.0-rev2-mac TARGETS GoogleBenchmark PACKAGE_HASH ad25de0146769c91e179953d845de2bec8ed4a691f973f47e3eb37639381f665) diff --git a/cmake/3rdParty/Platform/Windows/BuiltInPackages_windows.cmake b/cmake/3rdParty/Platform/Windows/BuiltInPackages_windows.cmake index d02fefc0e9..d56a03f515 100644 --- a/cmake/3rdParty/Platform/Windows/BuiltInPackages_windows.cmake +++ b/cmake/3rdParty/Platform/Windows/BuiltInPackages_windows.cmake @@ -33,7 +33,7 @@ ly_associate_package(PACKAGE_NAME AWSNativeSDK-1.7.167-rev4-windows ly_associate_package(PACKAGE_NAME Lua-5.3.5-rev5-windows TARGETS Lua PACKAGE_HASH 136faccf1f73891e3fa3b95f908523187792e56f5b92c63c6a6d7e72d1158d40) ly_associate_package(PACKAGE_NAME PhysX-4.1.2.29882248-rev3-windows TARGETS PhysX PACKAGE_HASH 0c5ffbd9fa588e5cf7643721a7cfe74d0fe448bf82252d39b3a96d06dfca2298) ly_associate_package(PACKAGE_NAME etc2comp-9cd0f9cae0-rev1-windows TARGETS etc2comp PACKAGE_HASH fc9ae937b2ec0d42d5e7d0e9e8c80e5e4d257673fb33bc9b7d6db76002117123) -ly_associate_package(PACKAGE_NAME mcpp-2.7.2_az.1-rev1-windows TARGETS mcpp PACKAGE_HASH 511672598fa319bfb8db87f965b59abff1620bb7c1dcf7669e039a8acd8d3ff8) +ly_associate_package(PACKAGE_NAME mcpp-2.7.2_az.2-rev1-windows TARGETS mcpp PACKAGE_HASH 794789aba639bfe2f4e8fcb4424d679933dd6290e523084aa0a4e287ac44acb2) ly_associate_package(PACKAGE_NAME mikkelsen-1.0.0.4-windows TARGETS mikkelsen PACKAGE_HASH 872c4d245a1c86139aa929f2b465b63ea4ea55b04ced50309135dd4597457a4e) ly_associate_package(PACKAGE_NAME googletest-1.8.1-rev4-windows TARGETS googletest PACKAGE_HASH 7e8f03ae8a01563124e3daa06386f25a2b311c10bb95bff05cae6c41eff83837) ly_associate_package(PACKAGE_NAME googlebenchmark-1.5.0-rev2-windows TARGETS GoogleBenchmark PACKAGE_HASH 0c94ca69ae8e7e4aab8e90032b5c82c5964410429f3dd9dbb1f9bf4fe032b1d4) From 6c7cd2abf0a981f37708dd38d0e7085ed31db5cb Mon Sep 17 00:00:00 2001 From: Guthrie Adams Date: Mon, 27 Sep 2021 18:50:33 -0500 Subject: [PATCH 40/51] =?UTF-8?q?Atom=20Tools:=20Fixing=20problems=20with?= =?UTF-8?q?=20inspector=20property=20group=20layout=20and=20deletion=20?= =?UTF-8?q?=E2=80=A2=20A=20previous=20change=20introduced=20a=20heading=20?= =?UTF-8?q?section=20of=20the=20top=20of=20the=20inspector=20=E2=80=A2=20T?= =?UTF-8?q?he=20code=20was=20incorrectly=20attempting=20to=20delete=20chil?= =?UTF-8?q?d=20widgets=20and=20layouts=20from=20the=20layout=20of=20each?= =?UTF-8?q?=20section=20=E2=80=A2=20Size=20policies=20were=20also=20update?= =?UTF-8?q?d=20to=20give=20priority=20to=20the=20section=20containing=20th?= =?UTF-8?q?e=20property=20groups=20=E2=80=A2=20Tested=20material=20compone?= =?UTF-8?q?nt=20property=20editor=20and=20material=20editor=20to=20make=20?= =?UTF-8?q?sure=20that=20widgets=20were=20laid=20out=20correctly=20at=20th?= =?UTF-8?q?e=20top=20of=20the=20inspector?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Guthrie Adams --- .../Code/Source/Inspector/InspectorWidget.cpp | 18 +- .../Code/Source/Inspector/InspectorWidget.ui | 169 ++++++++---------- 2 files changed, 87 insertions(+), 100 deletions(-) diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Inspector/InspectorWidget.cpp b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Inspector/InspectorWidget.cpp index 5eda93ca59..5ef2dbb2c5 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Inspector/InspectorWidget.cpp +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Inspector/InspectorWidget.cpp @@ -31,20 +31,26 @@ namespace AtomToolsFramework void InspectorWidget::AddHeading(QWidget* headingWidget) { - headingWidget->setParent(m_ui->m_headingSection); - m_ui->m_headingSectionLayout->addWidget(headingWidget); + headingWidget->setParent(this); + m_ui->m_headingContentsLayout->addWidget(headingWidget); } void InspectorWidget::ClearHeading() { - qDeleteAll(m_ui->m_headingSection->findChildren(QString(), Qt::FindDirectChildrenOnly)); - qDeleteAll(m_ui->m_headingSectionLayout->children()); + while (QLayoutItem* child = m_ui->m_headingContentsLayout->takeAt(0)) + { + delete child->widget(); + delete child; + } } void InspectorWidget::Reset() { - qDeleteAll(m_ui->m_groupContents->findChildren(QString(), Qt::FindDirectChildrenOnly)); - qDeleteAll(m_ui->m_groupContentsLayout->children()); + while (QLayoutItem* child = m_ui->m_groupContentsLayout->takeAt(0)) + { + delete child->widget(); + delete child; + } m_groups.clear(); } diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Inspector/InspectorWidget.ui b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Inspector/InspectorWidget.ui index 13f9c9e41c..c197ee172e 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Inspector/InspectorWidget.ui +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Inspector/InspectorWidget.ui @@ -30,104 +30,85 @@ 0 - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::ScrollBarAsNeeded - - 0 + + true - - 0 - - - 0 - - - 0 - - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Qt::ScrollBarAsNeeded + + + + 0 + 0 + 679 + 767 + + + + + 0 + 0 + + + + + 0 - - true + + 0 - - - - 0 - 0 - 683 - 763 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - - - - - + + 0 + + + 0 + + + 0 + + + + + + From 96d99c2814ce8da8d5fbd322b1e8456caaac601b Mon Sep 17 00:00:00 2001 From: AMZN-Igarri <82394219+AMZN-Igarri@users.noreply.github.com> Date: Tue, 28 Sep 2021 10:11:15 +0200 Subject: [PATCH 41/51] Switched over from Error to Warning - Restore viewport when exiting game mode error. (#4343) Signed-off-by: igarri --- Code/Editor/EditorViewportWidget.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Code/Editor/EditorViewportWidget.cpp b/Code/Editor/EditorViewportWidget.cpp index 5b5b52d28f..dad6734428 100644 --- a/Code/Editor/EditorViewportWidget.cpp +++ b/Code/Editor/EditorViewportWidget.cpp @@ -2428,7 +2428,7 @@ void EditorViewportWidget::RestoreViewportAfterGameMode() } else { - AZ_Error("CryLegacy", false, "Not restoring the editor viewport camera is currently unsupported"); + AZ_Warning("CryLegacy", false, "Not restoring the editor viewport camera is currently unsupported"); SetViewTM(preGameModeViewTM); } } From 7da866e81a8cc547e17e93462173cd2ab4c534e2 Mon Sep 17 00:00:00 2001 From: hultonha <82228511+hultonha@users.noreply.github.com> Date: Tue, 28 Sep 2021 09:26:20 +0100 Subject: [PATCH 42/51] Update to how entity space is treated in the viewport (#4263) * first pass fixes for how entity space is handled in the viewport interaction model Signed-off-by: hultonha * small updates to simplify space handling Signed-off-by: hultonha * fix for influence group with one entity selected Signed-off-by: hultonha * some tidy-up and fix for scale manipulator snapping Signed-off-by: hultonha * couple of small fixes for scale manipulator Signed-off-by: hultonha * small comment update Signed-off-by: hultonha * fixes for integration test failures Signed-off-by: hultonha * add test for rotation manipulator Signed-off-by: hultonha * add test coverage for rotation manipulators Signed-off-by: hultonha * add tests for translation manipulators and some other tidy-up changes Signed-off-by: hultonha * add tests for translating a group of entities Signed-off-by: hultonha * add some tests for scale manipulators Signed-off-by: hultonha * some updates and formatting changes (clang-format) to ViewportScreen Signed-off-by: hultonha * simplify usage of lround code Signed-off-by: hultonha * update missed name updates Signed-off-by: hultonha * update usage of WorldToScreenNdc Signed-off-by: hultonha * updates following review feedback Signed-off-by: hultonha * some more small tidy-up changes Signed-off-by: hultonha * move static variables to be marked inline Signed-off-by: hultonha * small formatting fixes Signed-off-by: hultonha --- .../AzFramework/Viewport/ScreenGeometry.h | 53 +- .../AzFramework/Viewport/ViewportScreen.cpp | 50 +- .../AzFramework/Viewport/ViewportScreen.h | 41 +- .../Manipulators/ScaleManipulators.cpp | 1 + .../Manipulators/TranslationManipulators.cpp | 27 +- .../EditorTransformComponentSelection.cpp | 179 +++--- .../EditorTransformComponentSelection.h | 11 +- ...EditorTransformComponentSelectionTests.cpp | 600 +++++++++++++++++- .../Tests/Viewport/ViewportScreenTests.cpp | 128 ++-- .../AtomFont/Code/Source/FFont.cpp | 2 +- 10 files changed, 868 insertions(+), 224 deletions(-) diff --git a/Code/Framework/AzFramework/AzFramework/Viewport/ScreenGeometry.h b/Code/Framework/AzFramework/AzFramework/Viewport/ScreenGeometry.h index c0fa1ad631..1529d760e5 100644 --- a/Code/Framework/AzFramework/AzFramework/Viewport/ScreenGeometry.h +++ b/Code/Framework/AzFramework/AzFramework/Viewport/ScreenGeometry.h @@ -8,11 +8,10 @@ #pragma once -#include -#include -#include -#include #include +#include +#include +#include namespace AZ { @@ -27,7 +26,11 @@ namespace AzFramework AZ_TYPE_INFO(ScreenPoint, "{8472B6C2-527F-44FC-87F8-C226B1A57A97}"); ScreenPoint() = default; - ScreenPoint(int x, int y) : m_x(x), m_y(y) {} + ScreenPoint(int x, int y) + : m_x(x) + , m_y(y) + { + } int m_x; //!< X screen position. int m_y; //!< Y screen position. @@ -42,7 +45,11 @@ namespace AzFramework AZ_TYPE_INFO(ScreenVector, "{1EAA2C62-8FDB-4A28-9FE3-1FA4F1418894}"); ScreenVector() = default; - ScreenVector(int x, int y) : m_x(x), m_y(y) {} + ScreenVector(int x, int y) + : m_x(x) + , m_y(y) + { + } int m_x; //!< X screen delta. int m_y; //!< Y screen delta. @@ -71,14 +78,14 @@ namespace AzFramework inline const ScreenPoint operator+(const ScreenPoint& lhs, const ScreenVector& rhs) { - ScreenPoint result{lhs}; + ScreenPoint result{ lhs }; result += rhs; return result; } inline const ScreenPoint operator-(const ScreenPoint& lhs, const ScreenVector& rhs) { - ScreenPoint result{lhs}; + ScreenPoint result{ lhs }; result -= rhs; return result; } @@ -99,14 +106,14 @@ namespace AzFramework inline const ScreenVector operator+(const ScreenVector& lhs, const ScreenVector& rhs) { - ScreenVector result{lhs}; + ScreenVector result{ lhs }; result += rhs; return result; } inline const ScreenVector operator-(const ScreenVector& lhs, const ScreenVector& rhs) { - ScreenVector result{lhs}; + ScreenVector result{ lhs }; result -= rhs; return result; } @@ -131,23 +138,25 @@ namespace AzFramework return !operator==(lhs, rhs); } + inline ScreenVector& operator*=(ScreenVector& lhs, const float rhs) + { + lhs.m_x = aznumeric_cast(AZStd::lround(aznumeric_cast(lhs.m_x) * rhs)); + lhs.m_y = aznumeric_cast(AZStd::lround(aznumeric_cast(lhs.m_y) * rhs)); + return lhs; + } + + inline const ScreenVector operator*(const ScreenVector& lhs, const float rhs) + { + ScreenVector result{ lhs }; + result *= rhs; + return result; + } + inline float ScreenVectorLength(const ScreenVector& screenVector) { return aznumeric_cast(AZStd::sqrt(screenVector.m_x * screenVector.m_x + screenVector.m_y * screenVector.m_y)); } - inline ScreenPoint ScreenPointFromNDC(const AZ::Vector3& screenNDC, const AZ::Vector2& viewportSize) - { - return ScreenPoint( - aznumeric_caster(AZStd::round(screenNDC.GetX() * viewportSize.GetX())), - aznumeric_caster(AZStd::round((1.0f - screenNDC.GetY()) * viewportSize.GetY()))); - } - - inline AZ::Vector2 NDCFromScreenPoint(const ScreenPoint& screenPoint, const AZ::Vector2& viewportSize) - { - return AZ::Vector2(aznumeric_cast(screenPoint.m_x), viewportSize.GetY() - aznumeric_cast(screenPoint.m_y)) / viewportSize; - } - //! Return an AZ::Vector2 from a ScreenPoint. inline AZ::Vector2 Vector2FromScreenPoint(const ScreenPoint& screenPoint) { diff --git a/Code/Framework/AzFramework/AzFramework/Viewport/ViewportScreen.cpp b/Code/Framework/AzFramework/AzFramework/Viewport/ViewportScreen.cpp index a94b806ad3..afd16a6c23 100644 --- a/Code/Framework/AzFramework/AzFramework/Viewport/ViewportScreen.cpp +++ b/Code/Framework/AzFramework/AzFramework/Viewport/ViewportScreen.cpp @@ -23,7 +23,7 @@ namespace AzFramework // y-up, z into the screen, x-left // // x -> -x - // y -> z + // y -> z // z -> y // // the same transform can be used to go to/from z-up - the only difference is the order of @@ -37,15 +37,15 @@ namespace AzFramework // yaw = AZ::Matrix4x4::CreateRotationZ(AZ::DegToRad(180.0f)); // conversion = pitch * yaw return AZ::Matrix4x4::CreateFromColumns( - AZ::Vector4(-1.0f, 0.0f, 0.0f, 0.0f), AZ::Vector4(0.0f, 0.0f, 1.0f, .0f), - AZ::Vector4(0.0f, 1.0f, 0.0f, 0.0f), AZ::Vector4(0.0f, 0.0f, 0.0f, 1.0f)); + AZ::Vector4(-1.0f, 0.0f, 0.0f, 0.0f), AZ::Vector4(0.0f, 0.0f, 1.0f, 0.0f), AZ::Vector4(0.0f, 1.0f, 0.0f, 0.0f), + AZ::Vector4(0.0f, 0.0f, 0.0f, 1.0f)); } AZ::Matrix4x4 CameraTransform(const CameraState& cameraState) { return AZ::Matrix4x4::CreateFromColumns( - AZ::Vector3ToVector4(cameraState.m_side), AZ::Vector3ToVector4(cameraState.m_forward), - AZ::Vector3ToVector4(cameraState.m_up), AZ::Vector3ToVector4(cameraState.m_position, 1.0f)); + AZ::Vector3ToVector4(cameraState.m_side), AZ::Vector3ToVector4(cameraState.m_forward), AZ::Vector3ToVector4(cameraState.m_up), + AZ::Vector3ToVector4(cameraState.m_position, 1.0f)); } AZ::Matrix4x4 CameraView(const CameraState& cameraState) @@ -63,8 +63,7 @@ namespace AzFramework AZ::Matrix4x4 CameraProjection(const CameraState& cameraState) { return AZ::Matrix4x4::CreateProjection( - cameraState.VerticalFovRadian(), AspectRatio(cameraState.m_viewportSize), cameraState.m_nearClip, - cameraState.m_farClip); + cameraState.VerticalFovRadian(), AspectRatio(cameraState.m_viewportSize), cameraState.m_nearClip, cameraState.m_farClip); } AZ::Matrix4x4 InverseCameraProjection(const CameraState& cameraState) @@ -93,12 +92,11 @@ namespace AzFramework const auto cameraWorldTransform = AZ::Transform::CreateFromMatrix3x3AndTranslation( AZ::Matrix3x3::CreateFromMatrix4x4(worldFromView), worldFromView.GetTranslation()); return AZ::ViewFrustumAttributes( - cameraWorldTransform, AspectRatio(cameraState.m_viewportSize), cameraState.m_fovOrZoom, - cameraState.m_nearClip, cameraState.m_farClip); + cameraWorldTransform, AspectRatio(cameraState.m_viewportSize), cameraState.m_fovOrZoom, cameraState.m_nearClip, + cameraState.m_farClip); } - AZ::Vector3 WorldToScreenNDC( - const AZ::Vector3& worldPosition, const AZ::Matrix4x4& cameraView, const AZ::Matrix4x4& cameraProjection) + AZ::Vector3 WorldToScreenNdc(const AZ::Vector3& worldPosition, const AZ::Matrix4x4& cameraView, const AZ::Matrix4x4& cameraProjection) { // transform the world space position to clip space const auto clipSpacePosition = cameraProjection * cameraView * AZ::Vector3ToVector4(worldPosition, 1.0f); @@ -108,25 +106,24 @@ namespace AzFramework return (AZ::Vector4ToVector3(ndcPosition) + AZ::Vector3::CreateOne()) * 0.5f; } - ScreenPoint WorldToScreen( - const AZ::Vector3& worldPosition, const AZ::Matrix4x4& cameraView, const AZ::Matrix4x4& cameraProjection, + const AZ::Vector3& worldPosition, + const AZ::Matrix4x4& cameraView, + const AZ::Matrix4x4& cameraProjection, const AZ::Vector2& viewportSize) { - const auto ndcNormalizedPosition = WorldToScreenNDC(worldPosition, cameraView, cameraProjection); + const auto ndcNormalizedPosition = WorldToScreenNdc(worldPosition, cameraView, cameraProjection); // scale ndc position by screen dimensions to return screen position - return ScreenPointFromNDC(ndcNormalizedPosition, viewportSize); + return ScreenPointFromNdc(AZ::Vector3ToVector2(ndcNormalizedPosition), viewportSize); } ScreenPoint WorldToScreen(const AZ::Vector3& worldPosition, const CameraState& cameraState) { - return WorldToScreen( - worldPosition, CameraView(cameraState), CameraProjection(cameraState), cameraState.m_viewportSize); + return WorldToScreen(worldPosition, CameraView(cameraState), CameraProjection(cameraState), cameraState.m_viewportSize); } - AZ::Vector3 ScreenNDCToWorld( - const AZ::Vector2& normalizedScreenPosition, const AZ::Matrix4x4& inverseCameraView, - const AZ::Matrix4x4& inverseCameraProjection) + AZ::Vector3 ScreenNdcToWorld( + const AZ::Vector2& normalizedScreenPosition, const AZ::Matrix4x4& inverseCameraView, const AZ::Matrix4x4& inverseCameraProjection) { // convert screen space coordinates from <0, 1> to <-1,1> range const auto ndcPosition = normalizedScreenPosition * 2.0f - AZ::Vector2::CreateOne(); @@ -142,18 +139,19 @@ namespace AzFramework } AZ::Vector3 ScreenToWorld( - const ScreenPoint& screenPosition, const AZ::Matrix4x4& inverseCameraView, - const AZ::Matrix4x4& inverseCameraProjection, const AZ::Vector2& viewportSize) + const ScreenPoint& screenPosition, + const AZ::Matrix4x4& inverseCameraView, + const AZ::Matrix4x4& inverseCameraProjection, + const AZ::Vector2& viewportSize) { - const auto normalizedScreenPosition = NDCFromScreenPoint(screenPosition, viewportSize); + const auto normalizedScreenPosition = NdcFromScreenPoint(screenPosition, viewportSize); - return ScreenNDCToWorld(normalizedScreenPosition, inverseCameraView, inverseCameraProjection); + return ScreenNdcToWorld(normalizedScreenPosition, inverseCameraView, inverseCameraProjection); } AZ::Vector3 ScreenToWorld(const ScreenPoint& screenPosition, const CameraState& cameraState) { return ScreenToWorld( - screenPosition, InverseCameraView(cameraState), InverseCameraProjection(cameraState), - cameraState.m_viewportSize); + screenPosition, InverseCameraView(cameraState), InverseCameraProjection(cameraState), cameraState.m_viewportSize); } } // namespace AzFramework diff --git a/Code/Framework/AzFramework/AzFramework/Viewport/ViewportScreen.h b/Code/Framework/AzFramework/AzFramework/Viewport/ViewportScreen.h index 87bad66eab..8a6c0249b2 100644 --- a/Code/Framework/AzFramework/AzFramework/Viewport/ViewportScreen.h +++ b/Code/Framework/AzFramework/AzFramework/Viewport/ViewportScreen.h @@ -8,26 +8,42 @@ #pragma once +#include #include +#include +#include namespace AZ { class Frustum; class Matrix4x4; - class Vector3; struct ViewFrustumAttributes; } // namespace AZ namespace AzFramework { struct CameraState; - struct ScreenPoint; struct ViewportInfo; - //! Projects a position in world space to screen space normalized device coordinates for the given camera. - AZ::Vector3 WorldToScreenNDC( - const AZ::Vector3& worldPosition, const AZ::Matrix4x4& cameraView, const AZ::Matrix4x4& cameraProjection); + //! Returns a position in screen space (in the range [0-viewportSize.x, 0-viewportSize.y]) from normalized device + //! coordinates (in the range 0.0-1.0). + inline ScreenPoint ScreenPointFromNdc(const AZ::Vector2& screenNdc, const AZ::Vector2& viewportSize) + { + return ScreenPoint( + aznumeric_cast(AZStd::lround(screenNdc.GetX() * viewportSize.GetX())), + aznumeric_cast(AZStd::lround((1.0f - screenNdc.GetY()) * viewportSize.GetY()))); + } + //! Returns a position in normalized device coordinates (in the range [0.0-1.0, 0.0-1.0]) from a + //! screen space position (in the range [0-viewportSize.x, 0-viewportSize.y]). + inline AZ::Vector2 NdcFromScreenPoint(const ScreenPoint& screenPoint, const AZ::Vector2& viewportSize) + { + return AZ::Vector2(aznumeric_cast(screenPoint.m_x), viewportSize.GetY() - aznumeric_cast(screenPoint.m_y)) / + viewportSize; + } + + //! Projects a position in world space to screen space normalized device coordinates for the given camera. + AZ::Vector3 WorldToScreenNdc(const AZ::Vector3& worldPosition, const AZ::Matrix4x4& cameraView, const AZ::Matrix4x4& cameraProjection); //! Projects a position in world space to screen space for the given camera. ScreenPoint WorldToScreen(const AZ::Vector3& worldPosition, const CameraState& cameraState); @@ -35,7 +51,9 @@ namespace AzFramework //! Overload of WorldToScreen that accepts camera values that can be precomputed if this function //! is called many times in a loop. ScreenPoint WorldToScreen( - const AZ::Vector3& worldPosition, const AZ::Matrix4x4& cameraView, const AZ::Matrix4x4& cameraProjection, + const AZ::Vector3& worldPosition, + const AZ::Matrix4x4& cameraView, + const AZ::Matrix4x4& cameraProjection, const AZ::Vector2& viewportSize); //! Unprojects a position in screen space pixel coordinates to world space. @@ -45,14 +63,15 @@ namespace AzFramework //! Overload of ScreenToWorld that accepts camera values that can be precomputed if this function //! is called many times in a loop. AZ::Vector3 ScreenToWorld( - const ScreenPoint& screenPosition, const AZ::Matrix4x4& inverseCameraView, - const AZ::Matrix4x4& inverseCameraProjection, const AZ::Vector2& viewportSize); + const ScreenPoint& screenPosition, + const AZ::Matrix4x4& inverseCameraView, + const AZ::Matrix4x4& inverseCameraProjection, + const AZ::Vector2& viewportSize); //! Unprojects a position in screen space normalized device coordinates to world space. //! Note: The position returned will be on the near clip plane of the camera in world space. - AZ::Vector3 ScreenNDCToWorld( - const AZ::Vector2& ndcPosition, const AZ::Matrix4x4& inverseCameraView, - const AZ::Matrix4x4& inverseCameraProjection); + AZ::Vector3 ScreenNdcToWorld( + const AZ::Vector2& ndcPosition, const AZ::Matrix4x4& inverseCameraView, const AZ::Matrix4x4& inverseCameraProjection); //! Returns the camera projection for the current camera state. AZ::Matrix4x4 CameraProjection(const CameraState& cameraState); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Manipulators/ScaleManipulators.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Manipulators/ScaleManipulators.cpp index 1867f346ec..d8c43ace9e 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Manipulators/ScaleManipulators.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Manipulators/ScaleManipulators.cpp @@ -71,6 +71,7 @@ namespace AzToolsFramework } m_uniformScaleManipulator->SetVisualOrientationOverride(QuaternionFromTransformNoScaling(localTransform)); + m_uniformScaleManipulator->SetLocalPosition(localTransform.GetTranslation()); m_uniformScaleManipulator->SetLocalOrientation(AZ::Quaternion::CreateIdentity()); } diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Manipulators/TranslationManipulators.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Manipulators/TranslationManipulators.cpp index 049cab4a13..0efe310250 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Manipulators/TranslationManipulators.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Manipulators/TranslationManipulators.cpp @@ -13,14 +13,14 @@ namespace AzToolsFramework { - static const float s_surfaceManipulatorTransparency = 0.75f; - static const float s_axisLength = 2.0f; - static const float s_surfaceManipulatorRadius = 0.1f; + static const float SurfaceManipulatorTransparency = 0.75f; + static const float LinearManipulatorAxisLength = 2.0f; + static const float SurfaceManipulatorRadius = 0.1f; - static const AZ::Color s_xAxisColor = AZ::Color(1.0f, 0.0f, 0.0f, 1.0f); - static const AZ::Color s_yAxisColor = AZ::Color(0.0f, 1.0f, 0.0f, 1.0f); - static const AZ::Color s_zAxisColor = AZ::Color(0.0f, 0.0f, 1.0f, 1.0f); - static const AZ::Color s_surfaceManipulatorColor = AZ::Color(1.0f, 1.0f, 0.0f, 0.5f); + static const AZ::Color LinearManipulatorXAxisColor = AZ::Color(1.0f, 0.0f, 0.0f, 1.0f); + static const AZ::Color LinearManipulatorYAxisColor = AZ::Color(0.0f, 1.0f, 0.0f, 1.0f); + static const AZ::Color LinearManipulatorZAxisColor = AZ::Color(0.0f, 0.0f, 1.0f, 1.0f); + static const AZ::Color SurfaceManipulatorColor = AZ::Color(1.0f, 1.0f, 0.0f, 0.5f); TranslationManipulators::TranslationManipulators( const Dimensions dimensions, const AZ::Transform& worldFromLocal, const AZ::Vector3& nonUniformScale) @@ -291,7 +291,7 @@ namespace AzToolsFramework { const AZ::Color color[2] = { defaultColor, - Vector3ToVector4(BaseManipulator::s_defaultMouseOverColor.GetAsVector3(), s_surfaceManipulatorTransparency) + Vector3ToVector4(BaseManipulator::s_defaultMouseOverColor.GetAsVector3(), SurfaceManipulatorTransparency) }; return color[mouseOver]; @@ -325,15 +325,16 @@ namespace AzToolsFramework void ConfigureTranslationManipulatorAppearance3d(TranslationManipulators* translationManipulators) { translationManipulators->SetAxes(AZ::Vector3::CreateAxisX(), AZ::Vector3::CreateAxisY(), AZ::Vector3::CreateAxisZ()); - translationManipulators->ConfigurePlanarView(s_xAxisColor, s_yAxisColor, s_zAxisColor); - translationManipulators->ConfigureLinearView(s_axisLength, s_xAxisColor, s_yAxisColor, s_zAxisColor); - translationManipulators->ConfigureSurfaceView(s_surfaceManipulatorRadius, s_surfaceManipulatorColor); + translationManipulators->ConfigurePlanarView(LinearManipulatorXAxisColor, LinearManipulatorYAxisColor, LinearManipulatorZAxisColor); + translationManipulators->ConfigureLinearView( + LinearManipulatorAxisLength, LinearManipulatorXAxisColor, LinearManipulatorYAxisColor, LinearManipulatorZAxisColor); + translationManipulators->ConfigureSurfaceView(SurfaceManipulatorRadius, SurfaceManipulatorColor); } void ConfigureTranslationManipulatorAppearance2d(TranslationManipulators* translationManipulators) { translationManipulators->SetAxes(AZ::Vector3::CreateAxisX(), AZ::Vector3::CreateAxisY()); - translationManipulators->ConfigurePlanarView(s_xAxisColor); - translationManipulators->ConfigureLinearView(s_axisLength, s_xAxisColor, s_yAxisColor); + translationManipulators->ConfigurePlanarView(LinearManipulatorXAxisColor); + translationManipulators->ConfigureLinearView(LinearManipulatorAxisLength, LinearManipulatorXAxisColor, LinearManipulatorYAxisColor); } } // namespace AzToolsFramework diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.cpp index 8157d369d2..ccb72e1d50 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.cpp @@ -700,13 +700,6 @@ namespace AzToolsFramework switch (referenceFrame) { case ReferenceFrame::Local: - // if we have a group selection, always use the pivot override if one - // is set when moving to local space (can't pick individual local space) - if (entityIdMap.size() > 1) - { - pivot.m_worldOrientation = pivotOverrideFrame.m_orientationOverride.value(); - } - break; case ReferenceFrame::Parent: pivot.m_worldOrientation = pivotOverrideFrame.m_orientationOverride.value(); break; @@ -784,7 +777,7 @@ namespace AzToolsFramework SortEntitiesByLocationInHierarchy(sortedEntityIdsOut); } - static void UpdateInitialRotation(EntityIdManipulators& entityManipulators) + static void UpdateInitialTransform(EntityIdManipulators& entityManipulators) { // save new start orientation (if moving rotation axes separate from object // or switching type of rotation (modifier keys change)) @@ -797,22 +790,17 @@ namespace AzToolsFramework } } - // utility function to immediately return the current reference frame - // based on the state of the modifiers + // utility function to immediately return the current reference frame based on the state of the modifiers static ReferenceFrame ReferenceFrameFromModifiers(const ViewportInteraction::KeyboardModifiers modifiers) { - if (modifiers.Shift() && !modifiers.Alt()) - { - return ReferenceFrame::World; - } - else if (modifiers.Alt() && !modifiers.Shift()) - { - return ReferenceFrame::Local; - } - else - { - return ReferenceFrame::Parent; - } + return modifiers.Shift() ? ReferenceFrame::World : ReferenceFrame::Local; + } + + // utility function to immediately return the current sphere of influence of the manipulators based on the + // state of the modifiers + static Influence InfluenceFromModifiers(const ViewportInteraction::KeyboardModifiers modifiers) + { + return modifiers.Alt() ? Influence::Individual : Influence::Group; } template @@ -838,6 +826,7 @@ namespace AzToolsFramework else { const ReferenceFrame referenceFrame = spaceLock.value_or(ReferenceFrameFromModifiers(action.m_modifiers)); + const Influence influence = InfluenceFromModifiers(action.m_modifiers); // note: used for parent and world depending on the current reference frame const auto pivotOrientation = @@ -854,9 +843,9 @@ namespace AzToolsFramework const AZ::Vector3 worldTranslation = GetWorldTranslation(entityId); - switch (referenceFrame) + switch (influence) { - case ReferenceFrame::Local: + case Influence::Individual: { // move in each entities local space at once AZ::Quaternion worldOrientation = AZ::Quaternion::CreateIdentity(); @@ -876,10 +865,9 @@ namespace AzToolsFramework entityId, entityItLookupIt->second.m_initial.GetTranslation() + localOffset, transformChangedInternally); } break; - case ReferenceFrame::Parent: - case ReferenceFrame::World: + case Influence::Group: { - AZ::Quaternion offsetRotation = pivotOrientation.m_worldOrientation * + const AZ::Quaternion offsetRotation = pivotOrientation.m_worldOrientation * QuaternionFromTransformNoScaling(entityIdManipulators.m_manipulators->GetLocalTransform().GetInverse()); const AZ::Vector3 localOffset = offsetRotation.TransformVector(action.LocalPositionOffset()); @@ -1062,7 +1050,8 @@ namespace AzToolsFramework { AZStd::chrono::milliseconds timeNow; AzToolsFramework::ViewportInteraction::EditorViewportInputTimeNowRequestBus::BroadcastResult( - timeNow, &AzToolsFramework::ViewportInteraction::EditorViewportInputTimeNowRequestBus::Events::EditorViewportInputTimeNow); + timeNow, + &AzToolsFramework::ViewportInteraction::EditorViewportInputTimeNowRequestBus::Events::EditorViewportInputTimeNow); return timeNow; }); } @@ -1263,7 +1252,7 @@ namespace AzToolsFramework AZStd::unique_ptr translationManipulators = AZStd::make_unique( TranslationManipulators::Dimensions::Three, AZ::Transform::CreateIdentity(), AZ::Vector3::CreateOne()); - translationManipulators->SetLineBoundWidth(ManipulatorLineBoundWidth(ViewportUi::DefaultViewportId)); + translationManipulators->SetLineBoundWidth(ManipulatorLineBoundWidth()); InitializeManipulators(*translationManipulators); @@ -1297,7 +1286,7 @@ namespace AzToolsFramework ViewportInteraction::KeyboardModifiers prevModifiers{}; translationManipulators->InstallLinearManipulatorMouseMoveCallback( - [this, prevModifiers, manipulatorEntityIds](const LinearManipulator::Action& action) mutable -> void + [this, prevModifiers, manipulatorEntityIds](const LinearManipulator::Action& action) mutable { UpdateTranslationManipulator( action, manipulatorEntityIds->m_entityIds, m_entityIdManipulators, m_pivotOverrideFrame, prevModifiers, @@ -1331,7 +1320,7 @@ namespace AzToolsFramework }); translationManipulators->InstallPlanarManipulatorMouseMoveCallback( - [this, prevModifiers, manipulatorEntityIds](const PlanarManipulator::Action& action) mutable -> void + [this, prevModifiers, manipulatorEntityIds](const PlanarManipulator::Action& action) mutable { UpdateTranslationManipulator( action, manipulatorEntityIds->m_entityIds, m_entityIdManipulators, m_pivotOverrideFrame, prevModifiers, @@ -1364,7 +1353,7 @@ namespace AzToolsFramework }); translationManipulators->InstallSurfaceManipulatorMouseMoveCallback( - [this, prevModifiers, manipulatorEntityIds](const SurfaceManipulator::Action& action) mutable -> void + [this, prevModifiers, manipulatorEntityIds](const SurfaceManipulator::Action& action) mutable { UpdateTranslationManipulator( action, manipulatorEntityIds->m_entityIds, m_entityIdManipulators, m_pivotOverrideFrame, prevModifiers, @@ -1391,7 +1380,7 @@ namespace AzToolsFramework AZStd::unique_ptr rotationManipulators = AZStd::make_unique(AZ::Transform::CreateIdentity()); - rotationManipulators->SetCircleBoundWidth(ManipulatorCicleBoundWidth(ViewportUi::DefaultViewportId)); + rotationManipulators->SetCircleBoundWidth(ManipulatorCicleBoundWidth()); InitializeManipulators(*rotationManipulators); @@ -1415,7 +1404,7 @@ namespace AzToolsFramework AZStd::shared_ptr sharedRotationState = AZStd::make_shared(); rotationManipulators->InstallLeftMouseDownCallback( - [this, sharedRotationState]([[maybe_unused]] const AngularManipulator::Action& action) mutable -> void + [this, sharedRotationState]([[maybe_unused]] const AngularManipulator::Action& action) mutable { sharedRotationState->m_savedOrientation = AZ::Quaternion::CreateIdentity(); sharedRotationState->m_referenceFrameAtMouseDown = m_referenceFrame; @@ -1437,11 +1426,13 @@ namespace AzToolsFramework BeginRecordManipulatorCommand(); }); - ViewportInteraction::KeyboardModifiers prevModifiers{}; rotationManipulators->InstallMouseMoveCallback( - [this, prevModifiers, sharedRotationState](const AngularManipulator::Action& action) mutable -> void + [this, prevModifiers = ViewportInteraction::KeyboardModifiers(), + sharedRotationState](const AngularManipulator::Action& action) mutable { const ReferenceFrame referenceFrame = m_spaceCluster.m_spaceLock.value_or(ReferenceFrameFromModifiers(action.m_modifiers)); + const Influence influence = InfluenceFromModifiers(action.m_modifiers); + const AZ::Quaternion manipulatorOrientation = action.m_start.m_rotation * action.m_current.m_delta; // store the pivot override frame when positioning the manipulator manually (ctrl) // so we don't lose the orientation when adding/removing entities from the selection @@ -1452,9 +1443,7 @@ namespace AzToolsFramework // only update the manipulator orientation if we're rotating in a local reference frame or we're // manually modifying the manipulator orientation independent of the entity by holding ctrl - if ((sharedRotationState->m_referenceFrameAtMouseDown == ReferenceFrame::Local && - m_entityIdManipulators.m_lookups.size() == 1) || - action.m_modifiers.Ctrl()) + if (sharedRotationState->m_referenceFrameAtMouseDown == ReferenceFrame::Local || action.m_modifiers.Ctrl()) { m_entityIdManipulators.m_manipulators->SetLocalTransform(AZ::Transform::CreateFromQuaternionAndTranslation( manipulatorOrientation, m_entityIdManipulators.m_manipulators->GetLocalTransform().GetTranslation())); @@ -1463,20 +1452,24 @@ namespace AzToolsFramework // save state if we change the type of rotation we're doing to to prevent snapping if (prevModifiers != action.m_modifiers) { - UpdateInitialRotation(m_entityIdManipulators); + UpdateInitialTransform(m_entityIdManipulators); sharedRotationState->m_savedOrientation = action.m_current.m_delta.GetInverseFull(); } // allow the user to modify the orientation without moving the object if ctrl is held if (action.m_modifiers.Ctrl()) { - UpdateInitialRotation(m_entityIdManipulators); + UpdateInitialTransform(m_entityIdManipulators); sharedRotationState->m_savedOrientation = action.m_current.m_delta.GetInverseFull(); } else { - const auto pivotOrientation = ETCS::CalculateSelectionPivotOrientation( - m_entityIdManipulators.m_lookups, m_pivotOverrideFrame, ReferenceFrame::Parent); + // only update the pivot override if the orientation is being modified in local space and we have + // more than one entity selected (so rotating a single entity does not set the orientation override) + if (referenceFrame == ReferenceFrame::Local && sharedRotationState->m_entityIds.size() > 1) + { + m_pivotOverrideFrame.m_orientationOverride = manipulatorOrientation; + } // note: must use sorted entityIds based on hierarchy order when updating transforms for (AZ::EntityId entityId : sharedRotationState->m_entityIds) @@ -1492,9 +1485,9 @@ namespace AzToolsFramework const AZ::Transform offsetRotation = AZ::Transform::CreateFromQuaternion(sharedRotationState->m_savedOrientation * action.m_current.m_delta); - switch (referenceFrame) + switch (influence) { - case ReferenceFrame::Local: + case Influence::Individual: { const AZ::Quaternion rotation = entityIdLookupIt->second.m_initial.GetRotation().GetNormalized(); const AZ::Vector3 position = entityIdLookupIt->second.m_initial.GetTranslation(); @@ -1510,23 +1503,10 @@ namespace AzToolsFramework AZ::Transform::CreateTranslation(-centerOffset) * AZ::Transform::CreateUniformScale(scale)); } break; - case ReferenceFrame::Parent: + case Influence::Group: { const AZ::Transform pivotTransform = AZ::Transform::CreateFromQuaternionAndTranslation( - pivotOrientation.m_worldOrientation, - m_entityIdManipulators.m_manipulators->GetLocalTransform().GetTranslation()); - - const AZ::Transform transformInPivotSpace = - pivotTransform.GetInverse() * entityIdLookupIt->second.m_initial; - - SetEntityWorldTransform(entityId, pivotTransform * offsetRotation * transformInPivotSpace); - } - break; - case ReferenceFrame::World: - { - const AZ::Transform pivotTransform = AZ::Transform::CreateFromQuaternionAndTranslation( - AZ::Quaternion::CreateIdentity(), - m_entityIdManipulators.m_manipulators->GetLocalTransform().GetTranslation()); + manipulatorOrientation, m_entityIdManipulators.m_manipulators->GetLocalTransform().GetTranslation()); const AZ::Transform transformInPivotSpace = pivotTransform.GetInverse() * entityIdLookupIt->second.m_initial; @@ -1561,7 +1541,7 @@ namespace AzToolsFramework AZ_PROFILE_FUNCTION(AzToolsFramework); AZStd::unique_ptr scaleManipulators = AZStd::make_unique(AZ::Transform::CreateIdentity()); - scaleManipulators->SetLineBoundWidth(ManipulatorLineBoundWidth(ViewportUi::DefaultViewportId)); + scaleManipulators->SetLineBoundWidth(ManipulatorLineBoundWidth()); InitializeManipulators(*scaleManipulators); @@ -1571,13 +1551,20 @@ namespace AzToolsFramework scaleManipulators->SetAxes(AZ::Vector3::CreateAxisX(), AZ::Vector3::CreateAxisY(), AZ::Vector3::CreateAxisZ()); scaleManipulators->ConfigureView(2.0f, AZ::Color::CreateOne(), AZ::Color::CreateOne(), AZ::Color::CreateOne()); - // lambdas capture shared_ptr by value to increment ref count - auto manipulatorEntityIds = AZStd::make_shared(); - - auto uniformLeftMouseDownCallback = [this, manipulatorEntityIds]([[maybe_unused]] const LinearManipulator::Action& action) + struct SharedScaleState { + AZ::Vector3 m_savedScaleOffset = AZ::Vector3::CreateZero(); + EntityIdList m_entityIds; + }; + + // lambdas capture shared_ptr by value to increment ref count + auto sharedScaleState = AZStd::make_shared(); + + auto uniformLeftMouseDownCallback = [this, sharedScaleState]([[maybe_unused]] const LinearManipulator::Action& action) + { + sharedScaleState->m_savedScaleOffset = AZ::Vector3::CreateZero(); // important to sort entityIds based on hierarchy order when updating transforms - BuildSortedEntityIdVectorFromEntityIdMap(m_entityIdManipulators.m_lookups, manipulatorEntityIds->m_entityIds); + BuildSortedEntityIdVectorFromEntityIdMap(m_entityIdManipulators.m_lookups, sharedScaleState->m_entityIds); for (auto& entityIdLookup : m_entityIdManipulators.m_lookups) { @@ -1591,20 +1578,32 @@ namespace AzToolsFramework m_axisPreview.m_orientation = QuaternionFromTransformNoScaling(m_entityIdManipulators.m_manipulators->GetLocalTransform()); }; - auto uniformLeftMouseUpCallback = [this, manipulatorEntityIds]([[maybe_unused]] const LinearManipulator::Action& action) + auto uniformLeftMouseUpCallback = [this, sharedScaleState]([[maybe_unused]] const LinearManipulator::Action& action) { AzToolsFramework::EditorTransformChangeNotificationBus::Broadcast( - &AzToolsFramework::EditorTransformChangeNotificationBus::Events::OnEntityTransformChanged, - manipulatorEntityIds->m_entityIds); + &AzToolsFramework::EditorTransformChangeNotificationBus::Events::OnEntityTransformChanged, sharedScaleState->m_entityIds); m_entityIdManipulators.m_manipulators->SetLocalTransform(RecalculateAverageManipulatorTransform( m_entityIdManipulators.m_lookups, m_pivotOverrideFrame, m_pivotMode, m_referenceFrame)); }; - auto uniformLeftMouseMoveCallback = [this, manipulatorEntityIds](const LinearManipulator::Action& action) + auto uniformLeftMouseMoveCallback = [this, sharedScaleState, prevModifiers = ViewportInteraction::KeyboardModifiers()]( + const LinearManipulator::Action& action) mutable { + // do nothing to modify the manipulator + if (action.m_modifiers.Ctrl()) + { + return; + } + + if (prevModifiers != action.m_modifiers) + { + UpdateInitialTransform(m_entityIdManipulators); + sharedScaleState->m_savedScaleOffset = action.LocalScaleOffset(); + } + // note: must use sorted entityIds based on hierarchy order when updating transforms - for (AZ::EntityId entityId : manipulatorEntityIds->m_entityIds) + for (AZ::EntityId entityId : sharedScaleState->m_entityIds) { auto entityIdLookupIt = m_entityIdManipulators.m_lookups.find(entityId); if (entityIdLookupIt == m_entityIdManipulators.m_lookups.end()) @@ -1620,26 +1619,33 @@ namespace AzToolsFramework return vec.GetX() + vec.GetY() + vec.GetZ(); }; - const float uniformScale = action.m_start.m_sign * sumVectorElements(action.LocalScaleOffset()); + const float uniformScale = + action.m_start.m_sign * sumVectorElements(action.LocalScaleOffset() - sharedScaleState->m_savedScaleOffset); const float scale = AZ::GetClamp(1.0f + uniformScale / initialScale, AZ::MinTransformScale, AZ::MaxTransformScale); const AZ::Transform scaleTransform = AZ::Transform::CreateUniformScale(scale); - if (action.m_modifiers.Alt()) + switch (InfluenceFromModifiers(action.m_modifiers)) { - const AZ::Transform pivotTransform = TransformNormalizedScale(entityIdLookupIt->second.m_initial); - const AZ::Transform transformInPivotSpace = pivotTransform.GetInverse() * initial; + case Influence::Individual: + { + const AZ::Transform pivotTransform = TransformNormalizedScale(entityIdLookupIt->second.m_initial); + const AZ::Transform transformInPivotSpace = pivotTransform.GetInverse() * initial; - SetEntityWorldTransform(entityId, pivotTransform * scaleTransform * transformInPivotSpace); - } - else - { - const AZ::Transform pivotTransform = - TransformNormalizedScale(m_entityIdManipulators.m_manipulators->GetLocalTransform()); - const AZ::Transform transformInPivotSpace = pivotTransform.GetInverse() * initial; + SetEntityWorldTransform(entityId, pivotTransform * scaleTransform * transformInPivotSpace); + } + break; + case Influence::Group: + { + const AZ::Transform pivotTransform = + TransformNormalizedScale(m_entityIdManipulators.m_manipulators->GetLocalTransform()); + const AZ::Transform transformInPivotSpace = pivotTransform.GetInverse() * initial; - SetEntityWorldTransform(entityId, pivotTransform * scaleTransform * transformInPivotSpace); + SetEntityWorldTransform(entityId, pivotTransform * scaleTransform * transformInPivotSpace); + } } } + + prevModifiers = action.m_modifiers; }; scaleManipulators->InstallAxisLeftMouseDownCallback(uniformLeftMouseDownCallback); @@ -2729,8 +2735,7 @@ namespace AzToolsFramework if (m_pivotOverrideFrame.m_orientationOverride && m_entityIdManipulators.m_manipulators) { - m_pivotOverrideFrame.m_orientationOverride = - QuaternionFromTransformNoScaling(m_entityIdManipulators.m_manipulators->GetLocalTransform()); + m_pivotOverrideFrame.m_orientationOverride = m_entityIdManipulators.m_manipulators->GetLocalTransform().GetRotation(); } if (m_pivotOverrideFrame.m_translationOverride && m_entityIdManipulators.m_manipulators) @@ -3337,7 +3342,7 @@ namespace AzToolsFramework display.SetLineWidth(4.0f); - const auto axisFlip = [&transform, &cameraState](const AZ::Vector3& axis) -> float + const auto axisFlip = [&transform, &cameraState](const AZ::Vector3& axis) { return ShouldFlipCameraAxis( AZ::Transform::CreateIdentity(), transform.GetTranslation(), TransformDirectionNoScaling(transform, axis), @@ -3554,7 +3559,7 @@ namespace AzToolsFramework // screen space const auto calculateGizmoAxis = [&cameraView, &cameraProjection, &screenOffset](const AZ::Vector3& axis) { - auto result = AZ::Vector2(AzFramework::WorldToScreenNDC(axis, cameraView, cameraProjection)); + auto result = AZ::Vector2(AzFramework::WorldToScreenNdc(axis, cameraView, cameraProjection)); result.SetY(1.0f - result.GetY()); return result + screenOffset; }; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.h b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.h index 6cc89b2d10..a0e87d9d6f 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.h @@ -96,6 +96,14 @@ namespace AzToolsFramework AZ::u8 m_pickTypes = PickType::None; //!< What mode(s) were we in when picking an EntityId override. }; + //! How a manipulator should treat an adjustment. + //! @note Determines if a transform is applied to an individual entity or the whole group. + enum class Influence + { + Group, + Individual + }; + //! What frame/space is the manipulator currently operating in. enum class ReferenceFrame { @@ -328,7 +336,8 @@ namespace AzToolsFramework OptionalFrame m_pivotOverrideFrame; //!< Has a pivot override been set. Mode m_mode = Mode::Translation; //!< Manipulator mode - default to translation. Pivot m_pivotMode = Pivot::Object; //!< Entity pivot mode - default to object (authored root). - ReferenceFrame m_referenceFrame = ReferenceFrame::Parent; //!< What reference frame is the Manipulator currently operating in. + ReferenceFrame m_referenceFrame = ReferenceFrame::Local; //!< What reference frame is the Manipulator currently operating in. + Influence m_influence = Influence::Group; //!< What sphere of influence does the Manipulator have. Frame m_axisPreview; //!< Axes of entity at the time of mouse down to indicate delta of translation. bool m_triedToRefresh = false; //!< Did a refresh event occur to recalculate the current Manipulator transform. //! Was EditorTransformComponentSelection responsible for the most recent entity selection change. diff --git a/Code/Framework/AzToolsFramework/Tests/EditorTransformComponentSelectionTests.cpp b/Code/Framework/AzToolsFramework/Tests/EditorTransformComponentSelectionTests.cpp index 449c4549be..52470f41e3 100644 --- a/Code/Framework/AzToolsFramework/Tests/EditorTransformComponentSelectionTests.cpp +++ b/Code/Framework/AzToolsFramework/Tests/EditorTransformComponentSelectionTests.cpp @@ -242,11 +242,11 @@ namespace UnitTest { // the initial starting position of the entities AZ::TransformBus::Event( - m_entityId1, &AZ::TransformBus::Events::SetWorldTM, AZ::Transform::CreateTranslation(m_entity1WorldTranslation)); + m_entityId1, &AZ::TransformBus::Events::SetWorldTM, AZ::Transform::CreateTranslation(Entity1WorldTranslation)); AZ::TransformBus::Event( - m_entityId2, &AZ::TransformBus::Events::SetWorldTM, AZ::Transform::CreateTranslation(m_entity2WorldTranslation)); + m_entityId2, &AZ::TransformBus::Events::SetWorldTM, AZ::Transform::CreateTranslation(Entity2WorldTranslation)); AZ::TransformBus::Event( - m_entityId3, &AZ::TransformBus::Events::SetWorldTM, AZ::Transform::CreateTranslation(m_entity3WorldTranslation)); + m_entityId3, &AZ::TransformBus::Events::SetWorldTM, AZ::Transform::CreateTranslation(Entity3WorldTranslation)); } static void PositionCamera(AzFramework::CameraState& cameraState) @@ -261,9 +261,10 @@ namespace UnitTest AZ::EntityId m_entityId1; AZ::EntityId m_entityId2; AZ::EntityId m_entityId3; - AZ::Vector3 m_entity1WorldTranslation = AZ::Vector3(5.0f, 15.0f, 10.0f); - AZ::Vector3 m_entity2WorldTranslation = AZ::Vector3(5.0f, 14.0f, 10.0f); - AZ::Vector3 m_entity3WorldTranslation = AZ::Vector3(5.0f, 16.0f, 10.0f); + + static inline const AZ::Vector3 Entity1WorldTranslation = AZ::Vector3(5.0f, 15.0f, 10.0f); + static inline const AZ::Vector3 Entity2WorldTranslation = AZ::Vector3(5.0f, 14.0f, 10.0f); + static inline const AZ::Vector3 Entity3WorldTranslation = AZ::Vector3(5.0f, 16.0f, 10.0f); }; void ArrangeIndividualRotatedEntitySelection(const AzToolsFramework::EntityIdList& entityIds, const AZ::Quaternion& orientation) @@ -371,16 +372,16 @@ namespace UnitTest // Given AzToolsFramework::SelectEntity(m_entityId1); - ArrangeIndividualRotatedEntitySelection(m_entityIds, AZ::Quaternion::CreateRotationX(AZ::DegToRad(90.0f))); + const auto entityTransform = AZ::Transform::CreateFromQuaternion(AZ::Quaternion::CreateRotationX(AZ::DegToRad(90.0f))); + ArrangeIndividualRotatedEntitySelection(m_entityIds, entityTransform.GetRotation()); RefreshManipulators(EditorTransformComponentSelectionRequestBus::Events::RefreshType::All); SetTransformMode(EditorTransformComponentSelectionRequestBus::Events::Mode::Rotation); const AZ::Transform manipulatorTransformBefore = GetManipulatorTransform().value_or(AZ::Transform::CreateIdentity()); - // check preconditions - manipulator transform matches parent/world transform (identity) - EXPECT_THAT(manipulatorTransformBefore.GetBasisY(), IsClose(AZ::Vector3::CreateAxisY())); - EXPECT_THAT(manipulatorTransformBefore.GetBasisZ(), IsClose(AZ::Vector3::CreateAxisZ())); + // check preconditions - manipulator transform matches the entity transform + EXPECT_THAT(manipulatorTransformBefore, IsClose(entityTransform)); /////////////////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -624,7 +625,7 @@ namespace UnitTest EXPECT_TRUE(selectedEntitiesBefore.empty()); // calculate the position in screen space of the initial entity position - const auto entity1ScreenPosition = AzFramework::WorldToScreen(m_entity1WorldTranslation, m_cameraState); + const auto entity1ScreenPosition = AzFramework::WorldToScreen(Entity1WorldTranslation, m_cameraState); // click the entity in the viewport m_actionDispatcher->SetStickySelect(true) @@ -649,7 +650,7 @@ namespace UnitTest EXPECT_TRUE(selectedEntitiesBefore.empty()); // calculate the position in screen space of the initial entity position - const auto entity1ScreenPosition = AzFramework::WorldToScreen(m_entity1WorldTranslation, m_cameraState); + const auto entity1ScreenPosition = AzFramework::WorldToScreen(Entity1WorldTranslation, m_cameraState); // click the entity in the viewport m_actionDispatcher->SetStickySelect(false) @@ -728,7 +729,7 @@ namespace UnitTest AzToolsFramework::SelectEntity(m_entityId1); // calculate the position in screen space of the second entity - const auto entity2ScreenPosition = AzFramework::WorldToScreen(m_entity2WorldTranslation, m_cameraState); + const auto entity2ScreenPosition = AzFramework::WorldToScreen(Entity2WorldTranslation, m_cameraState); // click the entity in the viewport m_actionDispatcher->SetStickySelect(true) @@ -754,7 +755,7 @@ namespace UnitTest AzToolsFramework::SelectEntity(m_entityId1); // calculate the position in screen space of the second entity - const auto entity2ScreenPosition = AzFramework::WorldToScreen(m_entity2WorldTranslation, m_cameraState); + const auto entity2ScreenPosition = AzFramework::WorldToScreen(Entity2WorldTranslation, m_cameraState); // click the entity in the viewport m_actionDispatcher->SetStickySelect(false) @@ -780,7 +781,7 @@ namespace UnitTest AzToolsFramework::SelectEntity(m_entityId1); // calculate the position in screen space of the second entity - const auto entity2ScreenPosition = AzFramework::WorldToScreen(m_entity2WorldTranslation, m_cameraState); + const auto entity2ScreenPosition = AzFramework::WorldToScreen(Entity2WorldTranslation, m_cameraState); // click the entity in the viewport m_actionDispatcher->SetStickySelect(true) @@ -806,7 +807,7 @@ namespace UnitTest AzToolsFramework::SelectEntity(m_entityId1); // calculate the position in screen space of the second entity - const auto entity2ScreenPosition = AzFramework::WorldToScreen(m_entity2WorldTranslation, m_cameraState); + const auto entity2ScreenPosition = AzFramework::WorldToScreen(Entity2WorldTranslation, m_cameraState); // click the entity in the viewport m_actionDispatcher->SetStickySelect(false) @@ -832,7 +833,7 @@ namespace UnitTest AzToolsFramework::SelectEntities({ m_entityId1, m_entityId2 }); // calculate the position in screen space of the second entity - const auto entity2ScreenPosition = AzFramework::WorldToScreen(m_entity2WorldTranslation, m_cameraState); + const auto entity2ScreenPosition = AzFramework::WorldToScreen(Entity2WorldTranslation, m_cameraState); // click the entity in the viewport m_actionDispatcher->SetStickySelect(true) @@ -858,7 +859,7 @@ namespace UnitTest AzToolsFramework::SelectEntities({ m_entityId1, m_entityId2 }); // calculate the position in screen space of the second entity - const auto entity2ScreenPosition = AzFramework::WorldToScreen(m_entity2WorldTranslation, m_cameraState); + const auto entity2ScreenPosition = AzFramework::WorldToScreen(Entity2WorldTranslation, m_cameraState); // click the entity in the viewport m_actionDispatcher->SetStickySelect(false) @@ -1001,7 +1002,7 @@ namespace UnitTest AzToolsFramework::SelectEntity(m_entityId1); // calculate the position in screen space of the second entity - const auto entity2ScreenPosition = AzFramework::WorldToScreen(m_entity2WorldTranslation, m_cameraState); + const auto entity2ScreenPosition = AzFramework::WorldToScreen(Entity2WorldTranslation, m_cameraState); // single click select entity2 m_actionDispatcher->SetStickySelect(false) @@ -1035,7 +1036,7 @@ namespace UnitTest AzToolsFramework::SelectEntity(m_entityId1); // calculate the position in screen space of the second entity - const auto entity2ScreenPosition = AzFramework::WorldToScreen(m_entity2WorldTranslation, m_cameraState); + const auto entity2ScreenPosition = AzFramework::WorldToScreen(Entity2WorldTranslation, m_cameraState); // single click select entity2 m_actionDispatcher->SetStickySelect(GetParam()) @@ -1056,7 +1057,7 @@ namespace UnitTest manipulatorTransform, AzToolsFramework::GetEntityContextId(), &AzToolsFramework::EditorTransformComponentSelectionRequestBus::Events::GetManipulatorTransform); - EXPECT_THAT(manipulatorTransform->GetTranslation(), IsClose(m_entity2WorldTranslation)); + EXPECT_THAT(manipulatorTransform->GetTranslation(), IsClose(Entity2WorldTranslation)); } TEST_P( @@ -1069,7 +1070,7 @@ namespace UnitTest AzToolsFramework::SelectEntity(m_entityId1); // calculate the position in screen space of the second entity - const auto entity2ScreenPosition = AzFramework::WorldToScreen(m_entity2WorldTranslation, m_cameraState); + const auto entity2ScreenPosition = AzFramework::WorldToScreen(Entity2WorldTranslation, m_cameraState); // position in space above the entities const auto clickOffPositionWorld = AZ::Vector3(5.0f, 15.0f, 12.0f); @@ -1096,7 +1097,7 @@ namespace UnitTest manipulatorTransform, AzToolsFramework::GetEntityContextId(), &AzToolsFramework::EditorTransformComponentSelectionRequestBus::Events::GetManipulatorTransform); - EXPECT_THAT(manipulatorTransform->GetTranslation(), IsClose(m_entity2WorldTranslation)); + EXPECT_THAT(manipulatorTransform->GetTranslation(), IsClose(Entity2WorldTranslation)); }) ->MousePosition(clickOffPositionScreen) ->KeyboardModifierDown(AzToolsFramework::ViewportInteraction::KeyboardModifier::Control) @@ -1113,11 +1114,560 @@ namespace UnitTest &AzToolsFramework::EditorTransformComponentSelectionRequestBus::Events::GetManipulatorTransform); // manipulator transform is reset - EXPECT_THAT(manipulatorTransform->GetTranslation(), IsClose(m_entity1WorldTranslation)); + EXPECT_THAT(manipulatorTransform->GetTranslation(), IsClose(Entity1WorldTranslation)); } INSTANTIATE_TEST_CASE_P(All, EditorTransformComponentSelectionViewportPickingManipulatorTestFixtureParam, testing::Values(true, false)); + // create alias for EditorTransformComponentSelectionViewportPickingManipulatorTestFixture to help group tests + using EditorTransformComponentSelectionManipulatorInteractionTestFixture = + EditorTransformComponentSelectionViewportPickingManipulatorTestFixture; + + // type to group related inputs and outcomes for parameterized tests (single entity) + struct ManipulatorOptionsSingle + { + AzToolsFramework::ViewportInteraction::KeyboardModifier m_keyboardModifier; + AZ::Transform m_expectedManipulatorTransformAfter; + AZ::Transform m_expectedEntityTransformAfter; + }; + + class EditorTransformComponentSelectionRotationManipulatorSingleEntityTestFixtureParam + : public EditorTransformComponentSelectionManipulatorInteractionTestFixture + , public ::testing::WithParamInterface + { + }; + + TEST_P( + EditorTransformComponentSelectionRotationManipulatorSingleEntityTestFixtureParam, + RotatingASingleEntityWithDifferentModifierCombinations) + { + using AzToolsFramework::EditorTransformComponentSelectionRequestBus; + + PositionEntities(); + PositionCamera(m_cameraState); + + SetTransformMode(EditorTransformComponentSelectionRequestBus::Events::Mode::Rotation); + + AzToolsFramework::SelectEntity(m_entityId1); + + const float screenToWorldMultiplier = AzToolsFramework::CalculateScreenToWorldMultiplier(Entity1WorldTranslation, m_cameraState); + const float manipulatorRadius = 2.0f * screenToWorldMultiplier; + + const auto rotationManipulatorStartHoldWorldPosition = Entity1WorldTranslation + + AZ::Quaternion::CreateRotationX(AZ::DegToRad(-45.0f)).TransformVector(AZ::Vector3::CreateAxisY(-manipulatorRadius)); + const auto rotationManipulatorEndHoldWorldPosition = Entity1WorldTranslation + + AZ::Quaternion::CreateRotationX(AZ::DegToRad(-135.0f)).TransformVector(AZ::Vector3::CreateAxisY(-manipulatorRadius)); + + // calculate screen space positions + const auto rotationManipulatorHoldScreenPosition = + AzFramework::WorldToScreen(rotationManipulatorStartHoldWorldPosition, m_cameraState); + const auto rotationManipulatorEndHoldScreenPosition = + AzFramework::WorldToScreen(rotationManipulatorEndHoldWorldPosition, m_cameraState); + + m_actionDispatcher->CameraState(m_cameraState) + ->MousePosition(rotationManipulatorHoldScreenPosition) + ->KeyboardModifierDown(GetParam().m_keyboardModifier) + ->MouseLButtonDown() + ->MousePosition(rotationManipulatorEndHoldScreenPosition) + ->MouseLButtonUp(); + + const auto expectedEntityTransform = GetParam().m_expectedEntityTransformAfter; + const auto expectedManipulatorTransform = GetParam().m_expectedManipulatorTransformAfter; + + const auto manipulatorTransform = GetManipulatorTransform(); + const auto entityTransform = AzToolsFramework::GetWorldTransform(m_entityId1); + + EXPECT_THAT(*manipulatorTransform, IsClose(expectedManipulatorTransform)); + EXPECT_THAT(entityTransform, IsClose(expectedEntityTransform)); + } + + static const AZ::Transform ExpectedTransformAfterLocalRotationManipulatorMotion = AZ::Transform::CreateFromQuaternionAndTranslation( + AZ::Quaternion::CreateRotationX(AZ::DegToRad(-90.0f)), + EditorTransformComponentSelectionViewportPickingFixture::Entity1WorldTranslation); + + INSTANTIATE_TEST_CASE_P( + All, + EditorTransformComponentSelectionRotationManipulatorSingleEntityTestFixtureParam, + testing::Values( + // this replicates rotating an entity in local space with no modifiers held + // manipulator and entity rotate + ManipulatorOptionsSingle{ AzToolsFramework::ViewportInteraction::KeyboardModifier::None, + ExpectedTransformAfterLocalRotationManipulatorMotion, + ExpectedTransformAfterLocalRotationManipulatorMotion }, + // this replicates rotating an entity in local space with the alt modifier held + // manipulator and entity rotate + ManipulatorOptionsSingle{ AzToolsFramework::ViewportInteraction::KeyboardModifier::Alt, + ExpectedTransformAfterLocalRotationManipulatorMotion, + ExpectedTransformAfterLocalRotationManipulatorMotion }, + // this replicates rotating an entity in world space with the shift modifier held + // entity rotates, manipulator remains aligned to world + ManipulatorOptionsSingle{ + AzToolsFramework::ViewportInteraction::KeyboardModifier::Shift, + AZ::Transform::CreateTranslation(EditorTransformComponentSelectionViewportPickingFixture::Entity1WorldTranslation), + ExpectedTransformAfterLocalRotationManipulatorMotion }, + // this replicates rotating the manipulator in local space with the ctrl modifier held (entity is unchanged) + ManipulatorOptionsSingle{ + AzToolsFramework::ViewportInteraction::KeyboardModifier::Ctrl, ExpectedTransformAfterLocalRotationManipulatorMotion, + AZ::Transform::CreateTranslation(EditorTransformComponentSelectionViewportPickingFixture::Entity1WorldTranslation) })); + + // type to group related inputs and outcomes for parameterized tests (two entities) + struct ManipulatorOptionsMultiple + { + AzToolsFramework::ViewportInteraction::KeyboardModifier m_keyboardModifier; + AZ::Transform m_expectedManipulatorTransformAfter; + AZ::Transform m_firstExpectedEntityTransformAfter; + AZ::Transform m_secondExpectedEntityTransformAfter; + }; + + class EditorTransformComponentSelectionRotationManipulatorMultipleEntityTestFixtureParam + : public EditorTransformComponentSelectionManipulatorInteractionTestFixture + , public ::testing::WithParamInterface + { + }; + + TEST_P( + EditorTransformComponentSelectionRotationManipulatorMultipleEntityTestFixtureParam, + RotatingMultipleEntitiesWithDifferentModifierCombinations) + { + using AzToolsFramework::EditorTransformComponentSelectionRequestBus; + + PositionEntities(); + PositionCamera(m_cameraState); + + SetTransformMode(EditorTransformComponentSelectionRequestBus::Events::Mode::Rotation); + + AzToolsFramework::SelectEntities({ m_entityId2, m_entityId3 }); + + // manipulator should be centered between the two entities + const auto initialManipulatorTransform = GetManipulatorTransform(); + + const float screenToWorldMultiplier = + AzToolsFramework::CalculateScreenToWorldMultiplier(initialManipulatorTransform->GetTranslation(), m_cameraState); + const float manipulatorRadius = 2.0f * screenToWorldMultiplier; + + const auto rotationManipulatorStartHoldWorldPosition = initialManipulatorTransform->GetTranslation() + + AZ::Quaternion::CreateRotationX(AZ::DegToRad(-45.0f)).TransformVector(AZ::Vector3::CreateAxisY(-manipulatorRadius)); + const auto rotationManipulatorEndHoldWorldPosition = initialManipulatorTransform->GetTranslation() + + AZ::Quaternion::CreateRotationX(AZ::DegToRad(-135.0f)).TransformVector(AZ::Vector3::CreateAxisY(-manipulatorRadius)); + + // calculate screen space positions + const auto rotationManipulatorHoldScreenPosition = + AzFramework::WorldToScreen(rotationManipulatorStartHoldWorldPosition, m_cameraState); + const auto rotationManipulatorEndHoldScreenPosition = + AzFramework::WorldToScreen(rotationManipulatorEndHoldWorldPosition, m_cameraState); + + m_actionDispatcher->CameraState(m_cameraState) + ->MousePosition(rotationManipulatorHoldScreenPosition) + ->KeyboardModifierDown(GetParam().m_keyboardModifier) + ->MouseLButtonDown() + ->MousePosition(rotationManipulatorEndHoldScreenPosition) + ->MouseLButtonUp(); + + const auto expectedEntity2Transform = GetParam().m_firstExpectedEntityTransformAfter; + const auto expectedEntity3Transform = GetParam().m_secondExpectedEntityTransformAfter; + const auto expectedManipulatorTransform = GetParam().m_expectedManipulatorTransformAfter; + + const auto manipulatorTransformAfter = GetManipulatorTransform(); + const auto entity2Transform = AzToolsFramework::GetWorldTransform(m_entityId2); + const auto entity3Transform = AzToolsFramework::GetWorldTransform(m_entityId3); + + EXPECT_THAT(*manipulatorTransformAfter, IsClose(expectedManipulatorTransform)); + EXPECT_THAT(entity2Transform, IsClose(expectedEntity2Transform)); + EXPECT_THAT(entity3Transform, IsClose(expectedEntity3Transform)); + } + + // note: The aggregate manipulator position will be the average of entity 2 and 3 combined which + // winds up being the same as entity 1 + static const AZ::Vector3 AggregateManipulatorPositionWithEntity2and3Selected = + EditorTransformComponentSelectionViewportPickingFixture::Entity1WorldTranslation; + + static const AZ::Transform ExpectedEntity2TransformAfterLocalGroupRotationManipulatorMotion = + AZ::Transform::CreateTranslation(AggregateManipulatorPositionWithEntity2and3Selected) * + AZ::Transform::CreateFromQuaternion(AZ::Quaternion::CreateRotationX(AZ::DegToRad(-90.0f))) * + AZ::Transform::CreateTranslation(AZ::Vector3::CreateAxisY(-1.0f)); + static const AZ::Transform ExpectedEntity3TransformAfterLocalGroupRotationManipulatorMotion = + AZ::Transform::CreateTranslation(AggregateManipulatorPositionWithEntity2and3Selected) * + AZ::Transform::CreateFromQuaternion(AZ::Quaternion::CreateRotationX(AZ::DegToRad(-90.0f))) * + AZ::Transform::CreateTranslation(AZ::Vector3::CreateAxisY(1.0f)); + static const AZ::Transform ExpectedEntity2TransformAfterLocalIndividualRotationManipulatorMotion = + AZ::Transform::CreateTranslation(EditorTransformComponentSelectionViewportPickingFixture::Entity2WorldTranslation) * + AZ::Transform::CreateFromQuaternion(AZ::Quaternion::CreateRotationX(AZ::DegToRad(-90.0f))); + static const AZ::Transform ExpectedEntity3TransformAfterLocalIndividualRotationManipulatorMotion = + AZ::Transform::CreateTranslation(EditorTransformComponentSelectionViewportPickingFixture::Entity3WorldTranslation) * + AZ::Transform::CreateFromQuaternion(AZ::Quaternion::CreateRotationX(AZ::DegToRad(-90.0f))); + + INSTANTIATE_TEST_CASE_P( + All, + EditorTransformComponentSelectionRotationManipulatorMultipleEntityTestFixtureParam, + testing::Values( + // this replicates rotating a group of entities in local space with no modifiers held + // manipulator and entity rotate + ManipulatorOptionsMultiple{ AzToolsFramework::ViewportInteraction::KeyboardModifier::None, + ExpectedTransformAfterLocalRotationManipulatorMotion, + ExpectedEntity2TransformAfterLocalGroupRotationManipulatorMotion, + ExpectedEntity3TransformAfterLocalGroupRotationManipulatorMotion }, + // this replicates rotating a group of entities in local space with the alt modifier held + // manipulator and entity rotate + ManipulatorOptionsMultiple{ AzToolsFramework::ViewportInteraction::KeyboardModifier::Alt, + ExpectedTransformAfterLocalRotationManipulatorMotion, + ExpectedEntity2TransformAfterLocalIndividualRotationManipulatorMotion, + ExpectedEntity3TransformAfterLocalIndividualRotationManipulatorMotion }, + // this replicates rotating a group of entities in world space with the shift modifier held + // entity rotates, manipulator remains aligned to world + ManipulatorOptionsMultiple{ + AzToolsFramework::ViewportInteraction::KeyboardModifier::Shift, + AZ::Transform::CreateTranslation(EditorTransformComponentSelectionViewportPickingFixture::Entity1WorldTranslation), + ExpectedEntity2TransformAfterLocalGroupRotationManipulatorMotion, + ExpectedEntity3TransformAfterLocalGroupRotationManipulatorMotion }, + // this replicates rotating the manipulator in local space with the ctrl modifier held (entity is unchanged) + ManipulatorOptionsMultiple{ + AzToolsFramework::ViewportInteraction::KeyboardModifier::Ctrl, ExpectedTransformAfterLocalRotationManipulatorMotion, + AZ::Transform::CreateTranslation(EditorTransformComponentSelectionViewportPickingFixture::Entity2WorldTranslation), + AZ::Transform::CreateTranslation(EditorTransformComponentSelectionViewportPickingFixture::Entity3WorldTranslation) })); + + class EditorTransformComponentSelectionTranslationManipulatorSingleEntityTestFixtureParam + : public EditorTransformComponentSelectionManipulatorInteractionTestFixture + , public ::testing::WithParamInterface + { + }; + + static const float LinearManipulatorYAxisMovement = -3.0f; + static const float LinearManipulatorZAxisMovement = 2.0f; + + TEST_P( + EditorTransformComponentSelectionTranslationManipulatorSingleEntityTestFixtureParam, + TranslatingASingleEntityWithDifferentModifierCombinations) + { + using AzToolsFramework::EditorTransformComponentSelectionRequestBus; + + PositionEntities(); + + // move camera up and to the left so it's just above the normal row of entities + AzFramework::SetCameraTransform( + m_cameraState, + AZ::Transform::CreateFromQuaternionAndTranslation( + AZ::Quaternion::CreateFromEulerAnglesDegrees(AZ::Vector3(0.0f, 0.0f, 90.0f)), AZ::Vector3(10.0f, 14.5, 11.0f))); + + SetTransformMode(EditorTransformComponentSelectionRequestBus::Events::Mode::Translation); + + AzToolsFramework::SelectEntity(m_entityId1); + const auto entity1Transform = AzToolsFramework::GetWorldTransform(m_entityId1); + + const float screenToWorldMultiplier = AzToolsFramework::CalculateScreenToWorldMultiplier( + AzToolsFramework::GetWorldTransform(m_entityId1).GetTranslation(), m_cameraState); + + // calculate positions for two click and drag motions (moving a linear manipulator) + // begin each click in the center of the line of the linear manipulators + const auto translationManipulatorStartHoldWorldPosition1 = + AzToolsFramework::GetWorldTransform(m_entityId1).GetTranslation() + entity1Transform.GetBasisZ() * screenToWorldMultiplier; + const auto translationManipulatorEndHoldWorldPosition1 = + translationManipulatorStartHoldWorldPosition1 + AZ::Vector3::CreateAxisZ(LinearManipulatorZAxisMovement); + const auto translationManipulatorStartHoldWorldPosition2 = AzToolsFramework::GetWorldTransform(m_entityId1).GetTranslation() + + AZ::Vector3::CreateAxisZ(LinearManipulatorZAxisMovement) - entity1Transform.GetBasisY() * screenToWorldMultiplier; + const auto translationManipulatorEndHoldWorldPosition2 = + translationManipulatorStartHoldWorldPosition2 + AZ::Vector3::CreateAxisY(LinearManipulatorYAxisMovement); + + // transform to screen space + const auto translationManipulatorStartHoldScreenPosition1 = + AzFramework::WorldToScreen(translationManipulatorStartHoldWorldPosition1, m_cameraState); + const auto translationManipulatorEndHoldScreenPosition1 = + AzFramework::WorldToScreen(translationManipulatorEndHoldWorldPosition1, m_cameraState); + const auto translationManipulatorStartHoldScreenPosition2 = + AzFramework::WorldToScreen(translationManipulatorStartHoldWorldPosition2, m_cameraState); + const auto translationManipulatorEndHoldScreenPosition2 = + AzFramework::WorldToScreen(translationManipulatorEndHoldWorldPosition2, m_cameraState); + + m_actionDispatcher->CameraState(m_cameraState) + ->MousePosition(translationManipulatorStartHoldScreenPosition1) + ->KeyboardModifierDown(GetParam().m_keyboardModifier) + ->MouseLButtonDown() + ->MousePosition(translationManipulatorEndHoldScreenPosition1) + ->MouseLButtonUp() + ->MousePosition(translationManipulatorStartHoldScreenPosition2) + ->MouseLButtonDown() + ->MousePosition(translationManipulatorEndHoldScreenPosition2) + ->MouseLButtonUp(); + + const auto expectedEntityTransform = GetParam().m_expectedEntityTransformAfter; + const auto expectedManipulatorTransform = GetParam().m_expectedManipulatorTransformAfter; + + const auto manipulatorTransform = GetManipulatorTransform(); + const auto entityTransform = AzToolsFramework::GetWorldTransform(m_entityId1); + + EXPECT_THAT(*manipulatorTransform, IsCloseTolerance(expectedManipulatorTransform, 0.01f)); + EXPECT_THAT(entityTransform, IsCloseTolerance(expectedEntityTransform, 0.01f)); + } + + static const AZ::Transform ExpectedTransformAfterLocalTranslationManipulatorMotion = AZ::Transform::CreateTranslation( + EditorTransformComponentSelectionViewportPickingFixture::Entity1WorldTranslation + + AZ::Vector3(0.0f, LinearManipulatorYAxisMovement, LinearManipulatorZAxisMovement)); + + // where the manipulator should end up after the input from TranslatingMultipleEntitiesWithDifferentModifierCombinations + static const AZ::Transform ExpectedManipulatorTransformAfterGroupTranslationManipulatorMotion = AZ::Transform::CreateTranslation( + AggregateManipulatorPositionWithEntity2and3Selected + + AZ::Vector3(0.0f, LinearManipulatorYAxisMovement, LinearManipulatorZAxisMovement)); + + INSTANTIATE_TEST_CASE_P( + All, + EditorTransformComponentSelectionTranslationManipulatorSingleEntityTestFixtureParam, + testing::Values( + // this replicates translating an entity in local space with no modifiers held + // manipulator and entity translate + ManipulatorOptionsSingle{ AzToolsFramework::ViewportInteraction::KeyboardModifier::None, + ExpectedTransformAfterLocalTranslationManipulatorMotion, + ExpectedTransformAfterLocalTranslationManipulatorMotion }, + // this replicates translating an entity in local space with the alt modifier held + // manipulator and entity translate (to the user, equivalent to no modifiers with one entity selected) + ManipulatorOptionsSingle{ AzToolsFramework::ViewportInteraction::KeyboardModifier::Alt, + ExpectedTransformAfterLocalTranslationManipulatorMotion, + ExpectedTransformAfterLocalTranslationManipulatorMotion }, + // this replicates translating an entity in world space with the shift modifier held + // manipulator and entity translate + ManipulatorOptionsSingle{ AzToolsFramework::ViewportInteraction::KeyboardModifier::Shift, + ExpectedTransformAfterLocalTranslationManipulatorMotion, + ExpectedTransformAfterLocalTranslationManipulatorMotion }, + // this replicates translating the manipulator in local space with the ctrl modifier held + // entity is unchanged, manipulator moves + ManipulatorOptionsSingle{ + AzToolsFramework::ViewportInteraction::KeyboardModifier::Ctrl, ExpectedTransformAfterLocalTranslationManipulatorMotion, + AZ::Transform::CreateTranslation(EditorTransformComponentSelectionViewportPickingFixture::Entity1WorldTranslation) })); + + class EditorTransformComponentSelectionTranslationManipulatorMultipleEntityTestFixtureParam + : public EditorTransformComponentSelectionManipulatorInteractionTestFixture + , public ::testing::WithParamInterface + { + }; + + static const AZ::Transform Entity2RotationForLocalTranslation = + AZ::Transform::CreateFromQuaternion(AZ::Quaternion::CreateRotationZ(AZ::DegToRad(90.0f))); + + TEST_P( + EditorTransformComponentSelectionTranslationManipulatorMultipleEntityTestFixtureParam, + TranslatingMultipleEntitiesWithDifferentModifierCombinations) + { + using AzToolsFramework::EditorTransformComponentSelectionRequestBus; + + PositionEntities(); + + // move camera up and to the left so it's just above the normal row of entities + AzFramework::SetCameraTransform( + m_cameraState, + AZ::Transform::CreateFromQuaternionAndTranslation( + AZ::Quaternion::CreateFromEulerAnglesDegrees(AZ::Vector3(0.0f, 0.0f, 90.0f)), AZ::Vector3(10.0f, 14.5, 11.0f))); + + SetTransformMode(EditorTransformComponentSelectionRequestBus::Events::Mode::Translation); + + // give entity 2 a different orientation to entity 3 so when moving in local space their translation vectors will be different + AZ::TransformBus::Event( + m_entityId2, &AZ::TransformBus::Events::SetWorldRotationQuaternion, Entity2RotationForLocalTranslation.GetRotation()); + + AzToolsFramework::SelectEntities({ m_entityId2, m_entityId3 }); + + const auto initialManipulatorTransform = GetManipulatorTransform(); + + const float screenToWorldMultiplier = AzToolsFramework::CalculateScreenToWorldMultiplier( + AzToolsFramework::GetWorldTransform(m_entityId1).GetTranslation(), m_cameraState); + + // calculate positions for two click and drag motions (moving a linear manipulator) + // begin each click in the center of the line of the linear manipulators + const auto translationManipulatorStartHoldWorldPosition1 = AzToolsFramework::GetWorldTransform(m_entityId1).GetTranslation() + + initialManipulatorTransform->GetBasisZ() * screenToWorldMultiplier; + const auto translationManipulatorEndHoldWorldPosition1 = + translationManipulatorStartHoldWorldPosition1 + AZ::Vector3::CreateAxisZ(LinearManipulatorZAxisMovement); + const auto translationManipulatorStartHoldWorldPosition2 = AzToolsFramework::GetWorldTransform(m_entityId1).GetTranslation() + + AZ::Vector3::CreateAxisZ(LinearManipulatorZAxisMovement) - initialManipulatorTransform->GetBasisY() * screenToWorldMultiplier; + const auto translationManipulatorEndHoldWorldPosition2 = + translationManipulatorStartHoldWorldPosition2 + AZ::Vector3::CreateAxisY(LinearManipulatorYAxisMovement); + + // transform to screen space + const auto translationManipulatorStartHoldScreenPosition1 = + AzFramework::WorldToScreen(translationManipulatorStartHoldWorldPosition1, m_cameraState); + const auto translationManipulatorEndHoldScreenPosition1 = + AzFramework::WorldToScreen(translationManipulatorEndHoldWorldPosition1, m_cameraState); + const auto translationManipulatorStartHoldScreenPosition2 = + AzFramework::WorldToScreen(translationManipulatorStartHoldWorldPosition2, m_cameraState); + const auto translationManipulatorEndHoldScreenPosition2 = + AzFramework::WorldToScreen(translationManipulatorEndHoldWorldPosition2, m_cameraState); + + m_actionDispatcher->CameraState(m_cameraState) + ->MousePosition(translationManipulatorStartHoldScreenPosition1) + ->KeyboardModifierDown(GetParam().m_keyboardModifier) + ->MouseLButtonDown() + ->MousePosition(translationManipulatorEndHoldScreenPosition1) + ->MouseLButtonUp() + ->MousePosition(translationManipulatorStartHoldScreenPosition2) + ->MouseLButtonDown() + ->MousePosition(translationManipulatorEndHoldScreenPosition2) + ->MouseLButtonUp(); + + const auto expectedEntity2Transform = GetParam().m_firstExpectedEntityTransformAfter; + const auto expectedEntity3Transform = GetParam().m_secondExpectedEntityTransformAfter; + const auto expectedManipulatorTransform = GetParam().m_expectedManipulatorTransformAfter; + + const auto manipulatorTransformAfter = GetManipulatorTransform(); + const auto entity2Transform = AzToolsFramework::GetWorldTransform(m_entityId2); + const auto entity3Transform = AzToolsFramework::GetWorldTransform(m_entityId3); + + EXPECT_THAT(*manipulatorTransformAfter, IsCloseTolerance(expectedManipulatorTransform, 0.01f)); + EXPECT_THAT(entity2Transform, IsCloseTolerance(expectedEntity2Transform, 0.01f)); + EXPECT_THAT(entity3Transform, IsCloseTolerance(expectedEntity3Transform, 0.01f)); + } + + static const AZ::Transform ExpectedEntity2TransformAfterLocalGroupTranslationManipulatorMotion = + AZ::Transform::CreateTranslation( + EditorTransformComponentSelectionViewportPickingFixture::Entity2WorldTranslation + + AZ::Vector3(0.0f, LinearManipulatorYAxisMovement, LinearManipulatorZAxisMovement)) * + Entity2RotationForLocalTranslation; + static const AZ::Transform ExpectedEntity3TransformAfterLocalGroupTranslationManipulatorMotion = AZ::Transform::CreateTranslation( + EditorTransformComponentSelectionViewportPickingFixture::Entity3WorldTranslation + + AZ::Vector3(0.0f, LinearManipulatorYAxisMovement, LinearManipulatorZAxisMovement)); + // note: as entity has been rotated by 90 degrees about Z in TranslatingMultipleEntitiesWithDifferentModifierCombinations then + // LinearManipulatorYAxisMovement is now aligned to the world x-axis + static const AZ::Transform ExpectedEntity2TransformAfterLocalIndividualTranslationManipulatorMotion = + AZ::Transform::CreateTranslation( + EditorTransformComponentSelectionViewportPickingFixture::Entity2WorldTranslation + + AZ::Vector3(-LinearManipulatorYAxisMovement, 0.0f, LinearManipulatorZAxisMovement)) * + Entity2RotationForLocalTranslation; + static const AZ::Transform ExpectedEntity3TransformAfterLocalIndividualTranslationManipulatorMotion = AZ::Transform::CreateTranslation( + EditorTransformComponentSelectionViewportPickingFixture::Entity3WorldTranslation + + AZ::Vector3(0.0f, LinearManipulatorYAxisMovement, LinearManipulatorZAxisMovement)); + + INSTANTIATE_TEST_CASE_P( + All, + EditorTransformComponentSelectionTranslationManipulatorMultipleEntityTestFixtureParam, + testing::Values( + // this replicates translating a group of entities in local space with no modifiers held (group influence) + // manipulator and entity translate + ManipulatorOptionsMultiple{ AzToolsFramework::ViewportInteraction::KeyboardModifier::None, + ExpectedManipulatorTransformAfterGroupTranslationManipulatorMotion, + ExpectedEntity2TransformAfterLocalGroupTranslationManipulatorMotion, + ExpectedEntity3TransformAfterLocalGroupTranslationManipulatorMotion }, + // this replicates translating a group of entities in local space with the alt modifier held + // entities move in their own local space (individual influence) + ManipulatorOptionsMultiple{ AzToolsFramework::ViewportInteraction::KeyboardModifier::Alt, + ExpectedManipulatorTransformAfterGroupTranslationManipulatorMotion, + ExpectedEntity2TransformAfterLocalIndividualTranslationManipulatorMotion, + ExpectedEntity3TransformAfterLocalIndividualTranslationManipulatorMotion }, + // this replicates translating a group of entities in world space with the shift modifier held + // entities and manipulator move in world space + ManipulatorOptionsMultiple{ AzToolsFramework::ViewportInteraction::KeyboardModifier::Shift, + ExpectedManipulatorTransformAfterGroupTranslationManipulatorMotion, + ExpectedEntity2TransformAfterLocalGroupTranslationManipulatorMotion, + ExpectedEntity3TransformAfterLocalGroupTranslationManipulatorMotion }, + // this replicates translating the manipulator in local space with the ctrl modifier held (entities are unchanged) + ManipulatorOptionsMultiple{ + AzToolsFramework::ViewportInteraction::KeyboardModifier::Ctrl, + ExpectedManipulatorTransformAfterGroupTranslationManipulatorMotion, + AZ::Transform::CreateTranslation(EditorTransformComponentSelectionViewportPickingFixture::Entity2WorldTranslation) * + Entity2RotationForLocalTranslation, + AZ::Transform::CreateTranslation(EditorTransformComponentSelectionViewportPickingFixture::Entity3WorldTranslation) })); + + class EditorTransformComponentSelectionScaleManipulatorMultipleEntityTestFixtureParam + : public EditorTransformComponentSelectionManipulatorInteractionTestFixture + , public ::testing::WithParamInterface + { + }; + + static const float LinearManipulatorZAxisMovementScale = 0.5f; + + TEST_P( + EditorTransformComponentSelectionScaleManipulatorMultipleEntityTestFixtureParam, + ScalingMultipleEntitiesWithDifferentModifierCombinations) + { + using AzToolsFramework::EditorTransformComponentSelectionRequestBus; + + PositionEntities(); + + // move camera up and to the left so it's just above the normal row of entities + AzFramework::SetCameraTransform( + m_cameraState, + AZ::Transform::CreateFromQuaternionAndTranslation( + AZ::Quaternion::CreateFromEulerAnglesDegrees(AZ::Vector3(0.0f, 0.0f, 90.0f)), AZ::Vector3(10.0f, 15.0f, 10.1f))); + + SetTransformMode(EditorTransformComponentSelectionRequestBus::Events::Mode::Scale); + + AzToolsFramework::SelectEntities({ m_entityId2, m_entityId3 }); + + // manipulator should be centered between the two entities + const auto initialManipulatorTransform = GetManipulatorTransform(); + + const float screenToWorldMultiplier = + AzToolsFramework::CalculateScreenToWorldMultiplier(initialManipulatorTransform->GetTranslation(), m_cameraState); + + const auto translationManipulatorStartHoldWorldPosition1 = AzToolsFramework::GetWorldTransform(m_entityId1).GetTranslation() + + initialManipulatorTransform->GetBasisZ() * screenToWorldMultiplier; + const auto translationManipulatorEndHoldWorldPosition1 = + translationManipulatorStartHoldWorldPosition1 + AZ::Vector3::CreateAxisZ(LinearManipulatorZAxisMovementScale); + + // calculate screen space positions + const auto scaleManipulatorHoldScreenPosition = + AzFramework::WorldToScreen(translationManipulatorStartHoldWorldPosition1, m_cameraState); + const auto scaleManipulatorEndHoldScreenPosition = + AzFramework::WorldToScreen(translationManipulatorEndHoldWorldPosition1, m_cameraState); + + m_actionDispatcher->CameraState(m_cameraState) + ->MousePosition(scaleManipulatorHoldScreenPosition) + ->KeyboardModifierDown(GetParam().m_keyboardModifier) + ->MouseLButtonDown() + ->MousePosition(scaleManipulatorEndHoldScreenPosition) + ->MouseLButtonUp(); + + const auto expectedEntity2Transform = GetParam().m_firstExpectedEntityTransformAfter; + const auto expectedEntity3Transform = GetParam().m_secondExpectedEntityTransformAfter; + const auto expectedManipulatorTransform = GetParam().m_expectedManipulatorTransformAfter; + + const auto manipulatorTransformAfter = GetManipulatorTransform(); + const auto entity2Transform = AzToolsFramework::GetWorldTransform(m_entityId2); + const auto entity3Transform = AzToolsFramework::GetWorldTransform(m_entityId3); + + EXPECT_THAT(*manipulatorTransformAfter, IsCloseTolerance(expectedManipulatorTransform, 0.01f)); + EXPECT_THAT(entity2Transform, IsCloseTolerance(expectedEntity2Transform, 0.01f)); + EXPECT_THAT(entity3Transform, IsCloseTolerance(expectedEntity3Transform, 0.01f)); + } + + static const AZ::Transform ExpectedEntity2TransformAfterLocalGroupScaleManipulatorMotion = + AZ::Transform::CreateTranslation(EditorTransformComponentSelectionViewportPickingFixture::Entity2WorldTranslation) * + AZ::Transform::CreateTranslation(AZ::Vector3(0.0f, -1.0f, 0.0f)) * + AZ::Transform::CreateUniformScale(LinearManipulatorZAxisMovement); + static const AZ::Transform ExpectedEntity3TransformAfterLocalGroupScaleManipulatorMotion = + AZ::Transform::CreateTranslation(EditorTransformComponentSelectionViewportPickingFixture::Entity3WorldTranslation) * + AZ::Transform::CreateTranslation(AZ::Vector3(0.0f, 1.0f, 0.0f)) * AZ::Transform::CreateUniformScale(LinearManipulatorZAxisMovement); + static const AZ::Transform ExpectedEntity2TransformAfterLocalIndividualScaleManipulatorMotion = + AZ::Transform::CreateTranslation(EditorTransformComponentSelectionViewportPickingFixture::Entity2WorldTranslation) * + AZ::Transform::CreateUniformScale(LinearManipulatorZAxisMovement); + static const AZ::Transform ExpectedEntity3TransformAfterLocalIndividualScaleManipulatorMotion = + AZ::Transform::CreateTranslation(EditorTransformComponentSelectionViewportPickingFixture::Entity3WorldTranslation) * + AZ::Transform::CreateUniformScale(LinearManipulatorZAxisMovement); + + INSTANTIATE_TEST_CASE_P( + All, + EditorTransformComponentSelectionScaleManipulatorMultipleEntityTestFixtureParam, + testing::Values( + // this replicates scaling a group of entities in local space with no modifiers held + // entities scale relative to manipulator pivot + ManipulatorOptionsMultiple{ AzToolsFramework::ViewportInteraction::KeyboardModifier::None, + AZ::Transform::CreateTranslation(AggregateManipulatorPositionWithEntity2and3Selected), + ExpectedEntity2TransformAfterLocalGroupScaleManipulatorMotion, + ExpectedEntity3TransformAfterLocalGroupScaleManipulatorMotion }, + // this replicates scaling a group of entities in local space with the alt modifier held + // entities scale about their own pivot + ManipulatorOptionsMultiple{ AzToolsFramework::ViewportInteraction::KeyboardModifier::Alt, + AZ::Transform::CreateTranslation(AggregateManipulatorPositionWithEntity2and3Selected), + ExpectedEntity2TransformAfterLocalIndividualScaleManipulatorMotion, + ExpectedEntity3TransformAfterLocalIndividualScaleManipulatorMotion }, + // this replicates scaling a group of entities in world space with the shift modifier held + // entities scale relative to manipulator pivot in world space + ManipulatorOptionsMultiple{ AzToolsFramework::ViewportInteraction::KeyboardModifier::Shift, + AZ::Transform::CreateTranslation(AggregateManipulatorPositionWithEntity2and3Selected), + ExpectedEntity2TransformAfterLocalGroupScaleManipulatorMotion, + ExpectedEntity3TransformAfterLocalGroupScaleManipulatorMotion }, + // this has no effect (entities and manipulator are unchanged) + ManipulatorOptionsMultiple{ + AzToolsFramework::ViewportInteraction::KeyboardModifier::Ctrl, + AZ::Transform::CreateTranslation(AggregateManipulatorPositionWithEntity2and3Selected), + AZ::Transform::CreateTranslation(EditorTransformComponentSelectionViewportPickingFixture::Entity2WorldTranslation), + AZ::Transform::CreateTranslation(EditorTransformComponentSelectionViewportPickingFixture::Entity3WorldTranslation) })); + using EditorTransformComponentSelectionManipulatorTestFixture = IndirectCallManipulatorViewportInteractionFixtureMixin; @@ -1661,7 +2211,7 @@ namespace UnitTest All, EditorTransformComponentSelectionSingleEntityPivotAndOverrideFixture, testing::Values( - ReferenceFrameWithOrientation{ AzToolsFramework::ReferenceFrame::Local, ChildExpectedPivotLocalOrientationInWorldSpace }, + ReferenceFrameWithOrientation{ AzToolsFramework::ReferenceFrame::Local, PivotOverrideLocalOrientationInWorldSpace }, ReferenceFrameWithOrientation{ AzToolsFramework::ReferenceFrame::Parent, PivotOverrideLocalOrientationInWorldSpace }, ReferenceFrameWithOrientation{ AzToolsFramework::ReferenceFrame::World, AZ::Quaternion::CreateIdentity() })); diff --git a/Code/Framework/AzToolsFramework/Tests/Viewport/ViewportScreenTests.cpp b/Code/Framework/AzToolsFramework/Tests/Viewport/ViewportScreenTests.cpp index 77058038e9..e8fce6653f 100644 --- a/Code/Framework/AzToolsFramework/Tests/Viewport/ViewportScreenTests.cpp +++ b/Code/Framework/AzToolsFramework/Tests/Viewport/ViewportScreenTests.cpp @@ -6,8 +6,8 @@ * */ -#include #include +#include #include #include #include @@ -20,18 +20,17 @@ namespace UnitTest { - // transform a point from normalized device coordinates to world space, and then from world space back to normalized device coordinates - AZ::Vector2 ScreenNDCToWorldToScreenNDC( - const AZ::Vector2& ndcPoint, const AzFramework::CameraState& cameraState) + // transform a point from normalized device coordinates to world space, and then from world space back to normalized device coordinates + AZ::Vector2 ScreenNdcToWorldToScreenNdc(const AZ::Vector2& ndcPoint, const AzFramework::CameraState& cameraState) { - const auto worldResult = AzFramework::ScreenNDCToWorld(ndcPoint, InverseCameraView(cameraState), InverseCameraProjection(cameraState)); - const auto ndcResult = AzFramework::WorldToScreenNDC(worldResult, CameraView(cameraState), CameraProjection(cameraState)); + const auto worldResult = + AzFramework::ScreenNdcToWorld(ndcPoint, InverseCameraView(cameraState), InverseCameraProjection(cameraState)); + const auto ndcResult = AzFramework::WorldToScreenNdc(worldResult, CameraView(cameraState), CameraProjection(cameraState)); return AZ::Vector3ToVector2(ndcResult); } // transform a point from screen space to world space, and then from world space back to screen space - AzFramework::ScreenPoint ScreenToWorldToScreen( - const AzFramework::ScreenPoint& screenPoint, const AzFramework::CameraState& cameraState) + AzFramework::ScreenPoint ScreenToWorldToScreen(const AzFramework::ScreenPoint& screenPoint, const AzFramework::CameraState& cameraState) { const auto worldResult = AzFramework::ScreenToWorld(screenPoint, cameraState); return AzFramework::WorldToScreen(worldResult, cameraState); @@ -47,25 +46,25 @@ namespace UnitTest const auto cameraState = AzFramework::CreateIdentityDefaultCamera(cameraPosition, screenDimensions); { - const auto expectedScreenPoint = ScreenPoint{600, 450}; + const auto expectedScreenPoint = ScreenPoint{ 600, 450 }; const auto resultScreenPoint = ScreenToWorldToScreen(expectedScreenPoint, cameraState); EXPECT_EQ(resultScreenPoint, expectedScreenPoint); } { - const auto expectedScreenPoint = ScreenPoint{400, 300}; + const auto expectedScreenPoint = ScreenPoint{ 400, 300 }; const auto resultScreenPoint = ScreenToWorldToScreen(expectedScreenPoint, cameraState); EXPECT_EQ(resultScreenPoint, expectedScreenPoint); } { - const auto expectedScreenPoint = ScreenPoint{0, 0}; + const auto expectedScreenPoint = ScreenPoint{ 0, 0 }; const auto resultScreenPoint = ScreenToWorldToScreen(expectedScreenPoint, cameraState); EXPECT_EQ(resultScreenPoint, expectedScreenPoint); } { - const auto expectedScreenPoint = ScreenPoint{800, 600}; + const auto expectedScreenPoint = ScreenPoint{ 800, 600 }; const auto resultScreenPoint = ScreenToWorldToScreen(expectedScreenPoint, cameraState); EXPECT_EQ(resultScreenPoint, expectedScreenPoint); } @@ -81,7 +80,7 @@ namespace UnitTest const auto cameraState = AzFramework::CreateDefaultCamera(cameraTransform, screenDimensions); - const auto expectedScreenPoint = ScreenPoint{200, 300}; + const auto expectedScreenPoint = ScreenPoint{ 200, 300 }; const auto resultScreenPoint = ScreenToWorldToScreen(expectedScreenPoint, cameraState); EXPECT_EQ(resultScreenPoint, expectedScreenPoint); } @@ -93,46 +92,46 @@ namespace UnitTest using AzFramework::ScreenPoint; const auto screenDimensions = AZ::Vector2(800.0f, 600.0f); - const auto cameraTransform = AZ::Transform::CreateTranslation(AZ::Vector3(10.0f, 0.0f, 0.0f)) * - AZ::Transform::CreateRotationZ(AZ::DegToRad(-90.0f)); + const auto cameraTransform = + AZ::Transform::CreateTranslation(AZ::Vector3(10.0f, 0.0f, 0.0f)) * AZ::Transform::CreateRotationZ(AZ::DegToRad(-90.0f)); const auto cameraState = AzFramework::CreateDefaultCamera(cameraTransform, screenDimensions); - const auto worldResult = AzFramework::ScreenToWorld(ScreenPoint{400, 300}, cameraState); + const auto worldResult = AzFramework::ScreenToWorld(ScreenPoint{ 400, 300 }, cameraState); EXPECT_THAT(worldResult, IsClose(AZ::Vector3(10.1f, 0.0f, 0.0f))); } - + //////////////////////////////////////////////////////////////////////////////////////////////////////// // NDC tests TEST(ViewportScreen, WorldToScreenNDCAndScreenNDCToWorldReturnsTheSameValueIdentityCameraOffsetFromOrigin) { using NdcPoint = AZ::Vector2; - + const auto screenDimensions = AZ::Vector2(800.0f, 600.0f); const auto cameraPosition = AZ::Vector3::CreateAxisY(-10.0f); const auto cameraState = AzFramework::CreateIdentityDefaultCamera(cameraPosition, screenDimensions); { - const auto expectedNdcPoint = NdcPoint{0.75f, 0.75f}; - const auto resultNdcPoint = ScreenNDCToWorldToScreenNDC(expectedNdcPoint, cameraState); + const auto expectedNdcPoint = NdcPoint{ 0.75f, 0.75f }; + const auto resultNdcPoint = ScreenNdcToWorldToScreenNdc(expectedNdcPoint, cameraState); EXPECT_THAT(resultNdcPoint, IsClose(expectedNdcPoint)); } { - const auto expectedNdcPoint = NdcPoint{0.5f, 0.5f}; - const auto resultNdcPoint = ScreenNDCToWorldToScreenNDC(expectedNdcPoint, cameraState); + const auto expectedNdcPoint = NdcPoint{ 0.5f, 0.5f }; + const auto resultNdcPoint = ScreenNdcToWorldToScreenNdc(expectedNdcPoint, cameraState); EXPECT_THAT(resultNdcPoint, IsClose(expectedNdcPoint)); } { - const auto expectedNdcPoint = NdcPoint{0.0f, 0.0f}; - const auto resultNdcPoint = ScreenNDCToWorldToScreenNDC(expectedNdcPoint, cameraState); + const auto expectedNdcPoint = NdcPoint{ 0.0f, 0.0f }; + const auto resultNdcPoint = ScreenNdcToWorldToScreenNdc(expectedNdcPoint, cameraState); EXPECT_THAT(resultNdcPoint, IsClose(expectedNdcPoint)); } { - const auto expectedNdcPoint = NdcPoint{1.0f, 1.0f}; - const auto resultNdcPoint = ScreenNDCToWorldToScreenNDC(expectedNdcPoint, cameraState); + const auto expectedNdcPoint = NdcPoint{ 1.0f, 1.0f }; + const auto resultNdcPoint = ScreenNdcToWorldToScreenNdc(expectedNdcPoint, cameraState); EXPECT_THAT(resultNdcPoint, IsClose(expectedNdcPoint)); } } @@ -147,8 +146,8 @@ namespace UnitTest const auto cameraState = AzFramework::CreateDefaultCamera(cameraTransform, screenDimensions); - const auto expectedNdcPoint = NdcPoint{0.25f, 0.5f}; - const auto resultNdcPoint = ScreenNDCToWorldToScreenNDC(expectedNdcPoint, cameraState); + const auto expectedNdcPoint = NdcPoint{ 0.25f, 0.5f }; + const auto resultNdcPoint = ScreenNdcToWorldToScreenNdc(expectedNdcPoint, cameraState); EXPECT_THAT(resultNdcPoint, IsClose(expectedNdcPoint)); } @@ -159,12 +158,13 @@ namespace UnitTest using NdcPoint = AZ::Vector2; const auto screenDimensions = AZ::Vector2(800.0f, 600.0f); - const auto cameraTransform = AZ::Transform::CreateTranslation(AZ::Vector3(10.0f, 0.0f, 0.0f)) * - AZ::Transform::CreateRotationZ(AZ::DegToRad(-90.0f)); + const auto cameraTransform = + AZ::Transform::CreateTranslation(AZ::Vector3(10.0f, 0.0f, 0.0f)) * AZ::Transform::CreateRotationZ(AZ::DegToRad(-90.0f)); const auto cameraState = AzFramework::CreateDefaultCamera(cameraTransform, screenDimensions); - const auto worldResult = AzFramework::ScreenNDCToWorld(NdcPoint{0.5f, 0.5f}, InverseCameraView(cameraState), InverseCameraProjection(cameraState)); + const auto worldResult = + AzFramework::ScreenNdcToWorld(NdcPoint{ 0.5f, 0.5f }, InverseCameraView(cameraState), InverseCameraProjection(cameraState)); EXPECT_THAT(worldResult, IsClose(AZ::Vector3(10.1f, 0.0f, 0.0f))); } @@ -175,7 +175,7 @@ namespace UnitTest using AzFramework::ScreenPoint; using AzFramework::ScreenVector; - const ScreenVector screenVector = ScreenPoint{100, 200} - ScreenPoint{10, 20}; + const ScreenVector screenVector = ScreenPoint{ 100, 200 } - ScreenPoint{ 10, 20 }; EXPECT_EQ(screenVector, ScreenVector(90, 180)); } @@ -184,7 +184,7 @@ namespace UnitTest using AzFramework::ScreenPoint; using AzFramework::ScreenVector; - const ScreenPoint screenPoint = ScreenPoint{100, 200} + ScreenVector{50, 25}; + const ScreenPoint screenPoint = ScreenPoint{ 100, 200 } + ScreenVector{ 50, 25 }; EXPECT_EQ(screenPoint, ScreenPoint(150, 225)); } @@ -193,7 +193,7 @@ namespace UnitTest using AzFramework::ScreenPoint; using AzFramework::ScreenVector; - const ScreenPoint screenPoint = ScreenPoint{120, 200} - ScreenVector{50, 20}; + const ScreenPoint screenPoint = ScreenPoint{ 120, 200 } - ScreenVector{ 50, 20 }; EXPECT_EQ(screenPoint, ScreenPoint(70, 180)); } @@ -202,7 +202,7 @@ namespace UnitTest using AzFramework::ScreenPoint; using AzFramework::ScreenVector; - const ScreenVector screenVector = ScreenVector{100, 200} + ScreenVector{50, 25}; + const ScreenVector screenVector = ScreenVector{ 100, 200 } + ScreenVector{ 50, 25 }; EXPECT_EQ(screenVector, ScreenVector(150, 225)); } @@ -211,7 +211,7 @@ namespace UnitTest using AzFramework::ScreenPoint; using AzFramework::ScreenVector; - const ScreenVector screenVector = ScreenVector{100, 200} - ScreenVector{50, 25}; + const ScreenVector screenVector = ScreenVector{ 100, 200 } - ScreenVector{ 50, 25 }; EXPECT_EQ(screenVector, ScreenVector(50, 175)); } @@ -220,8 +220,8 @@ namespace UnitTest using AzFramework::ScreenPoint; using AzFramework::ScreenVector; - const ScreenPoint screenPoint = ScreenPoint{100, 200}; - const ScreenVector screenVector = ScreenVector{50, 25}; + const ScreenPoint screenPoint = ScreenPoint{ 100, 200 }; + const ScreenVector screenVector = ScreenVector{ 50, 25 }; const AZ::Vector2 fromScreenPoint = AzFramework::Vector2FromScreenPoint(screenPoint); const AZ::Vector2 fromScreenVector = AzFramework::Vector2FromScreenVector(screenVector); @@ -295,6 +295,58 @@ namespace UnitTest EXPECT_NEAR(AzFramework::ScreenVectorLength(ScreenVector(12, 15)), 19.20937f, 0.001f); } + TEST(ViewportScreen, ScreenVectorTransformedByScalarUpwards) + { + using AzFramework::ScreenVector; + + auto screenVector = ScreenVector(5, 10); + auto scaledScreenVector = screenVector * 2.0f; + + EXPECT_EQ(scaledScreenVector, ScreenVector(10, 20)); + } + + TEST(ViewportScreen, ScreenVectorTransformedByScalarWithRounding) + { + using AzFramework::ScreenVector; + + auto screenVector = ScreenVector(1, 6); + auto scaledScreenVector = screenVector * 0.1f; + + // value less than 0.5 rounds down, greater than or equal to 0.5 rounds up + EXPECT_EQ(scaledScreenVector, ScreenVector(0, 1)); + } + + TEST(ViewportScreen, ScreenVectorTransformedByScalarWithRoundingAtHalfwayBoundary) + { + using AzFramework::ScreenVector; + + auto screenVector = ScreenVector(5, 10); + auto scaledScreenVector = screenVector * 0.1f; + + // value less than 0.5 rounds down, greater than or equal to 0.5 rounds up + EXPECT_EQ(scaledScreenVector, ScreenVector(1, 1)); + } + + TEST(ViewportScreen, ScreenVectorTransformedByScalarDownwards) + { + using AzFramework::ScreenVector; + + auto screenVector = ScreenVector(6, 12); + auto scaledScreenVector = screenVector * 0.5f; + + EXPECT_EQ(scaledScreenVector, ScreenVector(3, 6)); + } + + TEST(ViewportScreen, ScreenVectorTransformedByScalarInplace) + { + using AzFramework::ScreenVector; + + auto screenVector = ScreenVector(13, 37); + screenVector *= 10.0f; + + EXPECT_EQ(screenVector, ScreenVector(130, 370)); + } + //////////////////////////////////////////////////////////////////////////////////////////////////////// // Other tests TEST(ViewportScreen, CanGetCameraTransformFromCameraViewAndBack) diff --git a/Gems/AtomLyIntegration/AtomFont/Code/Source/FFont.cpp b/Gems/AtomLyIntegration/AtomFont/Code/Source/FFont.cpp index c5268b427f..86b3011836 100644 --- a/Gems/AtomLyIntegration/AtomFont/Code/Source/FFont.cpp +++ b/Gems/AtomLyIntegration/AtomFont/Code/Source/FFont.cpp @@ -1726,7 +1726,7 @@ void AZ::FFont::DrawScreenAlignedText3d( { return; } - AZ::Vector3 positionNDC = AzFramework::WorldToScreenNDC( + AZ::Vector3 positionNDC = AzFramework::WorldToScreenNdc( params.m_position, currentView->GetWorldToViewMatrix(), currentView->GetViewToClipMatrix() From 1f6dfd0194d84ef5f126e970936fffe77e2c7772 Mon Sep 17 00:00:00 2001 From: AMZN-Igarri <82394219+AMZN-Igarri@users.noreply.github.com> Date: Tue, 28 Sep 2021 13:47:21 +0200 Subject: [PATCH 43/51] Asset Browser Search View Column Resizing (#4087) * Added resizing Signed-off-by: igarri * Added Signals and Slots for resizing columns Signed-off-by: igarri * Cleaned up code Signed-off-by: igarri * Fixed Column Resize when docking Signed-off-by: igarri * Added constants Signed-off-by: igarri * Applied Code review feedback Signed-off-by: igarri * aplied more feedback Signed-off-by: igarri * Addressed Code Review Comments Signed-off-by: igarri * Fixing bad merge Signed-off-by: igarri * Added fix in initialization Signed-off-by: igarri * Changed header setup sequence Signed-off-by: igarri --- .../AzAssetBrowser/AzAssetBrowserWindow.cpp | 23 +++++++++++++++++++ .../AzAssetBrowser/AzAssetBrowserWindow.h | 8 +++++++ .../AssetPicker/AssetPickerDialog.cpp | 10 ++++++++ .../AssetPicker/AssetPickerDialog.h | 4 ++++ .../AssetPicker/AssetPickerDialog.ui | 6 ++--- .../Views/AssetBrowserTableView.cpp | 21 ++++++++++++++--- .../Views/AssetBrowserTableView.h | 4 +++- 7 files changed, 69 insertions(+), 7 deletions(-) diff --git a/Code/Editor/AzAssetBrowser/AzAssetBrowserWindow.cpp b/Code/Editor/AzAssetBrowser/AzAssetBrowserWindow.cpp index 18946c7874..9fe4cfd0d2 100644 --- a/Code/Editor/AzAssetBrowser/AzAssetBrowserWindow.cpp +++ b/Code/Editor/AzAssetBrowser/AzAssetBrowserWindow.cpp @@ -141,6 +141,10 @@ AzAssetBrowserWindow::AzAssetBrowserWindow(QWidget* parent) m_ui->m_assetBrowserTreeViewWidget, &AzAssetBrowser::AssetBrowserTreeView::ClearTypeFilter, m_ui->m_searchWidget, &AzAssetBrowser::SearchWidget::ClearTypeFilter); + connect( + this, &AzAssetBrowserWindow::SizeChangedSignal, m_ui->m_assetBrowserTableViewWidget, + &AzAssetBrowser::AssetBrowserTableView::UpdateSizeSlot); + m_ui->m_assetBrowserTreeViewWidget->SetName("AssetBrowserTreeView_main"); } @@ -164,6 +168,25 @@ QObject* AzAssetBrowserWindow::createListenerForShowAssetEditorEvent(QObject* pa return listener; } +void AzAssetBrowserWindow::resizeEvent(QResizeEvent* resizeEvent) +{ + // leftLayout is the parent of the tableView + // rightLayout is the parent of the preview window. + // Workaround: When docking windows this event keeps holding the old size of the widgets instead of the new one + // but the resizeEvent holds the new size of the whole widget + // So we have to save the proportions somehow + const QWidget* leftLayout = m_ui->m_leftLayout; + const QVBoxLayout* rightLayout = m_ui->m_rightLayout; + + const float oldLeftLayoutWidth = aznumeric_cast(leftLayout->geometry().width()); + const float oldWidth = aznumeric_cast(leftLayout->geometry().width() + rightLayout->geometry().width()); + + const float newWidth = oldLeftLayoutWidth * aznumeric_cast(resizeEvent->size().width()) / oldWidth; + + emit SizeChangedSignal(aznumeric_cast(newWidth)); + QWidget::resizeEvent(resizeEvent); +} + void AzAssetBrowserWindow::OnInitViewToggleButton() { CreateSwitchViewMenu(); diff --git a/Code/Editor/AzAssetBrowser/AzAssetBrowserWindow.h b/Code/Editor/AzAssetBrowser/AzAssetBrowserWindow.h index bf742b0cf2..753f000300 100644 --- a/Code/Editor/AzAssetBrowser/AzAssetBrowserWindow.h +++ b/Code/Editor/AzAssetBrowser/AzAssetBrowserWindow.h @@ -53,9 +53,17 @@ public: static QObject* createListenerForShowAssetEditorEvent(QObject* parent); + +Q_SIGNALS: + void SizeChangedSignal(int newWidth); + +protected: + void resizeEvent(QResizeEvent* resizeEvent) override; + private: void OnInitViewToggleButton(); void UpdateDisplayInfo(); + protected slots: void CreateSwitchViewMenu(); void SetExpandedAssetBrowserMode(); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/AssetBrowser/AssetPicker/AssetPickerDialog.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/AssetBrowser/AssetPicker/AssetPickerDialog.cpp index 76b634b51e..966cd5a43a 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/AssetBrowser/AssetPicker/AssetPickerDialog.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/AssetBrowser/AssetPicker/AssetPickerDialog.cpp @@ -151,6 +151,10 @@ namespace AzToolsFramework m_ui->m_assetBrowserTableViewWidget, &AssetBrowserTableView::ClearTypeFilter, m_ui->m_searchWidget, &SearchWidget::ClearTypeFilter); + connect( + this, &AssetPickerDialog::SizeChangedSignal, m_ui->m_assetBrowserTableViewWidget, + &AssetBrowserTableView::UpdateSizeSlot); + m_ui->m_assetBrowserTableViewWidget->SetName("AssetBrowserTableView_main"); m_tableModel->UpdateTableModelMaps(); } @@ -206,6 +210,12 @@ namespace AzToolsFramework } } + void AssetPickerDialog::resizeEvent(QResizeEvent* resizeEvent) + { + emit SizeChangedSignal(m_ui->verticalLayout_4->geometry().width()); + QDialog::resizeEvent(resizeEvent); + } + void AssetPickerDialog::keyPressEvent(QKeyEvent* e) { // Until search widget is revised, Return key should not close the dialog, diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/AssetBrowser/AssetPicker/AssetPickerDialog.h b/Code/Framework/AzToolsFramework/AzToolsFramework/AssetBrowser/AssetPicker/AssetPickerDialog.h index bab265c134..f8ec6077ce 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/AssetBrowser/AssetPicker/AssetPickerDialog.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/AssetBrowser/AssetPicker/AssetPickerDialog.h @@ -46,6 +46,9 @@ namespace AzToolsFramework explicit AssetPickerDialog(AssetSelectionModel& selection, QWidget* parent = nullptr); virtual ~AssetPickerDialog(); + Q_SIGNALS: + void SizeChangedSignal(int newWidth); + protected: ////////////////////////////////////////////////////////////////////////// // QDialog @@ -53,6 +56,7 @@ namespace AzToolsFramework void accept() override; void reject() override; void keyPressEvent(QKeyEvent* e) override; + void resizeEvent(QResizeEvent* resizeEvent) override; private Q_SLOTS: void DoubleClickedSlot(const QModelIndex& index); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/AssetBrowser/AssetPicker/AssetPickerDialog.ui b/Code/Framework/AzToolsFramework/AzToolsFramework/AssetBrowser/AssetPicker/AssetPickerDialog.ui index b11ffb3990..df7d27d3c8 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/AssetBrowser/AssetPicker/AssetPickerDialog.ui +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/AssetBrowser/AssetPicker/AssetPickerDialog.ui @@ -117,6 +117,9 @@ 0 + + + @@ -142,9 +145,6 @@ - - - diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/AssetBrowser/Views/AssetBrowserTableView.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/AssetBrowser/Views/AssetBrowserTableView.cpp index 217e282a37..feab9a9e5a 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/AssetBrowser/Views/AssetBrowserTableView.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/AssetBrowser/Views/AssetBrowserTableView.cpp @@ -20,13 +20,17 @@ AZ_PUSH_DISABLE_WARNING( #include #include #include - +#include #include AZ_POP_DISABLE_WARNING namespace AzToolsFramework { namespace AssetBrowser { + const float MinHeaderResizeProportion = .25f; + const float MaxHeaderResizeProportion = .75f; + const float DefaultHeaderResizeProportion = .5f; + AssetBrowserTableView::AssetBrowserTableView(QWidget* parent) : AzQtComponents::TableView(parent) , m_delegate(new SearchEntryDelegate(this)) @@ -65,8 +69,10 @@ namespace AzToolsFramework AzQtComponents::TableView::setModel(model); connect(m_tableModel, &AssetBrowserTableModel::layoutChanged, this, &AssetBrowserTableView::layoutChangedSlot); - header()->setSectionResizeMode(0, QHeaderView::ResizeMode::Stretch); - header()->setSectionResizeMode(1, QHeaderView::ResizeMode::Stretch); + header()->setStretchLastSection(true); + header()->setSectionResizeMode(0, QHeaderView::ResizeMode::Interactive); + header()->setSectionResizeMode(1, QHeaderView::ResizeMode::Interactive); + UpdateSizeSlot(parentWidget()->width()); header()->setSortIndicatorShown(false); header()->setSectionsClickable(false); } @@ -148,8 +154,17 @@ namespace AzToolsFramework void AssetBrowserTableView::OnAssetBrowserComponentReady() { + UpdateSizeSlot(parentWidget()->width()); } + void AssetBrowserTableView::UpdateSizeSlot(int newWidth) + { + setColumnWidth(0, aznumeric_cast(newWidth * DefaultHeaderResizeProportion)); + header()->setMinimumSectionSize(aznumeric_cast(newWidth * MinHeaderResizeProportion)); + header()->setMaximumSectionSize(aznumeric_cast(newWidth * MaxHeaderResizeProportion)); + } + + void AssetBrowserTableView::OnContextMenu([[maybe_unused]] const QPoint& point) { const auto& selectedAssets = GetSelectedAssets(); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/AssetBrowser/Views/AssetBrowserTableView.h b/Code/Framework/AzToolsFramework/AzToolsFramework/AssetBrowser/Views/AssetBrowserTableView.h index b4eca59cef..b93500ba37 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/AssetBrowser/Views/AssetBrowserTableView.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/AssetBrowser/Views/AssetBrowserTableView.h @@ -59,12 +59,14 @@ namespace AzToolsFramework void ClearStringFilter(); void ClearTypeFilter(); + public Q_SLOTS: + void UpdateSizeSlot(int newWidth); + protected Q_SLOTS: void selectionChanged(const QItemSelection& selected, const QItemSelection& deselected) override; void rowsAboutToBeRemoved(const QModelIndex& parent, int start, int end) override; void layoutChangedSlot(const QList &parents = QList(), QAbstractItemModel::LayoutChangeHint hint = QAbstractItemModel::NoLayoutChangeHint); - private: QString m_name; QPointer m_tableModel; From 9078d1925aafc50c72e40f99da0f3f83b05f354c Mon Sep 17 00:00:00 2001 From: srikappa-amzn Date: Tue, 28 Sep 2021 06:42:53 -0700 Subject: [PATCH 44/51] Mock newly added methods to SpawnableEntitiesInterface Signed-off-by: srikappa-amzn --- .../AzFramework/Tests/Mocks/MockSpawnableEntitiesInterface.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Code/Framework/AzFramework/Tests/Mocks/MockSpawnableEntitiesInterface.h b/Code/Framework/AzFramework/Tests/Mocks/MockSpawnableEntitiesInterface.h index a5df7af4c3..a437545adf 100644 --- a/Code/Framework/AzFramework/Tests/Mocks/MockSpawnableEntitiesInterface.h +++ b/Code/Framework/AzFramework/Tests/Mocks/MockSpawnableEntitiesInterface.h @@ -40,6 +40,11 @@ namespace AzFramework MOCK_METHOD2(DespawnAllEntities, void(EntitySpawnTicket& ticket, DespawnAllEntitiesOptionalArgs optionalArgs)); + MOCK_METHOD3(DespawnEntity, void(AZ::EntityId entityId, EntitySpawnTicket& ticket, DespawnEntityOptionalArgs optionalArgs)); + + MOCK_METHOD2( + RetrieveEntitySpawnTicket, void(EntitySpawnTicket::Id entitySpawnTicketId, RetrieveEntitySpawnTicketCallback callback)); + MOCK_METHOD3( ReloadSpawnable, void(EntitySpawnTicket& ticket, AZ::Data::Asset spawnable, ReloadSpawnableOptionalArgs optionalArgs)); From a641586df28d4d62f5ba08c28296ff19746c2118 Mon Sep 17 00:00:00 2001 From: Danilo Aimini <82231674+AMZN-daimini@users.noreply.github.com> Date: Tue, 28 Sep 2021 10:02:47 -0700 Subject: [PATCH 45/51] Move FocusModeInterface retrieval to EntityOutlinerListModel constructor to prevent crash on widget refresh. Also force expansion of root on construction for the same reason. Signed-off-by: Danilo Aimini <82231674+AMZN-daimini@users.noreply.github.com> --- .../UI/Outliner/EntityOutlinerListModel.cpp | 7 ++----- .../AzToolsFramework/UI/Outliner/EntityOutlinerWidget.cpp | 3 +++ 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerListModel.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerListModel.cpp index 90f8e1d121..b3ac8b4c73 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerListModel.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerListModel.cpp @@ -82,6 +82,8 @@ namespace AzToolsFramework , m_entityExpansionState() , m_entityFilteredState() { + m_focusModeInterface = AZ::Interface::Get(); + AZ_Assert(m_focusModeInterface != nullptr, "EntityOutlinerListModel requires a FocusModeInterface instance on construction."); } EntityOutlinerListModel::~EntityOutlinerListModel() @@ -106,11 +108,6 @@ namespace AzToolsFramework m_editorEntityUiInterface = AZ::Interface::Get(); AZ_Assert(m_editorEntityUiInterface != nullptr, "EntityOutlinerListModel requires a EditorEntityUiInterface instance on Initialize."); - - m_focusModeInterface = AZ::Interface::Get(); - AZ_Assert( - m_focusModeInterface != nullptr, - "EntityOutlinerListModel requires a FocusModeInterface instance on Initialize."); } int EntityOutlinerListModel::rowCount(const QModelIndex& parent) const diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerWidget.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerWidget.cpp index 4db235d073..689e5b5dc4 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerWidget.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerWidget.cpp @@ -232,6 +232,9 @@ namespace AzToolsFramework m_gui->m_objectTree->header()->setSortIndicatorShown(false); m_gui->m_objectTree->header()->setStretchLastSection(false); + // Always expand root entity (level entity) - needed if the widget is re-created while a level is already open. + m_gui->m_objectTree->expand(m_proxyModel->index(0, 0)); + // resize the icon columns so that the Visibility and Lock toggle icon columns stay right-justified m_gui->m_objectTree->header()->setStretchLastSection(false); m_gui->m_objectTree->header()->setMinimumSectionSize(0); From 29d9abba5767b09647d080f504e04ef965b7d4c9 Mon Sep 17 00:00:00 2001 From: John Jones-Steele <82226755+jjjoness@users.noreply.github.com> Date: Tue, 28 Sep 2021 18:08:58 +0100 Subject: [PATCH 46/51] Removed redundant code (#4363) Signed-off-by: John Jones-Steele --- Gems/PhysX/Code/Source/EditorShapeColliderComponent.cpp | 1 - Gems/PhysX/Code/Source/EditorShapeColliderComponent.h | 2 -- 2 files changed, 3 deletions(-) diff --git a/Gems/PhysX/Code/Source/EditorShapeColliderComponent.cpp b/Gems/PhysX/Code/Source/EditorShapeColliderComponent.cpp index 57bfe17a30..2bbc04faae 100644 --- a/Gems/PhysX/Code/Source/EditorShapeColliderComponent.cpp +++ b/Gems/PhysX/Code/Source/EditorShapeColliderComponent.cpp @@ -676,7 +676,6 @@ namespace PhysX LmbrCentral::ShapeComponentNotificationsBus::Handler::BusDisconnect(); AZ::TransformNotificationBus::Handler::BusDisconnect(); AzToolsFramework::EntitySelectionEvents::Bus::Handler::BusDisconnect(); - AzFramework::EntityDebugDisplayEventBus::Handler::BusDisconnect(); AzToolsFramework::Components::EditorComponentBase::Deactivate(); if (m_sceneInterface && m_editorBodyHandle != AzPhysics::InvalidSimulatedBodyHandle) diff --git a/Gems/PhysX/Code/Source/EditorShapeColliderComponent.h b/Gems/PhysX/Code/Source/EditorShapeColliderComponent.h index 792d3c4327..431b34b1ae 100644 --- a/Gems/PhysX/Code/Source/EditorShapeColliderComponent.h +++ b/Gems/PhysX/Code/Source/EditorShapeColliderComponent.h @@ -10,7 +10,6 @@ #include #include -#include #include #include #include @@ -58,7 +57,6 @@ namespace PhysX //! component to create geometry in the PhysX simulation. class EditorShapeColliderComponent : public AzToolsFramework::Components::EditorComponentBase - , protected AzFramework::EntityDebugDisplayEventBus::Handler , protected AzToolsFramework::EntitySelectionEvents::Bus::Handler , private AZ::TransformNotificationBus::Handler , protected DebugDraw::DisplayCallback From e958b0e09cc2aca260605919917254838923c1cb Mon Sep 17 00:00:00 2001 From: Mikhail Naumov <82239319+AMZN-mnaumov@users.noreply.github.com> Date: Tue, 28 Sep 2021 14:07:56 -0500 Subject: [PATCH 47/51] No longer can instantiate prefabs in an empty level (#4277) * No longer can instantiate prefabs in an empty level Signed-off-by: Mikhail Naumov * PR feedback from Ram Signed-off-by: Mikhail Naumov * removing tab Signed-off-by: Mikhail Naumov * PR feedback Signed-off-by: Mikhail Naumov * Implement Project Manager 'build' button for Mac and Linux(#4248) Signed-off-by: Steve Pham Signed-off-by: Mikhail Naumov * Remove the PrefabEditManager. Introduce the FocusMode system on the Editor side, and a PrefabFocusHandler on the Prefab side to handle focus. Signed-off-by: Danilo Aimini <82231674+AMZN-daimini@users.noreply.github.com> Signed-off-by: Mikhail Naumov * Changed FocusOnOwningPrefab to return Outcome. Added comments and error checking. Signed-off-by: Danilo Aimini <82231674+AMZN-daimini@users.noreply.github.com> Signed-off-by: Mikhail Naumov * Remove assert for edge case that can actually happen in normal circumstances, simply return false in those cases. Signed-off-by: Danilo Aimini <82231674+AMZN-daimini@users.noreply.github.com> Signed-off-by: Mikhail Naumov * Remove nested FocusModeFramework namespace Signed-off-by: Danilo Aimini <82231674+AMZN-daimini@users.noreply.github.com> Signed-off-by: Mikhail Naumov * Minor fixes to variable names and comments. Signed-off-by: Danilo Aimini <82231674+AMZN-daimini@users.noreply.github.com> Signed-off-by: Mikhail Naumov * ClearFocusRoot implementation Signed-off-by: Danilo Aimini <82231674+AMZN-daimini@users.noreply.github.com> Signed-off-by: Mikhail Naumov * Remove pragma once from cpp file Signed-off-by: Danilo Aimini <82231674+AMZN-daimini@users.noreply.github.com> Signed-off-by: Mikhail Naumov * Fix header formatting to pass validation Signed-off-by: Danilo Aimini <82231674+AMZN-daimini@users.noreply.github.com> Signed-off-by: Mikhail Naumov * Do not assert if m_focusModeInterface can't be initialized Signed-off-by: Danilo Aimini <82231674+AMZN-daimini@users.noreply.github.com> Signed-off-by: Mikhail Naumov * Initialization changes to pass unit tests. Some interfaces are now retrieved on demand and there's clearer failure paths that don't involve asserts to better handle test/headless initializations. Signed-off-by: Danilo Aimini <82231674+AMZN-daimini@users.noreply.github.com> Signed-off-by: Mikhail Naumov * LYN-6793 [iOS] [asset_profile] 4 assets fail to process for iOS (#4268) * LYN-6793 [iOS] [asset_profile] 4 assets fail to process for iOS The issue was because the compression of ETC formats took too long. Replaced all ETC and PVRTC formats with ASTC formats. Update all pixel operation for formats with single R channel to align the change with R8. Signed-off-by: Qing Tao Signed-off-by: Mikhail Naumov * Use resize_no_construct when creating a buffer asset, since the initial data is going to be memcopy'd anyways. (#4249) Signed-off-by: amzn-tommy Signed-off-by: Mikhail Naumov * Fix a minor spelling mistake (#4247) Signed-off-by: amzn-tommy Signed-off-by: Mikhail Naumov * 1. Update iOS deployment target to 14 (#4266) 2. Set MacOS default deployment target to 11. Signed-off-by: amzn-sj Signed-off-by: Mikhail Naumov * Debug build clang fixes Signed-off-by: AMZN-Olex <5432499+AMZN-Olex@users.noreply.github.com> Signed-off-by: Mikhail Naumov * Fix failed 'server' platform assets on Linux related to Shaders (#4275) * Add missing 'server' platform identifier for the ShaderBuilder * Use the current host platform as the fallback platform identifier (and not 'pc') Signed-off-by: Steve Pham Signed-off-by: Mikhail Naumov * [development] Atom CPU profiler include cleanup (#4272) - Removed all unnecessary includes to Atom CpuProfiler.h - Added includes to AzCore Profiler.h where necessary Signed-off-by: AMZN-ScottR 24445312+AMZN-ScottR@users.noreply.github.com Signed-off-by: Mikhail Naumov * Add guard against edge case. Signed-off-by: Danilo Aimini <82231674+AMZN-daimini@users.noreply.github.com> Signed-off-by: Mikhail Naumov * Expose Transform::CreateLookAt to behavior context to use with hydra tests Signed-off-by: amzn-tommy Signed-off-by: Mikhail Naumov * Cached BLAS objects at the sub-mesh level Signed-off-by: dmcdiar Signed-off-by: Mikhail Naumov * EMotion FX: Asset Processor generates an Actor for every FBX (#4284) Fixing the actor behavior to ignore actor asset generation for files that only contain nodes/bones. We're only checking for blend shapes and skins now. In case neither is present in an FBX, we don't export the actor asset. This makes a plain skeleton a special case where users manually need to add the actor group for the fbx. The most common use case for these are animation files anyway, where we don't want to export actor assets. Also increased the version number of the actor group so that they are all getting reprocessed and the unneeded actor assets get removed. Signed-off-by: Benjamin Jillich Signed-off-by: Mikhail Naumov * EMotion FX: Crash in the actor builder when fbx meta data contains node groups (#4283) Fixing a crash related to the small array conversion that made multiple assets crash the actor builder. Signed-off-by: Benjamin Jillich Signed-off-by: Mikhail Naumov * Project Manager Gem Dependencies (#4132) * Fix engine API change and add gem dependencies Signed-off-by: AMZN-alexpete <26804013+AMZN-alexpete@users.noreply.github.com> * Add GemCatalog dependency test Signed-off-by: AMZN-alexpete <26804013+AMZN-alexpete@users.noreply.github.com> * Clarify display name and fix missing const Signed-off-by: AMZN-alexpete <26804013+AMZN-alexpete@users.noreply.github.com> * Moving a couple helper functions into private scope Signed-off-by: AMZN-alexpete <26804013+AMZN-alexpete@users.noreply.github.com> * Update gem count when unselecting a gem #4074 This addresses the following issue https://github.com/o3de/o3de/issues/4074 Signed-off-by: AMZN-alexpete <26804013+AMZN-alexpete@users.noreply.github.com> * Active/Inactive filter and dependency tooltips Signed-off-by: AMZN-alexpete <26804013+AMZN-alexpete@users.noreply.github.com> * Accessors for previously added and dependencies Signed-off-by: AMZN-alexpete <26804013+AMZN-alexpete@users.noreply.github.com> * Cart displays gem dependency changes Signed-off-by: AMZN-alexpete <26804013+AMZN-alexpete@users.noreply.github.com> * Shorten titles to fit in summary popup Signed-off-by: AMZN-alexpete <26804013+AMZN-alexpete@users.noreply.github.com> * Remove QString::number Signed-off-by: AMZN-alexpete <26804013+AMZN-alexpete@users.noreply.github.com> * Remove extra space Signed-off-by: AMZN-alexpete <26804013+AMZN-alexpete@users.noreply.github.com> * Consolidate source model accesor helpers Signed-off-by: AMZN-alexpete <26804013+AMZN-alexpete@users.noreply.github.com> * Addressing minor feedback Signed-off-by: AMZN-alexpete <26804013+AMZN-alexpete@users.noreply.github.com> * Remove unused local variable Signed-off-by: AMZN-alexpete <26804013+AMZN-alexpete@users.noreply.github.com> Signed-off-by: Mikhail Naumov * Fix AutoGen of RPCs with no params Signed-off-by: puvvadar Signed-off-by: Mikhail Naumov * Adds Inspector to Gem Repo Screen (#4242) * Adds the gem repo screen with the UI built but with mocked data and not connected to the o3de scripts Signed-off-by: nggieber * Changed name of added to enabled, disabled define, removed unused functions Signed-off-by: nggieber * Added Repo Screen Inspector UI Signed-off-by: nggieber * Addressed minor PR feedback Signed-off-by: nggieber * Add some more minor PR changes Signed-off-by: nggieber Signed-off-by: Mikhail Naumov * Updated NetworkSpawnableHolderComponent to use TransformBus instead of FindComponent. Added dependency on TransformService Signed-off-by: pereslav Signed-off-by: Mikhail Naumov * Fixed Entity::GetTransform to work for the components of an activating entity. Made the cached transform lazy evaluated. Signed-off-by: pereslav Signed-off-by: Mikhail Naumov * Changed NetworkSpawnableHolderComponent to use Entity::GetTransform instead of an ebus call Signed-off-by: pereslav Signed-off-by: Mikhail Naumov * Moved local variable to a smaller scope Signed-off-by: pereslav Signed-off-by: Mikhail Naumov * Signed-off-by: LesaelR (#4278) * Signed-off-by: LesaelR * Removing the un-needed sandbox marks. Signed-off-by: LesaelR Signed-off-by: Mikhail Naumov * Small fix for color node tooltip to ask for values 0-1 instead of 0-255 Signed-off-by: Gene Walters Signed-off-by: Mikhail Naumov * Exposing NetworkCharacterComponent::TryMoveWithVelocity to script. Updating Multiplayer AutoComponent baseclass behavior context to Reflect itself instead of its derived (human made) component. This is so the derived class can also create behaviorcontext classes of its own if needed. Misc copyright header edit. Signed-off-by: Gene Walters Signed-off-by: Mikhail Naumov * Make sure Multiplayer AutoComponents dont generate property OnChange script events if GenerateEventBindings is disabled Signed-off-by: Gene Walters Signed-off-by: Mikhail Naumov * Adding parameter names to NetworkCharacterComponent script events so people know what the parameters are used for in scriptcanvas Signed-off-by: Gene Walters Signed-off-by: Mikhail Naumov * Small fix to use AZ_CRC_CE Signed-off-by: Gene Walters Signed-off-by: Mikhail Naumov * Disabling some tests when prefab mode is enabled Signed-off-by: Mikhail Naumov * Revert "Disabling some tests when prefab mode is enabled" This reverts commit 3fe9358d42ec9d7fa5ffaee458e4c6ca3ecbed6e. Signed-off-by: Mikhail Naumov * Revert "PR feedback" This reverts commit a5b86d1954974b950b7719d1bc7dd56bc2a2a21b. Signed-off-by: Mikhail Naumov * Revert "removing tab" This reverts commit ff6ef4bfb514d82a1b668baa95901ad363e648d0. Signed-off-by: Mikhail Naumov * Revert "PR feedback from Ram" This reverts commit cba7f9c2a114d3bb51c9b64f38a7e1fef7bb8fe8. Signed-off-by: Mikhail Naumov * Renaming level->rootInstance Signed-off-by: Mikhail Naumov Co-authored-by: Steve Pham <82231385+spham-amzn@users.noreply.github.com> Co-authored-by: Danilo Aimini <82231674+AMZN-daimini@users.noreply.github.com> Co-authored-by: Qing Tao <55564570+VickyAtAZ@users.noreply.github.com> Co-authored-by: Tommy Walton <82672795+amzn-tommy@users.noreply.github.com> Co-authored-by: SJ Co-authored-by: AMZN-Olex <5432499+AMZN-Olex@users.noreply.github.com> Co-authored-by: Scott Romero <24445312+AMZN-ScottR@users.noreply.github.com> Co-authored-by: amzn-tommy Co-authored-by: dmcdiar Co-authored-by: Benjamin Jillich <43751992+amzn-jillich@users.noreply.github.com> Co-authored-by: Alex Peterson <26804013+AMZN-alexpete@users.noreply.github.com> Co-authored-by: puvvadar Co-authored-by: AMZN-nggieber <52797929+AMZN-nggieber@users.noreply.github.com> Co-authored-by: pereslav Co-authored-by: LesaelR <89800757+LesaelR@users.noreply.github.com> Co-authored-by: Gene Walters --- .../Entity/PrefabEditorEntityOwnershipInterface.h | 2 ++ .../Entity/PrefabEditorEntityOwnershipService.cpp | 9 +++++++++ .../Entity/PrefabEditorEntityOwnershipService.h | 2 ++ .../AzToolsFramework/Prefab/PrefabPublicHandler.cpp | 5 +++++ 4 files changed, 18 insertions(+) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipInterface.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipInterface.h index feb2fc12bf..3bd6e656ed 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipInterface.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipInterface.h @@ -56,5 +56,7 @@ namespace AzToolsFramework virtual void StopPlayInEditor() = 0; virtual void CreateNewLevelPrefab(AZStd::string_view filename, const AZStd::string& templateFilename) = 0; + + virtual bool IsRootPrefabAssigned() const = 0; }; } diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipService.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipService.cpp index 0f2b896b40..e5daf2674d 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipService.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipService.cpp @@ -79,6 +79,8 @@ namespace AzToolsFramework void PrefabEditorEntityOwnershipService::Reset() { + m_isRootPrefabAssigned = false; + if (m_rootInstance) { AzToolsFramework::ToolsApplicationRequestBus::Broadcast( @@ -203,6 +205,7 @@ namespace AzToolsFramework m_rootInstance->SetTemplateSourcePath(m_loaderInterface->GenerateRelativePath(filename)); m_rootInstance->SetContainerEntityName("Level"); m_prefabSystemComponent->PropagateTemplateChanges(templateId); + m_isRootPrefabAssigned = true; return true; } @@ -302,6 +305,12 @@ namespace AzToolsFramework } m_prefabSystemComponent->PropagateTemplateChanges(templateId); + m_isRootPrefabAssigned = true; + } + + bool PrefabEditorEntityOwnershipService::IsRootPrefabAssigned() const + { + return m_isRootPrefabAssigned; } Prefab::InstanceOptionalReference PrefabEditorEntityOwnershipService::CreatePrefab( diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipService.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipService.h index a98fce8059..a172e9550c 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipService.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipService.h @@ -167,6 +167,7 @@ namespace AzToolsFramework void StopPlayInEditor() override; void CreateNewLevelPrefab(AZStd::string_view filename, const AZStd::string& templateFilename) override; + bool IsRootPrefabAssigned() const override; protected: @@ -215,5 +216,6 @@ namespace AzToolsFramework Prefab::PrefabLoaderInterface* m_loaderInterface; AzFramework::EntityContextId m_entityContextId; AZ::SerializeContext m_serializeContext; + bool m_isRootPrefabAssigned = false; }; } diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp index 63f042be85..41538d54e8 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp @@ -329,6 +329,11 @@ namespace AzToolsFramework return AZ::Failure(AZStd::string("Could not instantiate prefab - internal error " "(PrefabEditorEntityOwnershipInterface unavailable).")); } + if (!prefabEditorEntityOwnershipInterface->IsRootPrefabAssigned()) + { + return AZ::Failure(AZStd::string("Could not instantiate prefab - no root prefab assigned. " + "Currently, prefabs can only be instantiated inside a level")); + } InstanceOptionalReference instanceToParentUnder; From 9f1a30817783671ecf544959190097c2adc3d5e4 Mon Sep 17 00:00:00 2001 From: Alex Peterson <26804013+AMZN-alexpete@users.noreply.github.com> Date: Tue, 28 Sep 2021 13:15:24 -0700 Subject: [PATCH 48/51] Fix transparency issue on MacOS (#4358) Signed-off-by: AMZN-alexpete <26804013+AMZN-alexpete@users.noreply.github.com> --- Code/Tools/ProjectManager/Source/ProjectsScreen.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Code/Tools/ProjectManager/Source/ProjectsScreen.cpp b/Code/Tools/ProjectManager/Source/ProjectsScreen.cpp index 81d4047db5..b313c05230 100644 --- a/Code/Tools/ProjectManager/Source/ProjectsScreen.cpp +++ b/Code/Tools/ProjectManager/Source/ProjectsScreen.cpp @@ -358,8 +358,9 @@ namespace O3DE::ProjectManager painter.drawPixmap(backgroundRect, m_background); // Draw a semi-transparent overlay to darken down the colors. - painter.setCompositionMode (QPainter::CompositionMode_DestinationIn); - const float overlayTransparency = 0.7f; + // Use SourceOver, DestinationIn will make background transparent on Mac + painter.setCompositionMode (QPainter::CompositionMode_SourceOver); + const float overlayTransparency = 0.3f; painter.fillRect(backgroundRect, QColor(0, 0, 0, static_cast(255.0f * overlayTransparency))); } From 8791ae7ed7562a83b388ec659cb4e5aaa63f9a8b Mon Sep 17 00:00:00 2001 From: Ken Pruiksma Date: Tue, 28 Sep 2021 15:31:59 -0500 Subject: [PATCH 49/51] Terrain feature processor improvements regarding material, mesh, and lod (#4303) * Terrain feature processor improvements regarding material, mesh, and lod - Now using a material with pbr lighting for terrain. Removed some of the old shader code that wasn't needed anymore - Move heightmap image declaration to the material so it's not needed in the object SRG anymore - Added prototype code (commented out) for smoothing the terrain with a b-spline weighting function - Mesh data is all made with a proper mesh asset now. - Added basic LOD support (no continuous LOD yet, but it does pop between lod levels) - Moved RenderCommon to be accessible publicly. It contains stencil refs that should be public. Signed-off-by: Ken Pruiksma * Fixing terrain's per object srg because of changes in the default per object srg. Signed-off-by: Ken Pruiksma * PR Fixes Signed-off-by: Ken Pruiksma * Removing unused static. Signed-off-by: Ken Pruiksma --- .../Atom/Feature}/RenderCommon.h | 0 .../CoreLights/CoreLightsSystemComponent.cpp | 3 +- .../Code/Source/Mesh/MeshFeatureProcessor.cpp | 3 +- .../Source/ReflectionProbe/ReflectionProbe.h | 2 +- .../atom_feature_common_editor_files.cmake | 2 +- .../Code/atom_feature_common_files.cmake | 2 +- .../Terrain/DefaultPbrTerrain.material | 11 + .../Materials/Terrain/PbrTerrain.materialtype | 140 +++++ .../Assets/Shaders/Terrain/Terrain.azsl | 95 ---- .../Assets/Shaders/Terrain/Terrain.shader | 33 -- .../Shaders/Terrain/TerrainCommon.azsli | 154 +++++- .../Terrain/TerrainPBR_ForwardPass.azsl | 134 +++++ .../Terrain/TerrainPBR_ForwardPass.shader | 42 ++ .../Shaders/Terrain/Terrain_DepthPass.azsl | 1 + .../Shaders/Terrain/Terrain_Shadowmap.shader | 26 + Gems/Terrain/Code/CMakeLists.txt | 1 + .../TerrainFeatureProcessor.cpp | 506 ++++++++---------- .../TerrainRenderer/TerrainFeatureProcessor.h | 128 ++--- 18 files changed, 770 insertions(+), 513 deletions(-) rename Gems/Atom/Feature/Common/Code/{Source => Include/Atom/Feature}/RenderCommon.h (100%) create mode 100644 Gems/Terrain/Assets/Materials/Terrain/DefaultPbrTerrain.material create mode 100644 Gems/Terrain/Assets/Materials/Terrain/PbrTerrain.materialtype delete mode 100644 Gems/Terrain/Assets/Shaders/Terrain/Terrain.azsl delete mode 100644 Gems/Terrain/Assets/Shaders/Terrain/Terrain.shader create mode 100644 Gems/Terrain/Assets/Shaders/Terrain/TerrainPBR_ForwardPass.azsl create mode 100644 Gems/Terrain/Assets/Shaders/Terrain/TerrainPBR_ForwardPass.shader create mode 100644 Gems/Terrain/Assets/Shaders/Terrain/Terrain_Shadowmap.shader diff --git a/Gems/Atom/Feature/Common/Code/Source/RenderCommon.h b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/RenderCommon.h similarity index 100% rename from Gems/Atom/Feature/Common/Code/Source/RenderCommon.h rename to Gems/Atom/Feature/Common/Code/Include/Atom/Feature/RenderCommon.h diff --git a/Gems/Atom/Feature/Common/Code/Source/CoreLights/CoreLightsSystemComponent.cpp b/Gems/Atom/Feature/Common/Code/Source/CoreLights/CoreLightsSystemComponent.cpp index a2c6b8a67e..947028610c 100644 --- a/Gems/Atom/Feature/Common/Code/Source/CoreLights/CoreLightsSystemComponent.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/CoreLights/CoreLightsSystemComponent.cpp @@ -12,6 +12,7 @@ #include #include +#include #include #include #include @@ -33,8 +34,6 @@ #include #include -#include - namespace AZ { namespace Render diff --git a/Gems/Atom/Feature/Common/Code/Source/Mesh/MeshFeatureProcessor.cpp b/Gems/Atom/Feature/Common/Code/Source/Mesh/MeshFeatureProcessor.cpp index 3fcda71998..cced38c3a7 100644 --- a/Gems/Atom/Feature/Common/Code/Source/Mesh/MeshFeatureProcessor.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/Mesh/MeshFeatureProcessor.cpp @@ -6,10 +6,9 @@ * */ -#include - #include #include +#include #include #include #include diff --git a/Gems/Atom/Feature/Common/Code/Source/ReflectionProbe/ReflectionProbe.h b/Gems/Atom/Feature/Common/Code/Source/ReflectionProbe/ReflectionProbe.h index 5b56b70ec8..bee304c5b9 100644 --- a/Gems/Atom/Feature/Common/Code/Source/ReflectionProbe/ReflectionProbe.h +++ b/Gems/Atom/Feature/Common/Code/Source/ReflectionProbe/ReflectionProbe.h @@ -9,7 +9,7 @@ #pragma once #include -#include +#include #include #include #include diff --git a/Gems/Atom/Feature/Common/Code/atom_feature_common_editor_files.cmake b/Gems/Atom/Feature/Common/Code/atom_feature_common_editor_files.cmake index 59a29f04b6..8262500ca1 100644 --- a/Gems/Atom/Feature/Common/Code/atom_feature_common_editor_files.cmake +++ b/Gems/Atom/Feature/Common/Code/atom_feature_common_editor_files.cmake @@ -7,6 +7,7 @@ # set(FILES + Include/Atom/Feature/RenderCommon.h Include/Atom/Feature/Utils/EditorRenderComponentAdapter.h Include/Atom/Feature/Utils/EditorRenderComponentAdapter.inl Include/Atom/Feature/Utils/EditorLightingPreset.h @@ -16,7 +17,6 @@ set(FILES Source/EditorCommonSystemComponent.cpp Source/EditorCommonSystemComponent.h Source/CommonModule.cpp - Source/RenderCommon.h Source/Material/ConvertEmissiveUnitFunctorSourceData.cpp Source/Material/ConvertEmissiveUnitFunctorSourceData.h Source/Material/MaterialConverterSystemComponent.cpp diff --git a/Gems/Atom/Feature/Common/Code/atom_feature_common_files.cmake b/Gems/Atom/Feature/Common/Code/atom_feature_common_files.cmake index 92f96a591b..9372d6594a 100644 --- a/Gems/Atom/Feature/Common/Code/atom_feature_common_files.cmake +++ b/Gems/Atom/Feature/Common/Code/atom_feature_common_files.cmake @@ -8,6 +8,7 @@ set(FILES 3rdParty/ACES/ACES/Aces.h + Include/Atom/Feature/RenderCommon.h Include/Atom/Feature/ACES/AcesDisplayMapperFeatureProcessor.h Include/Atom/Feature/Automation/AtomAutomationBus.h Include/Atom/Feature/AuxGeom/AuxGeomFeatureProcessor.h @@ -52,7 +53,6 @@ set(FILES Source/FrameCaptureSystemComponent.h Source/ProfilingCaptureSystemComponent.cpp Source/ProfilingCaptureSystemComponent.h - Source/RenderCommon.h 3rdParty/ACES/ACES/Aces.cpp Source/ACES/AcesDisplayMapperFeatureProcessor.cpp Source/AuxGeom/AuxGeomBase.h diff --git a/Gems/Terrain/Assets/Materials/Terrain/DefaultPbrTerrain.material b/Gems/Terrain/Assets/Materials/Terrain/DefaultPbrTerrain.material new file mode 100644 index 0000000000..095fbba21a --- /dev/null +++ b/Gems/Terrain/Assets/Materials/Terrain/DefaultPbrTerrain.material @@ -0,0 +1,11 @@ +{ + "description": "", + "materialType": "PbrTerrain.materialtype", + "parentMaterial": "", + "propertyLayoutVersion": 1, + "properties": { + "settings": { + "baseColor": [ 0.78, 0.59, 0.48 ] + } + } +} diff --git a/Gems/Terrain/Assets/Materials/Terrain/PbrTerrain.materialtype b/Gems/Terrain/Assets/Materials/Terrain/PbrTerrain.materialtype new file mode 100644 index 0000000000..bf6cbbb850 --- /dev/null +++ b/Gems/Terrain/Assets/Materials/Terrain/PbrTerrain.materialtype @@ -0,0 +1,140 @@ +{ + "description": "A material for rendering terrain with a physically-based rendering (PBR) material shading model.", + "propertyLayout": { + "version": 1, + "groups": [ + { + "id": "settings", + "displayName": "Settings" + } + ], + "properties": { + "general": [ + { + "id": "applySpecularAA", + "displayName": "Apply Specular AA", + "description": "Whether to apply specular anti-aliasing in the shader.", + "type": "Bool", + "defaultValue": false, + "connection": { + "type": "ShaderOption", + "id": "o_applySpecularAA" + } + }, + { + "id": "enableShadows", + "displayName": "Enable Shadows", + "description": "Whether to use the shadow maps.", + "type": "Bool", + "defaultValue": true, + "connection": { + "type": "ShaderOption", + "id": "o_enableShadows" + } + }, + { + "id": "enableDirectionalLights", + "displayName": "Enable Directional Lights", + "description": "Whether to use directional lights.", + "type": "Bool", + "defaultValue": true, + "connection": { + "type": "ShaderOption", + "id": "o_enableDirectionalLights" + } + }, + { + "id": "enablePunctualLights", + "displayName": "Enable Punctual Lights", + "description": "Whether to use punctual lights.", + "type": "Bool", + "defaultValue": true, + "connection": { + "type": "ShaderOption", + "id": "o_enablePunctualLights" + } + }, + { + "id": "enableAreaLights", + "displayName": "Enable Area Lights", + "description": "Whether to use area lights.", + "type": "Bool", + "defaultValue": true, + "connection": { + "type": "ShaderOption", + "id": "o_enableAreaLights" + } + }, + { + "id": "enableIBL", + "displayName": "Enable IBL", + "description": "Whether to use Image Based Lighting (IBL).", + "type": "Bool", + "defaultValue": true, + "connection": { + "type": "ShaderOption", + "id": "o_enableIBL" + } + }, + { + "id": "forwardPassIBLSpecular", + "displayName": "Forward Pass IBL Specular", + "description": "Whether to apply IBL specular in the forward pass.", + "type": "Bool", + "defaultValue": false, + "connection": { + "type": "ShaderOption", + "id": "o_materialUseForwardPassIBLSpecular" + } + } + ], + "settings": [ + { + "id": "heightmapImage", + "displayName": "Heightmap Image", + "description": "Heightmap of the terrain, controlled by the runtime.", + "type": "Image", + "connection": { + "type": "ShaderInput", + "id": "m_heightmapImage" + } + }, + { + "id": "baseColor", + "displayName": "Base Color", + "type": "Color", + "defaultValue": [ 0.18, 0.18, 0.18 ], + "connection": { + "type": "ShaderInput", + "id": "m_baseColor" + } + }, + { + "id": "roughness", + "displayName": "Roughness", + "type": "Float", + "defaultValue": 1.0, + "min": 0.0, + "max": 1.0, + "connection": { + "type": "ShaderInput", + "id": "m_roughness" + } + } + ] + } + }, + "shaders": [ + { + "file": "../../Shaders/Terrain/TerrainPBR_ForwardPass.shader" + }, + { + "file": "../../Shaders/Terrain/Terrain_Shadowmap.shader" + }, + { + "file": "../../Shaders/Terrain/Terrain_DepthPass.shader" + } + ], + "functors": [ + ] +} diff --git a/Gems/Terrain/Assets/Shaders/Terrain/Terrain.azsl b/Gems/Terrain/Assets/Shaders/Terrain/Terrain.azsl deleted file mode 100644 index 50d11ca610..0000000000 --- a/Gems/Terrain/Assets/Shaders/Terrain/Terrain.azsl +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution. - * - * SPDX-License-Identifier: Apache-2.0 OR MIT - * - */ - -#include -#include -#include -#include "TerrainCommon.azsli" - -struct VertexOutput -{ - linear centroid float4 m_position : SV_Position; - float3 m_normal : NORMAL; - float3 m_tangent : TANGENT; - float3 m_bitangent : BITANGENT; - float m_height : UV; -}; - -struct ForwardPassOutput -{ - float4 m_diffuseColor : SV_Target0; //!< RGB = Diffuse Lighting, A = Blend Alpha (for blended surfaces) OR A = special encoding of surfaceScatteringFactor, m_subsurfaceScatteringQuality, o_enableSubsurfaceScattering - float4 m_specularColor : SV_Target1; //!< RGB = Specular Lighting, A = Unused - float4 m_albedo : SV_Target2; //!< RGB = Surface albedo pre-multiplied by other factors that will be multiplied later by diffuse GI, A = specularOcclusion - float4 m_specularF0 : SV_Target3; //!< RGB = Specular F0, A = roughness - float4 m_normal : SV_Target4; //!< RGB10 = EncodeNormalSignedOctahedron(worldNormal), A2 = multiScatterCompensationEnabled -}; - -struct PixelOutput -{ - float4 m_color : SV_Target0; -}; - -VertexOutput MainVS(in VertexInput input) -{ - VertexOutput output; - ObjectSrg::TerrainData terrainData = ObjectSrg::m_terrainData; - - float2 uv = input.m_uv; - float2 origUv = lerp(terrainData.m_uvMin, terrainData.m_uvMax, uv); - output.m_position = GetTerrainProjectedPosition(terrainData, input.m_position, origUv); - - // Calculate normal - float up = GetHeight(origUv + terrainData.m_uvStep * float2( 0.0f, -1.0f)); - float right = GetHeight(origUv + terrainData.m_uvStep * float2( 1.0f, 0.0f)); - float down = GetHeight(origUv + terrainData.m_uvStep * float2( 0.0f, 1.0f)); - float left = GetHeight(origUv + terrainData.m_uvStep * float2(-1.0f, 0.0f)); - - output.m_bitangent = normalize(float3(0.0, terrainData.m_sampleSpacing * 2.0f, down - up)); - output.m_tangent = normalize(float3(terrainData.m_sampleSpacing * 2.0f, 0.0, right - left)); - output.m_normal = cross(output.m_tangent, output.m_bitangent); - - output.m_height = GetHeight(origUv); - return output; -} - -ForwardPassOutput MainPS(in VertexOutput input) -{ - ForwardPassOutput output; - - float3 lightDirection = normalize(float3(-1.0, 1.0, -1.0)); - float3 lightIntensity = float3(1.0, 1.0, 1.0); - if (SceneSrg::m_directionalLightCount > 0) - { - lightDirection = SceneSrg::m_directionalLights[0].m_direction; - lightIntensity = SceneSrg::m_directionalLights[0].m_rgbIntensityLux; - } - - // Fake light intensity ranges from 1.0 for normals directly facing the light to zero for those - // directly facing away. - const float minLight = 0.01; - const float midLight = 0.1; - float lightDot = dot(normalize(input.m_normal), -lightDirection); - lightIntensity *= lightDot > 0.0 ? - lightDot * (1.0 - midLight) + midLight : // surface facing light - (lightDot + 1.0) * (midLight - minLight) + minLight; // surface facing away - - output.m_diffuseColor.rgb = 0.5 * lightIntensity; - output.m_diffuseColor.a = 0.0f; - - output.m_specularColor.rgb = 0.0; - - output.m_albedo.rgb = 0.25 + input.m_height * 0.5; - output.m_albedo.a = 0.0; - - output.m_specularF0.rgb = 0.04; - output.m_specularF0.a = 1.0; - - output.m_normal.rgb = input.m_normal; - output.m_normal.a = 0.0; - - return output; -} diff --git a/Gems/Terrain/Assets/Shaders/Terrain/Terrain.shader b/Gems/Terrain/Assets/Shaders/Terrain/Terrain.shader deleted file mode 100644 index e844a54a21..0000000000 --- a/Gems/Terrain/Assets/Shaders/Terrain/Terrain.shader +++ /dev/null @@ -1,33 +0,0 @@ -{ - "Source" : "./Terrain.azsl", - - "DepthStencilState" : - { - "Depth" : - { - "Enable" : true, - "CompareFunc" : "GreaterEqual" - } - }, - - "DrawList" : "forward", - - "ProgramSettings": - { - "EntryPoints": - [ - { - "name": "MainVS", - "type": "Vertex" - }, - { - "name": "MainPS", - "type": "Fragment" - } - ] - }, - - "BlendState" : { - "Enable" : false - } -} diff --git a/Gems/Terrain/Assets/Shaders/Terrain/TerrainCommon.azsli b/Gems/Terrain/Assets/Shaders/Terrain/TerrainCommon.azsli index 18b85bbd43..9df7b35ce8 100644 --- a/Gems/Terrain/Assets/Shaders/Terrain/TerrainCommon.azsli +++ b/Gems/Terrain/Assets/Shaders/Terrain/TerrainCommon.azsli @@ -9,18 +9,6 @@ ShaderResourceGroup ObjectSrg : SRG_PerObject { - Texture2D m_heightmapImage; - - Sampler PointSampler - { - MinFilter = Point; - MagFilter = Point; - MipFilter = Point; - AddressU = Clamp; - AddressV = Clamp; - AddressW = Clamp; - }; - row_major float3x4 m_modelToWorld; struct TerrainData @@ -33,14 +21,131 @@ ShaderResourceGroup ObjectSrg : SRG_PerObject }; TerrainData m_terrainData; + + // The below shouldn't be in this SRG but needs to be for now because the lighting functions depend on them. + + //! Reflection Probe (smallest probe volume that overlaps the object position) + struct ReflectionProbeData + { + row_major float3x4 m_modelToWorld; + row_major float3x4 m_modelToWorldInverse; // does not include extents + float3 m_outerObbHalfLengths; + float3 m_innerObbHalfLengths; + float m_padding; + bool m_useReflectionProbe; + bool m_useParallaxCorrection; + }; + + ReflectionProbeData m_reflectionProbeData; + TextureCube m_reflectionProbeCubeMap; + + float4x4 GetReflectionProbeWorldMatrix() + { + float4x4 modelToWorld = float4x4( + float4(1, 0, 0, 0), + float4(0, 1, 0, 0), + float4(0, 0, 1, 0), + float4(0, 0, 0, 1)); + + modelToWorld[0] = m_reflectionProbeData.m_modelToWorld[0]; + modelToWorld[1] = m_reflectionProbeData.m_modelToWorld[1]; + modelToWorld[2] = m_reflectionProbeData.m_modelToWorld[2]; + return modelToWorld; + } + + float4x4 GetReflectionProbeWorldMatrixInverse() + { + float4x4 modelToWorldInverse = float4x4( + float4(1, 0, 0, 0), + float4(0, 1, 0, 0), + float4(0, 0, 1, 0), + float4(0, 0, 0, 1)); + + modelToWorldInverse[0] = m_reflectionProbeData.m_modelToWorldInverse[0]; + modelToWorldInverse[1] = m_reflectionProbeData.m_modelToWorldInverse[1]; + modelToWorldInverse[2] = m_reflectionProbeData.m_modelToWorldInverse[2]; + return modelToWorldInverse; + } } +ShaderResourceGroup TerrainMaterialSrg : SRG_PerMaterial +{ + Texture2D m_heightmapImage; + + Sampler HeightmapSampler + { + MinFilter = Linear; + MagFilter = Linear; + MipFilter = Point; + AddressU = Clamp; + AddressV = Clamp; + AddressW = Clamp; + }; + + float3 m_baseColor; + float m_roughness; +} + +option bool o_useTerrainSmoothing = false; + struct VertexInput { float2 m_position : POSITION; float2 m_uv : UV; }; +// Sample a texture with a 5 tap B-Spline. Consider ripping this out and putting in a more general location. +// This function samples a 4x4 neighborhood around the uv. Normally this would take 16 samples, but by taking +// advantage of bilinear filtering this can be done with 9 taps on the edges between pixels. The cost is further +// reduced by dropping the diagonals. +float SampleBSpline5Tap(Texture2D texture, SamplerState textureSampler, float2 uv, float2 textureSize, float2 rcpTextureSize) +{ + // Think of sample locations in the 4x4 neighborhood as having a top left coordinate of 0,0 and + // a bottom right coordinate of 3,3. + + // Find the position in texture space then round it to get the center of the 1,1 pixel (tc1) + float2 texelPos = uv * textureSize; + float2 tc1= floor(texelPos - 0.5) + 0.5; + + // Offset from center position to texel + float2 f = texelPos - tc1; + + // Compute B-Spline weights based on the offset + float2 OneMinusF = (1.0 - f); + float2 OneMinusF2 = OneMinusF * OneMinusF; + float2 OneMinusF3 = OneMinusF2 * OneMinusF; + float2 w0 = OneMinusF3; + float2 w1 = 4.0 + 3.0 * f * f * f - 6.0 * f * f; + float2 w2 = 4.0 + 3.0 * OneMinusF3 - 6.0 * OneMinusF2; + float2 w3 = f * f * f; + + float2 w12 = w1 + w2; + + // Compute uv coordinates for sampling the texture + float2 tc0 = (tc1 - 1.0f) * rcpTextureSize; + float2 tc3 = (tc1 + 2.0f) * rcpTextureSize; + float2 tc12 = (tc1 + w2 / w12) * rcpTextureSize; + + // Compute sample weights + float sw0 = w12.x * w12.y; // middle + float sw1 = w12.x * w0.y; // top + float sw2 = w0.x * w12.y; // left + float sw3 = w12.x * w3.y; // bottom + float sw4 = w3.x * w12.y; // right + + // total weight of samples to normalize result. + float totalWeight = sw0 + sw1 + sw2 + sw3 + sw4; + + float result = 0.0f; + result += texture.SampleLevel(textureSampler, float2(tc12.x, tc12.y), 0.0).r * sw0; + result += texture.SampleLevel(textureSampler, float2(tc12.x, tc0.y), 0.0).r * sw1; + result += texture.SampleLevel(textureSampler, float2( tc0.x, tc12.y), 0.0).r * sw2; + result += texture.SampleLevel(textureSampler, float2(tc12.x, tc3.y), 0.0).r * sw3; + result += texture.SampleLevel(textureSampler, float2( tc3.x, tc12.y), 0.0).r * sw4; + + return result / totalWeight; +} + float4x4 GetObject_WorldMatrix() { float4x4 modelToWorld = float4x4( @@ -58,10 +163,23 @@ float4x4 GetObject_WorldMatrix() float GetHeight(float2 origUv) { float2 uv = clamp(origUv + (ObjectSrg::m_terrainData.m_uvStep * 0.5f), 0.0f, 1.0f); - return ObjectSrg::m_terrainData.m_heightScale * (ObjectSrg::m_heightmapImage.SampleLevel(ObjectSrg::PointSampler, uv, 0).r - 0.5f); + float height = 0.0f; + + if (o_useTerrainSmoothing) + { + float2 textureSize; + TerrainMaterialSrg::m_heightmapImage.GetDimensions(textureSize.x, textureSize.y); + height = SampleBSpline5Tap(TerrainMaterialSrg::m_heightmapImage, TerrainMaterialSrg::HeightmapSampler, uv, textureSize, rcp(textureSize)); + } + else + { + height = TerrainMaterialSrg::m_heightmapImage.SampleLevel(TerrainMaterialSrg::HeightmapSampler, uv, 0).r; + } + + return ObjectSrg::m_terrainData.m_heightScale * (height - 0.5f); } -float4 GetTerrainProjectedPosition(ObjectSrg::TerrainData terrainData, float2 vertexPosition, float2 uv) +float3 GetTerrainWorldPosition(ObjectSrg::TerrainData terrainData, float2 vertexPosition, float2 uv) { // Remove all vertices outside our bounds by turning them into NaN positions. if (any(uv > 1.0) || any (uv < 0.0)) @@ -71,6 +189,10 @@ float4 GetTerrainProjectedPosition(ObjectSrg::TerrainData terrainData, float2 ve // Loop up the height and calculate our final position. float height = GetHeight(uv); - float4 worldPosition = mul(GetObject_WorldMatrix(), float4(vertexPosition, height, 1.0f)); - return mul(ViewSrg::m_viewProjectionMatrix, worldPosition); + return mul(GetObject_WorldMatrix(), float4(vertexPosition, height, 1.0f)).xyz; +} + +float4 GetTerrainProjectedPosition(ObjectSrg::TerrainData terrainData, float2 vertexPosition, float2 uv) +{ + return mul(ViewSrg::m_viewProjectionMatrix, float4(GetTerrainWorldPosition(terrainData, vertexPosition, uv), 1.0)); } diff --git a/Gems/Terrain/Assets/Shaders/Terrain/TerrainPBR_ForwardPass.azsl b/Gems/Terrain/Assets/Shaders/Terrain/TerrainPBR_ForwardPass.azsl new file mode 100644 index 0000000000..d77b944498 --- /dev/null +++ b/Gems/Terrain/Assets/Shaders/Terrain/TerrainPBR_ForwardPass.azsl @@ -0,0 +1,134 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct VSOutput +{ + float4 m_position : SV_Position; + float3 m_normal: NORMAL; + float3 m_tangent : TANGENT; + float3 m_bitangent : BITANGENT; + float3 m_worldPosition : UV0; + float3 m_shadowCoords[ViewSrg::MaxCascadeCount] : UV2; + float2 m_uv : UV1; +}; + +VSOutput TerrainPBR_MainPassVS(VertexInput IN) +{ + VSOutput OUT; + + ObjectSrg::TerrainData terrainData = ObjectSrg::m_terrainData; + + float2 uv = IN.m_uv; + float2 origUv = lerp(terrainData.m_uvMin, terrainData.m_uvMax, uv); + float3 worldPosition = GetTerrainWorldPosition(terrainData, IN.m_position, origUv); + OUT.m_position = mul(ViewSrg::m_viewProjectionMatrix, float4(worldPosition, 1.0)); + OUT.m_worldPosition = worldPosition; + + // Calculate normal + float up = GetHeight(origUv + terrainData.m_uvStep * float2( 0.0f, -1.0f)); + float right = GetHeight(origUv + terrainData.m_uvStep * float2( 1.0f, 0.0f)); + float down = GetHeight(origUv + terrainData.m_uvStep * float2( 0.0f, 1.0f)); + float left = GetHeight(origUv + terrainData.m_uvStep * float2(-1.0f, 0.0f)); + + OUT.m_bitangent = normalize(float3(0.0, terrainData.m_sampleSpacing * 2.0f, down - up)); + OUT.m_tangent = normalize(float3(terrainData.m_sampleSpacing * 2.0f, 0.0, right - left)); + OUT.m_normal = cross(OUT.m_tangent, OUT.m_bitangent); + OUT.m_uv = uv; + + // directional light shadow + const uint shadowIndex = ViewSrg::m_shadowIndexDirectionalLight; + if (o_enableShadows && shadowIndex < SceneSrg::m_directionalLightCount) + { + DirectionalLightShadow::GetShadowCoords( + shadowIndex, + worldPosition, + OUT.m_shadowCoords); + } + + return OUT; +} + +ForwardPassOutput TerrainPBR_MainPassPS(VSOutput IN) +{ + // ------- Surface ------- + + Surface surface; + + // Position, Normal, Roughness + surface.position = IN.m_worldPosition.xyz; + surface.normal = normalize(IN.m_normal); + surface.roughnessLinear = TerrainMaterialSrg::m_roughness; + surface.CalculateRoughnessA(); + + // Albedo, SpecularF0 + const float specularF0Factor = 0.5f; + float3 color = TerrainMaterialSrg::m_baseColor; + surface.SetAlbedoAndSpecularF0(color, specularF0Factor, 0.0); + + // Clear Coat, Transmission + surface.clearCoat.InitializeToZero(); + surface.transmission.InitializeToZero(); + + // ------- LightingData ------- + + LightingData lightingData; + + // Light iterator + lightingData.tileIterator.Init(IN.m_position, PassSrg::m_lightListRemapped, PassSrg::m_tileLightData); + lightingData.Init(surface.position, surface.normal, surface.roughnessLinear); + + // Shadow, Occlusion + lightingData.shadowCoords = IN.m_shadowCoords; + + // Diffuse and Specular response + lightingData.specularResponse = FresnelSchlickWithRoughness(lightingData.NdotV, surface.specularF0, surface.roughnessLinear); + lightingData.diffuseResponse = 1.0f - lightingData.specularResponse; + + const float alpha = 1.0f; + + // ------- Lighting Calculation ------- + + // Apply Decals + ApplyDecals(lightingData.tileIterator, surface); + + // Apply Direct Lighting + ApplyDirectLighting(surface, lightingData); + + // Apply Image Based Lighting (IBL) + ApplyIBL(surface, lightingData); + + // Finalize Lighting + lightingData.FinalizeLighting(surface.transmission.tint); + + PbrLightingOutput lightingOutput = GetPbrLightingOutput(surface, lightingData, alpha); + + // ------- Output ------- + + ForwardPassOutput OUT; + + OUT.m_diffuseColor = lightingOutput.m_diffuseColor; + OUT.m_diffuseColor.w = -1; // Subsurface scattering is disabled + OUT.m_specularColor = lightingOutput.m_specularColor; + OUT.m_specularF0 = lightingOutput.m_specularF0; + OUT.m_albedo = lightingOutput.m_albedo; + OUT.m_normal = lightingOutput.m_normal; + + return OUT; +} diff --git a/Gems/Terrain/Assets/Shaders/Terrain/TerrainPBR_ForwardPass.shader b/Gems/Terrain/Assets/Shaders/Terrain/TerrainPBR_ForwardPass.shader new file mode 100644 index 0000000000..0e6f0beb1d --- /dev/null +++ b/Gems/Terrain/Assets/Shaders/Terrain/TerrainPBR_ForwardPass.shader @@ -0,0 +1,42 @@ +{ + "Source" : "./TerrainPBR_ForwardPass.azsl", + + "DepthStencilState" : + { + "Depth" : + { + "Enable" : true, + "CompareFunc" : "GreaterEqual" + }, + "Stencil" : + { + "Enable" : true, + "ReadMask" : "0x00", + "WriteMask" : "0xFF", + "FrontFace" : + { + "Func" : "Always", + "DepthFailOp" : "Keep", + "FailOp" : "Keep", + "PassOp" : "Replace" + } + } + }, + + "ProgramSettings": + { + "EntryPoints": + [ + { + "name": "TerrainPBR_MainPassVS", + "type": "Vertex" + }, + { + "name": "TerrainPBR_MainPassPS", + "type": "Fragment" + } + ] + }, + + "DrawList" : "forward" +} diff --git a/Gems/Terrain/Assets/Shaders/Terrain/Terrain_DepthPass.azsl b/Gems/Terrain/Assets/Shaders/Terrain/Terrain_DepthPass.azsl index 20c56323ac..fd855a1bcd 100644 --- a/Gems/Terrain/Assets/Shaders/Terrain/Terrain_DepthPass.azsl +++ b/Gems/Terrain/Assets/Shaders/Terrain/Terrain_DepthPass.azsl @@ -8,6 +8,7 @@ #include #include #include "TerrainCommon.azsli" +#include struct VSDepthOutput { diff --git a/Gems/Terrain/Assets/Shaders/Terrain/Terrain_Shadowmap.shader b/Gems/Terrain/Assets/Shaders/Terrain/Terrain_Shadowmap.shader new file mode 100644 index 0000000000..290c83bddf --- /dev/null +++ b/Gems/Terrain/Assets/Shaders/Terrain/Terrain_Shadowmap.shader @@ -0,0 +1,26 @@ +{ + "Source" : "Terrain_DepthPass", + + "DepthStencilState" : { + "Depth" : { "Enable" : true, "CompareFunc" : "LessEqual" } + }, + + "DrawList" : "shadow", + + "RasterState" : + { + "depthBias" : "10", + "depthBiasSlopeScale" : "4" + }, + + "ProgramSettings": + { + "EntryPoints": + [ + { + "name": "MainVS", + "type": "Vertex" + } + ] + } +} diff --git a/Gems/Terrain/Code/CMakeLists.txt b/Gems/Terrain/Code/CMakeLists.txt index fc07294dab..17ac3d8474 100644 --- a/Gems/Terrain/Code/CMakeLists.txt +++ b/Gems/Terrain/Code/CMakeLists.txt @@ -21,6 +21,7 @@ ly_add_target( AZ::AzFramework Gem::Atom_RPI.Public Gem::Atom_Utils.Static + Gem::Atom_Feature_Common Gem::GradientSignal Gem::SurfaceData Gem::LmbrCentral diff --git a/Gems/Terrain/Code/Source/TerrainRenderer/TerrainFeatureProcessor.cpp b/Gems/Terrain/Code/Source/TerrainRenderer/TerrainFeatureProcessor.cpp index 511b206b84..3e5f9a0608 100644 --- a/Gems/Terrain/Code/Source/TerrainRenderer/TerrainFeatureProcessor.cpp +++ b/Gems/Terrain/Code/Source/TerrainRenderer/TerrainFeatureProcessor.cpp @@ -20,28 +20,39 @@ #include #include #include +#include #include #include +#include #include #include +#include +#include +#include +#include +#include +#include #include #include #include #include #include - +#include namespace Terrain { namespace { - const uint32_t DEFAULT_UploadBufferSize = 512 * 1024; // 512k [[maybe_unused]] const char* TerrainFPName = "TerrainFeatureProcessor"; } + namespace MaterialInputs + { + static const char* const HeightmapImage("settings.heightmapImage"); + } + namespace ShaderInputs { - static const char* const HeightmapImage("m_heightmapImage"); static const char* const ModelToWorld("m_modelToWorld"); static const char* const TerrainData("m_terrainData"); } @@ -60,142 +71,44 @@ namespace Terrain void TerrainFeatureProcessor::Activate() { m_areaData = {}; - - InitializeAtomStuff(); - EnableSceneNotification(); + Initialize(); } - void TerrainFeatureProcessor::ConfigurePipelineState(ShaderState& shaderState, bool assertOnFail) + void TerrainFeatureProcessor::Initialize() { - if (shaderState.m_shader == nullptr) { - AZ_Assert(shaderState.m_shader || !assertOnFail, "Terrain shader failed to load correctly."); - return; - } - - bool success = GetParentScene()->ConfigurePipelineState(shaderState.m_shader->GetDrawListTag(), shaderState.m_pipelineStateDescriptor); - AZ_Assert(success || !assertOnFail, "Couldn't configure the pipeline state."); - if (success) - { - shaderState.m_pipelineState = shaderState.m_shader->AcquirePipelineState(shaderState.m_pipelineStateDescriptor); - AZ_Assert(shaderState.m_pipelineState, "Failed to acquire default pipeline state."); - } - } - - void TerrainFeatureProcessor::InitializeAtomStuff() - { - m_rhiSystem = AZ::RHI::RHISystemInterface::Get(); - - { - auto LoadShader = [this](const char* filePath, ShaderState& shaderState) - { - shaderState.m_shader = AZ::RPI::LoadShader(filePath); - if (!shaderState.m_shader) + // Load the terrain material asynchronously + const AZStd::string materialFilePath = "Materials/Terrain/DefaultPbrTerrain.azmaterial"; + m_materialAssetLoader = AZStd::make_unique(); + *m_materialAssetLoader = AZ::RPI::AssetUtils::AsyncAssetLoader::Create(materialFilePath, 0u, + [&](AZ::Data::Asset assetData, bool success) -> void { - AZ_Error(TerrainFPName, false, "Failed to find or create a shader instance from shader asset '%s'", filePath); - return; + const AZ::Data::Asset& materialAsset = static_cast>(assetData); + if (success) + { + m_materialInstance = AZ::RPI::Material::FindOrCreate(assetData); + if (!materialAsset->GetObjectSrgLayout()) + { + AZ_Error("TerrainFeatureProcessor", false, "No per-object ShaderResourceGroup found on terrain material."); + } + } } - - // Create the data layout - shaderState.m_pipelineStateDescriptor = AZ::RHI::PipelineStateDescriptorForDraw{}; - { - AZ::RHI::InputStreamLayoutBuilder layoutBuilder; - - layoutBuilder.AddBuffer() - ->Channel("POSITION", AZ::RHI::Format::R32G32_FLOAT) - ->Channel("UV", AZ::RHI::Format::R32G32_FLOAT) - ; - shaderState.m_pipelineStateDescriptor.m_inputStreamLayout = layoutBuilder.End(); - } - - auto shaderVariant = shaderState.m_shader->GetVariant(AZ::RPI::ShaderAsset::RootShaderVariantStableId); - shaderVariant.ConfigurePipelineState(shaderState.m_pipelineStateDescriptor); - - // If this fails to run now, it's ok, we'll initialize it in OnRenderPipelineAdded later. - ConfigurePipelineState(shaderState, false); - }; - - LoadShader("Shaders/Terrain/Terrain.azshader", m_shaderStates[ShaderType::Forward]); - LoadShader("Shaders/Terrain/Terrain_DepthPass.azshader", m_shaderStates[ShaderType::Depth]); - - // Forward and depth shader use same srg layout. - AZ::RHI::Ptr perObjectSrgLayout = - m_shaderStates[ShaderType::Forward].m_shader->FindShaderResourceGroupLayout(AZ::Name{"ObjectSrg"}); - - if (!perObjectSrgLayout) - { - AZ_Error(TerrainFPName, false, "Failed to get shader resource group layout"); - return; - } - else if (!perObjectSrgLayout->IsFinalized()) - { - AZ_Error(TerrainFPName, false, "Shader resource group layout is not loaded"); - return; - } - - m_heightmapImageIndex = perObjectSrgLayout->FindShaderInputImageIndex(AZ::Name(ShaderInputs::HeightmapImage)); - AZ_Error(TerrainFPName, m_heightmapImageIndex.IsValid(), "Failed to find shader input image %s.", ShaderInputs::HeightmapImage); - - m_modelToWorldIndex = perObjectSrgLayout->FindShaderInputConstantIndex(AZ::Name(ShaderInputs::ModelToWorld)); - AZ_Error(TerrainFPName, m_modelToWorldIndex.IsValid(), "Failed to find shader input constant %s.", ShaderInputs::ModelToWorld); - - m_terrainDataIndex = perObjectSrgLayout->FindShaderInputConstantIndex(AZ::Name(ShaderInputs::TerrainData)); - AZ_Error(TerrainFPName, m_terrainDataIndex.IsValid(), "Failed to find shader input constant %s.", ShaderInputs::TerrainData); + ); } - AZ::RHI::BufferPoolDescriptor dmaPoolDescriptor; - dmaPoolDescriptor.m_heapMemoryLevel = AZ::RHI::HeapMemoryLevel::Host; - dmaPoolDescriptor.m_bindFlags = AZ::RHI::BufferBindFlags::InputAssembly; - - m_hostPool = AZ::RHI::Factory::Get().CreateBufferPool(); - m_hostPool->SetName(AZ::Name("TerrainVertexPool")); - AZ::RHI::ResultCode resultCode = m_hostPool->Init(*m_rhiSystem->GetDevice(), dmaPoolDescriptor); - - if (resultCode != AZ::RHI::ResultCode::Success) - { - AZ_Error(TerrainFPName, false, "Failed to create host buffer pool from RPI"); - return; - } - - InitializeTerrainPatch(); - - if (!InitializeRenderBuffers()) + if (!InitializePatchModel()) { AZ_Error(TerrainFPName, false, "Failed to create Terrain render buffers!"); return; } } - void TerrainFeatureProcessor::OnRenderPipelineAdded([[maybe_unused]] AZ::RPI::RenderPipelinePtr pipeline) - { - for (ShaderState& shaderState: m_shaderStates) - { - ConfigurePipelineState(shaderState, true); - } - } - - void TerrainFeatureProcessor::OnRenderPipelineRemoved([[maybe_unused]] AZ::RPI::RenderPipeline* pipeline) - { - } - - void TerrainFeatureProcessor::OnRenderPipelinePassesChanged([[maybe_unused]] AZ::RPI::RenderPipeline* renderPipeline) - { - } - - void TerrainFeatureProcessor::Deactivate() { DisableSceneNotification(); - DestroyRenderBuffers(); + m_patchModel = {}; m_areaData = {}; - - if (m_hostPool) - { - m_hostPool.reset(); - } - - m_rhiSystem = nullptr; } void TerrainFeatureProcessor::Render(const AZ::RPI::FeatureProcessor::RenderPacket& packet) @@ -254,240 +167,269 @@ namespace Terrain { AZ_PROFILE_FUNCTION(AzRender); - if ((m_shaderStates[ShaderType::Forward].m_shader == nullptr) || - (m_shaderStates[ShaderType::Depth].m_shader == nullptr) || - m_shaderStates[ShaderType::Forward].m_shader->GetDrawListTag().IsNull() || - m_shaderStates[ShaderType::Depth].m_shader->GetDrawListTag().IsNull()) - { - return; - } - if (!m_areaData.m_terrainBounds.IsValid()) { return; } - if (m_areaData.m_propertiesDirty) + if (m_areaData.m_propertiesDirty && m_materialInstance) { m_areaData.m_propertiesDirty = false; m_sectorData.clear(); - AZ::RHI::DrawPacketBuilder drawPacketBuilder; + AZ::RPI::MaterialPropertyIndex heightmapPropertyIndex = + m_materialInstance->GetMaterialPropertiesLayout()->FindPropertyIndex(AZ::Name(MaterialInputs::HeightmapImage)); + AZ_Error(TerrainFPName, heightmapPropertyIndex.IsValid(), "Failed to find material input constant %s.", MaterialInputs::HeightmapImage); + AZ::Data::Instance heightmapImage = m_areaData.m_heightmapImage; + m_materialInstance->SetPropertyValue(heightmapPropertyIndex, heightmapImage); + m_materialInstance->Compile(); - uint32_t numIndices = static_cast(m_gridIndices.size()); + const auto layout = m_materialInstance->GetAsset()->GetObjectSrgLayout(); - AZ::RHI::DrawIndexed drawIndexed; - drawIndexed.m_indexCount = numIndices; - drawIndexed.m_indexOffset = 0; - drawIndexed.m_vertexOffset = 0; + m_modelToWorldIndex = layout->FindShaderInputConstantIndex(AZ::Name(ShaderInputs::ModelToWorld)); + AZ_Error(TerrainFPName, m_modelToWorldIndex.IsValid(), "Failed to find shader input constant %s.", ShaderInputs::ModelToWorld); + + m_terrainDataIndex = layout->FindShaderInputConstantIndex(AZ::Name(ShaderInputs::TerrainData)); + AZ_Error(TerrainFPName, m_terrainDataIndex.IsValid(), "Failed to find shader input constant %s.", ShaderInputs::TerrainData); float xFirstPatchStart = - m_areaData.m_terrainBounds.GetMin().GetX() - fmod(m_areaData.m_terrainBounds.GetMin().GetX(), m_gridMeters); - float xLastPatchStart = m_areaData.m_terrainBounds.GetMax().GetX() - fmod(m_areaData.m_terrainBounds.GetMax().GetX(), m_gridMeters); + m_areaData.m_terrainBounds.GetMin().GetX() - fmod(m_areaData.m_terrainBounds.GetMin().GetX(), GridMeters); + float xLastPatchStart = m_areaData.m_terrainBounds.GetMax().GetX() - fmod(m_areaData.m_terrainBounds.GetMax().GetX(), GridMeters); float yFirstPatchStart = - m_areaData.m_terrainBounds.GetMin().GetY() - fmod(m_areaData.m_terrainBounds.GetMin().GetY(), m_gridMeters); - float yLastPatchStart = m_areaData.m_terrainBounds.GetMax().GetY() - fmod(m_areaData.m_terrainBounds.GetMax().GetY(), m_gridMeters); + m_areaData.m_terrainBounds.GetMin().GetY() - fmod(m_areaData.m_terrainBounds.GetMin().GetY(), GridMeters); + float yLastPatchStart = m_areaData.m_terrainBounds.GetMax().GetY() - fmod(m_areaData.m_terrainBounds.GetMax().GetY(), GridMeters); - for (float yPatch = yFirstPatchStart; yPatch <= yLastPatchStart; yPatch += m_gridMeters) + for (float yPatch = yFirstPatchStart; yPatch <= yLastPatchStart; yPatch += GridMeters) { - for (float xPatch = xFirstPatchStart; xPatch <= xLastPatchStart; xPatch += m_gridMeters) + for (float xPatch = xFirstPatchStart; xPatch <= xLastPatchStart; xPatch += GridMeters) { - drawPacketBuilder.Begin(nullptr); - drawPacketBuilder.SetDrawArguments(drawIndexed); - drawPacketBuilder.SetIndexBufferView(m_indexBufferView); - auto& forwardShader = m_shaderStates[ShaderType::Forward].m_shader; - - auto resourceGroup = AZ::RPI::ShaderResourceGroup::Create(forwardShader->GetAsset(), forwardShader->GetSupervariantIndex(), AZ::Name("ObjectSrg")); - if (!resourceGroup) + const auto& materialAsset = m_materialInstance->GetAsset(); + auto& shaderAsset = materialAsset->GetMaterialTypeAsset()->GetShaderAssetForObjectSrg(); + auto objectSrg = AZ::RPI::ShaderResourceGroup::Create(shaderAsset, materialAsset->GetObjectSrgLayout()->GetName()); + if (!objectSrg) { - AZ_Error(TerrainFPName, false, "Failed to create shader resource group"); - return; + AZ_Warning("TerrainFeatureProcessor", false, "Failed to create a new shader resource group, skipping."); + continue; } - AZStd::array uvMin = { 0.0f, 0.0f }; - AZStd::array uvMax = { 1.0f, 1.0f }; + { // Update SRG + + AZStd::array uvMin = { 0.0f, 0.0f }; + AZStd::array uvMax = { 1.0f, 1.0f }; - uvMin[0] = (float)((xPatch - m_areaData.m_terrainBounds.GetMin().GetX()) / m_areaData.m_terrainBounds.GetXExtent()); - uvMin[1] = (float)((yPatch - m_areaData.m_terrainBounds.GetMin().GetY()) / m_areaData.m_terrainBounds.GetYExtent()); + uvMin[0] = (float)((xPatch - m_areaData.m_terrainBounds.GetMin().GetX()) / m_areaData.m_terrainBounds.GetXExtent()); + uvMin[1] = (float)((yPatch - m_areaData.m_terrainBounds.GetMin().GetY()) / m_areaData.m_terrainBounds.GetYExtent()); - uvMax[0] = - (float)(((xPatch + m_gridMeters) - m_areaData.m_terrainBounds.GetMin().GetX()) / m_areaData.m_terrainBounds.GetXExtent()); - uvMax[1] = - (float)(((yPatch + m_gridMeters) - m_areaData.m_terrainBounds.GetMin().GetY()) / m_areaData.m_terrainBounds.GetYExtent()); + uvMax[0] = + (float)(((xPatch + GridMeters) - m_areaData.m_terrainBounds.GetMin().GetX()) / m_areaData.m_terrainBounds.GetXExtent()); + uvMax[1] = + (float)(((yPatch + GridMeters) - m_areaData.m_terrainBounds.GetMin().GetY()) / m_areaData.m_terrainBounds.GetYExtent()); - AZStd::array uvStep = - { - 1.0f / m_areaData.m_heightmapImageWidth, 1.0f / m_areaData.m_heightmapImageHeight, - }; + AZStd::array uvStep = + { + 1.0f / m_areaData.m_heightmapImageWidth, 1.0f / m_areaData.m_heightmapImageHeight, + }; - AZ::Transform transform = m_areaData.m_transform; - transform.SetTranslation(xPatch, yPatch, m_areaData.m_transform.GetTranslation().GetZ()); + AZ::Transform transform = m_areaData.m_transform; + transform.SetTranslation(xPatch, yPatch, m_areaData.m_transform.GetTranslation().GetZ()); - AZ::Matrix3x4 matrix3x4 = AZ::Matrix3x4::CreateFromTransform(transform); + AZ::Matrix3x4 matrix3x4 = AZ::Matrix3x4::CreateFromTransform(transform); - resourceGroup->SetImage(m_heightmapImageIndex, m_areaData.m_heightmapImage); - resourceGroup->SetConstant(m_modelToWorldIndex, matrix3x4); + objectSrg->SetConstant(m_modelToWorldIndex, matrix3x4); - ShaderTerrainData terrainDataForSrg; - terrainDataForSrg.m_sampleSpacing = m_areaData.m_sampleSpacing; - terrainDataForSrg.m_heightScale = m_areaData.m_heightScale; - terrainDataForSrg.m_uvMin = uvMin; - terrainDataForSrg.m_uvMax = uvMax; - terrainDataForSrg.m_uvStep = uvStep; - resourceGroup->SetConstant(m_terrainDataIndex, terrainDataForSrg); + ShaderTerrainData terrainDataForSrg; + terrainDataForSrg.m_sampleSpacing = m_areaData.m_sampleSpacing; + terrainDataForSrg.m_heightScale = m_areaData.m_heightScale; + terrainDataForSrg.m_uvMin = uvMin; + terrainDataForSrg.m_uvMax = uvMax; + terrainDataForSrg.m_uvStep = uvStep; + objectSrg->SetConstant(m_terrainDataIndex, terrainDataForSrg); - resourceGroup->Compile(); - drawPacketBuilder.AddShaderResourceGroup(resourceGroup->GetRHIShaderResourceGroup()); - - auto addDrawItem = [&](ShaderState& shaderState) - { - AZ::RHI::DrawPacketBuilder::DrawRequest drawRequest; - drawRequest.m_listTag = shaderState.m_shader->GetDrawListTag(); - drawRequest.m_pipelineState = shaderState.m_pipelineState.get(); - drawRequest.m_streamBufferViews = AZStd::array_view(&m_vertexBufferView, 1); - drawPacketBuilder.AddDrawItem(drawRequest); - }; - - for (ShaderState& shaderState : m_shaderStates) - { - addDrawItem(shaderState); + objectSrg->Compile(); } - //addDrawItem(m_shaderStates[ShaderType::Forward]); - - m_sectorData.emplace_back( - drawPacketBuilder.End(), + + m_sectorData.push_back(); + SectorData& sectorData = m_sectorData.back(); + + for (auto& lod : m_patchModel->GetLods()) + { + AZ::RPI::ModelLod& modelLod = *lod.get(); + sectorData.m_drawPackets.emplace_back(modelLod, 0, m_materialInstance, objectSrg); + AZ::RPI::MeshDrawPacket& drawPacket = sectorData.m_drawPackets.back(); + + // set the shader option to select forward pass IBL specular if necessary + if (!drawPacket.SetShaderOption(AZ::Name("o_meshUseForwardPassIBLSpecular"), AZ::RPI::ShaderOptionValue{ false })) + { + AZ_Warning("MeshDrawPacket", false, "Failed to set o_meshUseForwardPassIBLSpecular on mesh draw packet"); + } + uint8_t stencilRef = AZ::Render::StencilRefs::UseDiffuseGIPass | AZ::Render::StencilRefs::UseIBLSpecularPass; + drawPacket.SetStencilRef(stencilRef); + drawPacket.Update(*GetParentScene(), true); + } + + sectorData.m_aabb = AZ::Aabb::CreateFromMinMax( AZ::Vector3(xPatch, yPatch, m_areaData.m_terrainBounds.GetMin().GetZ()), - AZ::Vector3(xPatch + m_gridMeters, yPatch + m_gridMeters, m_areaData.m_terrainBounds.GetMax().GetZ()) - ), - resourceGroup - ); + AZ::Vector3(xPatch + GridMeters, yPatch + GridMeters, m_areaData.m_terrainBounds.GetMax().GetZ()) + ); + sectorData.m_srg = objectSrg; } } } - - for (auto& view : process.m_views) + + for (auto& sectorData : m_sectorData) { - AZ::Frustum viewFrustum = AZ::Frustum::CreateFromMatrixColumnMajor(view->GetWorldToClipMatrix()); - for (auto& sectorData : m_sectorData) + uint8_t lodChoice = AZ::RPI::ModelLodAsset::LodCountMax; + + // Go through all cameras and choose an LOD based on the closest camera. + for (auto& view : process.m_views) { + if ((view->GetUsageFlags() & AZ::RPI::View::UsageFlags::UsageCamera) > 0) + { + AZ::Vector3 cameraPosition = view->GetCameraTransform().GetTranslation(); + AZ::Vector2 cameraPositionXY = AZ::Vector2(cameraPosition.GetX(), cameraPosition.GetY()); + AZ::Vector2 sectorCenterXY = AZ::Vector2(sectorData.m_aabb.GetCenter().GetX(), sectorData.m_aabb.GetCenter().GetY()); + + float sectorDistance = sectorCenterXY.GetDistance(cameraPositionXY); + float lodForCamera = ceilf(AZ::GetMax(0.0f, log2f(sectorDistance / (GridMeters * 4.0f)))); + lodChoice = AZ::GetMin(lodChoice, aznumeric_cast(lodForCamera)); + } + } + + // Add the correct LOD draw packet for visible sectors. + for (auto& view : process.m_views) + { + AZ::Frustum viewFrustum = AZ::Frustum::CreateFromMatrixColumnMajor(view->GetWorldToClipMatrix()); if (viewFrustum.IntersectAabb(sectorData.m_aabb) != AZ::IntersectResult::Exterior) { - view->AddDrawPacket(sectorData.m_drawPacket.get()); + uint8_t lodToRender = AZ::GetMin(lodChoice, aznumeric_cast(sectorData.m_drawPackets.size() - 1)); + view->AddDrawPacket(sectorData.m_drawPackets.at(lodToRender).GetRHIDrawPacket()); } } } } - void TerrainFeatureProcessor::InitializeTerrainPatch() + void TerrainFeatureProcessor::InitializeTerrainPatch(uint16_t gridSize, float gridSpacing, PatchData& patchdata) { - m_gridVertices.clear(); - m_gridIndices.clear(); + patchdata.m_positions.clear(); + patchdata.m_uvs.clear(); + patchdata.m_indices.clear(); - for (float y = 0.0f; y < m_gridMeters; y += m_gridSpacing) + uint16_t gridVertices = gridSize + 1; // For m_gridSize quads, (m_gridSize + 1) vertices are needed. + size_t size = gridVertices * gridVertices; + + patchdata.m_positions.reserve(size); + patchdata.m_uvs.reserve(size); + + for (uint16_t y = 0; y < gridVertices; ++y) { - for (float x = 0.0f; x < m_gridMeters; x += m_gridSpacing) + for (uint16_t x = 0; x < gridVertices; ++x) { - float x0 = x; - float x1 = x + m_gridSpacing; - float y0 = y; - float y1 = y + m_gridSpacing; + patchdata.m_positions.push_back({ aznumeric_cast(x) * gridSpacing, aznumeric_cast(y) * gridSpacing }); + patchdata.m_uvs.push_back({ aznumeric_cast(x) / gridSize, aznumeric_cast(y) / gridSize }); + } + } - uint16_t startIndex = (uint16_t)(m_gridVertices.size()); + patchdata.m_indices.reserve(gridSize * gridSize * 6); // total number of quads, 2 triangles with 6 indices per quad. + + for (uint16_t y = 0; y < gridSize; ++y) + { + for (uint16_t x = 0; x < gridSize; ++x) + { + uint16_t topLeft = y * gridVertices + x; + uint16_t topRight = topLeft + 1; + uint16_t bottomLeft = (y + 1) * gridVertices + x; + uint16_t bottomRight = bottomLeft + 1; - m_gridVertices.emplace_back(x0, y0, x0 / m_gridMeters, y0 / m_gridMeters); - m_gridVertices.emplace_back(x1, y0, x1 / m_gridMeters, y0 / m_gridMeters); - m_gridVertices.emplace_back(x0, y1, x0 / m_gridMeters, y1 / m_gridMeters); - m_gridVertices.emplace_back(x1, y1, x1 / m_gridMeters, y1 / m_gridMeters); - - m_gridIndices.emplace_back(startIndex); - m_gridIndices.emplace_back(aznumeric_cast(startIndex + 1)); - m_gridIndices.emplace_back(aznumeric_cast(startIndex + 2)); - m_gridIndices.emplace_back(aznumeric_cast(startIndex + 1)); - m_gridIndices.emplace_back(aznumeric_cast(startIndex + 3)); - m_gridIndices.emplace_back(aznumeric_cast(startIndex + 2)); + patchdata.m_indices.emplace_back(topLeft); + patchdata.m_indices.emplace_back(topRight); + patchdata.m_indices.emplace_back(bottomLeft); + patchdata.m_indices.emplace_back(bottomLeft); + patchdata.m_indices.emplace_back(topRight); + patchdata.m_indices.emplace_back(bottomRight); } } } - - bool TerrainFeatureProcessor::InitializeRenderBuffers() + + AZ::Outcome> TerrainFeatureProcessor::CreateBufferAsset( + const void* data, const AZ::RHI::BufferViewDescriptor& bufferViewDescriptor, const AZStd::string& bufferName) { - AZ::RHI::ResultCode result = AZ::RHI::ResultCode::Fail; + AZ::RPI::BufferAssetCreator creator; + creator.Begin(AZ::Uuid::CreateRandom()); - // Create geometry buffers - m_indexBuffer = AZ::RHI::Factory::Get().CreateBuffer(); - m_vertexBuffer = AZ::RHI::Factory::Get().CreateBuffer(); + AZ::RHI::BufferDescriptor bufferDescriptor; + bufferDescriptor.m_bindFlags = AZ::RHI::BufferBindFlags::InputAssembly | AZ::RHI::BufferBindFlags::ShaderRead; + bufferDescriptor.m_byteCount = static_cast(bufferViewDescriptor.m_elementSize) * static_cast(bufferViewDescriptor.m_elementCount); - m_indexBuffer->SetName(AZ::Name("TerrainIndexBuffer")); - m_vertexBuffer->SetName(AZ::Name("TerrainVertexBuffer")); + creator.SetBuffer(data, bufferDescriptor.m_byteCount, bufferDescriptor); + creator.SetBufferViewDescriptor(bufferViewDescriptor); + creator.SetUseCommonPool(AZ::RPI::CommonBufferPoolType::StaticInputAssembly); - AZStd::vector> buffers = { m_indexBuffer , m_vertexBuffer }; - - // Fill our buffers with the vertex/index data - for (size_t bufferIndex = 0; bufferIndex < buffers.size(); ++bufferIndex) + AZ::Data::Asset bufferAsset; + if (creator.End(bufferAsset)) { - AZ::RHI::Ptr buffer = buffers[bufferIndex]; + bufferAsset.SetHint(bufferName); + return AZ::Success(bufferAsset); + } - // Initialize the buffer + return AZ::Failure(); + } - AZ::RHI::BufferInitRequest bufferRequest; - bufferRequest.m_descriptor = AZ::RHI::BufferDescriptor{ AZ::RHI::BufferBindFlags::InputAssembly, DEFAULT_UploadBufferSize }; - bufferRequest.m_buffer = buffer.get(); + bool TerrainFeatureProcessor::InitializePatchModel() + { + AZ::RPI::ModelAssetCreator modelAssetCreator; + modelAssetCreator.Begin(AZ::Uuid::CreateRandom()); - result = m_hostPool->InitBuffer(bufferRequest); + uint16_t gridSize = GridSize; + float gridSpacing = GridSpacing; - if (result != AZ::RHI::ResultCode::Success) + for (uint32_t i = 0; i < AZ::RPI::ModelLodAsset::LodCountMax && gridSize > 0; ++i) + { + PatchData patchData; + InitializeTerrainPatch(gridSize, gridSpacing, patchData); + + auto positionBufferViewDesc = AZ::RHI::BufferViewDescriptor::CreateTyped(0, aznumeric_cast(patchData.m_positions.size()), AZ::RHI::Format::R32G32_FLOAT); + auto positionsOutcome = CreateBufferAsset(patchData.m_positions.data(), positionBufferViewDesc, "TerrainPatchPositions"); + + auto uvBufferViewDesc = AZ::RHI::BufferViewDescriptor::CreateTyped(0, aznumeric_cast(patchData.m_uvs.size()), AZ::RHI::Format::R32G32_FLOAT); + auto uvsOutcome = CreateBufferAsset(patchData.m_uvs.data(), uvBufferViewDesc, "TerrainPatchUvs"); + + auto indexBufferViewDesc = AZ::RHI::BufferViewDescriptor::CreateTyped(0, aznumeric_cast(patchData.m_indices.size()), AZ::RHI::Format::R16_UINT); + auto indicesOutcome = CreateBufferAsset(patchData.m_indices.data(), indexBufferViewDesc, "TerrainPatchIndices"); + + if (!positionsOutcome.IsSuccess() || !uvsOutcome.IsSuccess() || !indicesOutcome.IsSuccess()) { AZ_Error(TerrainFPName, false, "Failed to create GPU buffers for Terrain"); return false; } + + AZ::RPI::ModelLodAssetCreator modelLodAssetCreator; + modelLodAssetCreator.Begin(AZ::Uuid::CreateRandom()); - // Grab a pointer to the buffer's data + modelLodAssetCreator.BeginMesh(); + modelLodAssetCreator.AddMeshStreamBuffer(AZ::RHI::ShaderSemantic{ "POSITION" }, AZ::Name(), {positionsOutcome.GetValue(), positionBufferViewDesc}); + modelLodAssetCreator.AddMeshStreamBuffer(AZ::RHI::ShaderSemantic{ "UV" }, AZ::Name(), {uvsOutcome.GetValue(), uvBufferViewDesc}); + modelLodAssetCreator.SetMeshIndexBuffer({indicesOutcome.GetValue(), indexBufferViewDesc}); - m_hostPool->OrphanBuffer(*buffer); + AZ::Aabb aabb = AZ::Aabb::CreateFromMinMax(AZ::Vector3(0.0, 0.0, 0.0), AZ::Vector3(GridMeters, GridMeters, 0.0)); + modelLodAssetCreator.SetMeshAabb(AZStd::move(aabb)); + modelLodAssetCreator.SetMeshName(AZ::Name("Terrain Patch")); + modelLodAssetCreator.EndMesh(); - AZ::RHI::BufferMapResponse mapResponse; - m_hostPool->MapBuffer(AZ::RHI::BufferMapRequest(*buffer, 0, DEFAULT_UploadBufferSize), mapResponse); + AZ::Data::Asset modelLodAsset; + modelLodAssetCreator.End(modelLodAsset); + + modelAssetCreator.AddLodAsset(AZStd::move(modelLodAsset)); - auto* mappedData = reinterpret_cast(mapResponse.m_data); - - //0th index should always be the index buffer - if (bufferIndex == 0) - { - // Fill the index buffer with our terrain patch indices - const uint64_t idxSize = m_gridIndices.size() * sizeof(uint16_t); - memcpy(mappedData, m_gridIndices.data(), idxSize); - - m_indexBufferView = AZ::RHI::IndexBufferView( - *buffer, 0, static_cast(idxSize), AZ::RHI::IndexFormat::Uint16); - } - else - { - // Fill the vertex buffer with our terrain patch vertices - const uint64_t elementSize = m_gridVertices.size() * sizeof(Vertex); - memcpy(mappedData, m_gridVertices.data(), elementSize); - - m_vertexBufferView = AZ::RHI::StreamBufferView( - *buffer, 0, static_cast(elementSize), static_cast(sizeof(Vertex))); - } - - m_hostPool->UnmapBuffer(*buffer); + gridSize = gridSize / 2; + gridSpacing *= 2.0f; } - return true; - } + AZ::Data::Asset modelAsset; + bool success = modelAssetCreator.End(modelAsset); - void TerrainFeatureProcessor::DestroyRenderBuffers() - { - m_indexBuffer.reset(); - m_vertexBuffer.reset(); + m_patchModel = AZ::RPI::Model::FindOrCreate(modelAsset); - m_indexBufferView = {}; - m_vertexBufferView = {}; - - for (ShaderState& shaderState : m_shaderStates) - { - shaderState.Reset(); - } + return success; } } diff --git a/Gems/Terrain/Code/Source/TerrainRenderer/TerrainFeatureProcessor.h b/Gems/Terrain/Code/Source/TerrainRenderer/TerrainFeatureProcessor.h index 382f473b25..160d3113c2 100644 --- a/Gems/Terrain/Code/Source/TerrainRenderer/TerrainFeatureProcessor.h +++ b/Gems/Terrain/Code/Source/TerrainRenderer/TerrainFeatureProcessor.h @@ -14,9 +14,11 @@ #include #include +#include #include #include +#include #include #include #include @@ -26,6 +28,15 @@ #include #include +namespace AZ::RPI +{ + namespace AssetUtils + { + class AsyncAssetLoader; + } + class Model; +} + namespace Terrain { class TerrainFeatureProcessor final @@ -57,28 +68,6 @@ namespace Terrain private: - // System-level references to the shader, pipeline, and shader-related information - enum ShaderType - { - Depth, - Forward, - Count, - }; - - struct ShaderState - { - AZ::Data::Instance m_shader; - AZ::RHI::ConstPtr m_pipelineState; - AZ::RHI::PipelineStateDescriptorForDraw m_pipelineStateDescriptor; - - void Reset() - { - m_shader.reset(); - m_pipelineState.reset(); - m_pipelineStateDescriptor = {}; - } - }; - struct ShaderTerrainData // Must align with struct in Object Srg { AZStd::array m_uvMin; @@ -87,62 +76,47 @@ namespace Terrain float m_sampleSpacing; float m_heightScale; }; - - // RPI::SceneNotificationBus overrides ... - void OnRenderPipelineAdded(AZ::RPI::RenderPipelinePtr pipeline) override; - void OnRenderPipelineRemoved(AZ::RPI::RenderPipeline* pipeline) override; - void OnRenderPipelinePassesChanged(AZ::RPI::RenderPipeline* renderPipeline) override; - - void InitializeAtomStuff(); - void ConfigurePipelineState(ShaderState& shaderState, bool assertOnFail); - - void InitializeTerrainPatch(); - - bool InitializeRenderBuffers(); - void DestroyRenderBuffers(); - - void ProcessSurfaces(const FeatureProcessor::RenderPacket& process); - - // System-level parameters - const float m_gridSpacing{ 1.0f }; - const float m_gridMeters{ 32.0f }; - - // System-level cached reference to the Atom RHI - AZ::RHI::RHISystemInterface* m_rhiSystem = nullptr; - - AZStd::array m_shaderStates; - - AZ::RHI::ShaderInputImageIndex m_heightmapImageIndex; - AZ::RHI::ShaderInputConstantIndex m_modelToWorldIndex; - AZ::RHI::ShaderInputConstantIndex m_terrainDataIndex; - - // Pos_float_2 + UV_float_2 - struct Vertex + + struct VertexPosition { float m_posx; float m_posy; - float m_u; - float m_v; - - Vertex(float posx, float posy, float u, float v) - : m_posx(posx) - , m_posy(posy) - , m_u(u) - , m_v(v) - { - } }; - // System-level definition of a grid patch. (ex: 32m x 32m) - AZStd::vector m_gridVertices; - AZStd::vector m_gridIndices; + struct VertexUv + { + float m_u; + float m_v; + }; - // System-level data related to the grid patch - AZ::RHI::Ptr m_hostPool = nullptr; - AZ::RHI::Ptr m_indexBuffer; - AZ::RHI::Ptr m_vertexBuffer; - AZ::RHI::IndexBufferView m_indexBufferView; - AZ::RHI::StreamBufferView m_vertexBufferView; + struct PatchData + { + AZStd::vector m_positions; + AZStd::vector m_uvs; + AZStd::vector m_indices; + }; + + void Initialize(); + void InitializeTerrainPatch(uint16_t gridSize, float gridSpacing, PatchData& patchdata); + bool InitializePatchModel(); + + void ProcessSurfaces(const FeatureProcessor::RenderPacket& process); + + AZ::Outcome> CreateBufferAsset( + const void* data, const AZ::RHI::BufferViewDescriptor& bufferViewDescriptor, const AZStd::string& bufferName); + + // System-level parameters + static constexpr float GridSpacing{ 1.0f }; + static constexpr uint32_t GridSize{ 64 }; // number of terrain quads (vertices are m_gridSize + 1) + static constexpr float GridMeters{ GridSpacing * GridSize }; + + AZStd::unique_ptr m_materialAssetLoader; + AZ::Data::Instance m_materialInstance; + + AZ::RHI::ShaderInputConstantIndex m_modelToWorldIndex; + AZ::RHI::ShaderInputConstantIndex m_terrainDataIndex; + + AZ::Data::Instance m_patchModel; // Per-area data struct TerrainAreaData @@ -161,15 +135,9 @@ namespace Terrain struct SectorData { - AZ::Data::Instance m_srg; + AZ::Data::Instance m_srg; // Hold on to ref so it's not dropped AZ::Aabb m_aabb; - AZStd::unique_ptr m_drawPacket; - - SectorData(const AZ::RHI::DrawPacket* drawPacket, AZ::Aabb aabb, AZ::Data::Instance srg) - : m_srg(srg) - , m_aabb(aabb) - , m_drawPacket(drawPacket) - {} + AZStd::fixed_vector m_drawPackets; }; AZStd::vector m_sectorData; From a692ae1567f7c8435832c2ad4e8884e75a6d984d Mon Sep 17 00:00:00 2001 From: Nicholas Van Sickle Date: Tue, 28 Sep 2021 14:57:09 -0700 Subject: [PATCH 50/51] Restore event listening behavior in EntityOutlinerListModel (#4254) Disables a problematic child reparenting code path in EditorEntityModel that led to the series of remove -> add -> remove notifications for reparented entities that led to unnecessary updates and a sporadic QSortFilterProxyModel crash Signed-off-by: nvsickle --- .../Entity/EditorEntityModel.cpp | 20 ++++++++++++++----- .../UI/Outliner/EntityOutlinerListModel.cpp | 7 +++++-- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/EditorEntityModel.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/EditorEntityModel.cpp index 8d96cc42fe..ce1bf84db0 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/EditorEntityModel.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/EditorEntityModel.cpp @@ -381,12 +381,22 @@ namespace AzToolsFramework return; } - //orphan any children that remain attached to the entity - auto children = entityInfo.GetChildren(); - for (auto childId : children) + bool isPrefabSystemEnabled = false; + AzFramework::ApplicationRequests::Bus::BroadcastResult( + isPrefabSystemEnabled, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled); + + // For slices, orphan any children that remain attached to the entity + // For prefabs, this is an unneeded operation because the prefab system handles the orphans + // and the extra reparenting operation can be problematic for consumers subscribed to entity + // events, such as the entity outliner. + if (!isPrefabSystemEnabled) { - ReparentChild(childId, AZ::EntityId(), entityId); - m_entityOrphanTable[entityId].insert(childId); + auto children = entityInfo.GetChildren(); + for (auto childId : children) + { + ReparentChild(childId, AZ::EntityId(), entityId); + m_entityOrphanTable[entityId].insert(childId); + } } m_savedOrderInfo[entityId] = AZStd::make_pair(entityInfo.GetParent(), entityInfo.GetIndexForSorting()); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerListModel.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerListModel.cpp index b3ac8b4c73..2872c1bce3 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerListModel.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerListModel.cpp @@ -1344,7 +1344,10 @@ namespace AzToolsFramework //add/remove operations trigger selection change signals which assert and break undo/redo operations in progress in inspector etc. //so disallow selection updates until change is complete emit EnableSelectionUpdates(false); - beginResetModel(); + + auto parentIndex = GetIndexFromEntity(parentId); + auto childIndex = GetIndexFromEntity(childId); + beginRemoveRows(parentIndex, childIndex.row(), childIndex.row()); } void EntityOutlinerListModel::OnEntityInfoUpdatedRemoveChildEnd(AZ::EntityId parentId, AZ::EntityId childId) @@ -1352,7 +1355,7 @@ namespace AzToolsFramework (void)childId; AZ_PROFILE_FUNCTION(AzToolsFramework); - endResetModel(); + endRemoveRows(); //must refresh partial lock/visibility of parents m_isFilterDirty = true; From c34b6ffe3bc302cce3fa814da40518a08c5a3638 Mon Sep 17 00:00:00 2001 From: hultonha <82228511+hultonha@users.noreply.github.com> Date: Wed, 29 Sep 2021 10:11:49 +0100 Subject: [PATCH 51/51] Overhaul of how camera orbit/pivot behavior works (#4345) * overhaul to how camera orbit/pivot behavior works Signed-off-by: hultonha * update naming from orbit to pivot Signed-off-by: hultonha * fix camera unit tests Signed-off-by: hultonha * add additional tests for new camera pivot behavior Signed-off-by: hultonha * fix comment and add additional info for tests Signed-off-by: hultonha --- .../EditorModularViewportCameraComposer.cpp | 105 +++++------ .../EditorModularViewportCameraComposer.h | 12 +- .../EditorPreferencesPageViewportCamera.cpp | 54 +++--- .../EditorPreferencesPageViewportCamera.h | 10 +- Code/Editor/EditorViewportSettings.cpp | 50 ++--- Code/Editor/EditorViewportSettings.h | 20 +- .../AzFramework/Viewport/CameraInput.cpp | 175 +++++++----------- .../AzFramework/Viewport/CameraInput.h | 121 ++++++++---- .../AzFramework/Tests/CameraInputTests.cpp | 163 +++++++++++++--- .../ModularViewportCameraController.h | 5 - .../ModularViewportCameraController.cpp | 29 +-- 11 files changed, 410 insertions(+), 334 deletions(-) diff --git a/Code/Editor/EditorModularViewportCameraComposer.cpp b/Code/Editor/EditorModularViewportCameraComposer.cpp index e375e98fb6..600f2089e6 100644 --- a/Code/Editor/EditorModularViewportCameraComposer.cpp +++ b/Code/Editor/EditorModularViewportCameraComposer.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include namespace SandboxEditor @@ -94,7 +95,7 @@ namespace SandboxEditor cameras.AddCamera(m_firstPersonPanCamera); cameras.AddCamera(m_firstPersonTranslateCamera); cameras.AddCamera(m_firstPersonScrollCamera); - cameras.AddCamera(m_orbitCamera); + cameras.AddCamera(m_pivotCamera); }); return controller; @@ -131,8 +132,8 @@ namespace SandboxEditor m_firstPersonRotateCamera->SetActivationBeganFn(hideCursor); m_firstPersonRotateCamera->SetActivationEndedFn(showCursor); - m_firstPersonPanCamera = - AZStd::make_shared(SandboxEditor::CameraFreePanChannelId(), AzFramework::LookPan); + m_firstPersonPanCamera = AZStd::make_shared( + SandboxEditor::CameraFreePanChannelId(), AzFramework::LookPan, AzFramework::TranslatePivot); m_firstPersonPanCamera->m_panSpeedFn = [] { @@ -151,8 +152,8 @@ namespace SandboxEditor const auto translateCameraInputChannelIds = BuildTranslateCameraInputChannelIds(); - m_firstPersonTranslateCamera = - AZStd::make_shared(AzFramework::LookTranslation, translateCameraInputChannelIds); + m_firstPersonTranslateCamera = AZStd::make_shared( + translateCameraInputChannelIds, AzFramework::LookTranslation, AzFramework::TranslatePivot); m_firstPersonTranslateCamera->m_translateSpeedFn = [] { @@ -171,10 +172,10 @@ namespace SandboxEditor return SandboxEditor::CameraScrollSpeed(); }; - m_orbitCamera = AZStd::make_shared(SandboxEditor::CameraOrbitChannelId()); + m_pivotCamera = AZStd::make_shared(SandboxEditor::CameraPivotChannelId()); - m_orbitCamera->SetLookAtFn( - [viewportId = m_viewportId](const AZ::Vector3& position, const AZ::Vector3& direction) -> AZStd::optional + m_pivotCamera->SetPivotFn( + [viewportId = m_viewportId]([[maybe_unused]] const AZ::Vector3& position, [[maybe_unused]] const AZ::Vector3& direction) { AZStd::optional lookAtAfterInterpolation; AtomToolsFramework::ModularViewportCameraControllerRequestBus::EventResult( @@ -182,109 +183,98 @@ namespace SandboxEditor &AtomToolsFramework::ModularViewportCameraControllerRequestBus::Events::LookAtAfterInterpolation); // initially attempt to use the last set look at point after an interpolation has finished - if (lookAtAfterInterpolation.has_value()) + // note: ignore this if it is the same location as the camera (e.g. after go to position) + if (lookAtAfterInterpolation.has_value() && !lookAtAfterInterpolation->IsClose(position)) { return *lookAtAfterInterpolation; } - const float RayDistance = 1000.0f; - AzFramework::RenderGeometry::RayRequest ray; - ray.m_startWorldPosition = position; - ray.m_endWorldPosition = position + direction * RayDistance; - ray.m_onlyVisible = true; + // otherwise fall back to the selected entity pivot + AZStd::optional entityPivot; + AzToolsFramework::EditorTransformComponentSelectionRequestBus::EventResult( + entityPivot, AzToolsFramework::GetEntityContextId(), + &AzToolsFramework::EditorTransformComponentSelectionRequestBus::Events::GetManipulatorTransform); - AzFramework::RenderGeometry::RayResult renderGeometryIntersectionResult; - AzFramework::RenderGeometry::IntersectorBus::EventResult( - renderGeometryIntersectionResult, AzToolsFramework::GetEntityContextId(), - &AzFramework::RenderGeometry::IntersectorBus::Events::RayIntersect, ray); - - // attempt a ray intersection with any visible mesh and return the intersection position if successful - if (renderGeometryIntersectionResult) - { - return renderGeometryIntersectionResult.m_worldPosition; - } - - // if there is no selection or no intersection, fallback to default camera orbit behavior (ground plane - // intersection) - return {}; + // finally just use the identity + return entityPivot.value_or(AZ::Transform::CreateIdentity()).GetTranslation(); }); - m_orbitRotateCamera = AZStd::make_shared(SandboxEditor::CameraOrbitLookChannelId()); + m_pivotRotateCamera = AZStd::make_shared(SandboxEditor::CameraPivotLookChannelId()); - m_orbitRotateCamera->m_rotateSpeedFn = [] + m_pivotRotateCamera->m_rotateSpeedFn = [] { return SandboxEditor::CameraRotateSpeed(); }; - m_orbitRotateCamera->m_invertYawFn = [] + m_pivotRotateCamera->m_invertYawFn = [] { - return SandboxEditor::CameraOrbitYawRotationInverted(); + return SandboxEditor::CameraPivotYawRotationInverted(); }; - m_orbitTranslateCamera = - AZStd::make_shared(AzFramework::OrbitTranslation, translateCameraInputChannelIds); + m_pivotTranslateCamera = AZStd::make_shared( + translateCameraInputChannelIds, AzFramework::LookTranslation, AzFramework::TranslateOffset); - m_orbitTranslateCamera->m_translateSpeedFn = [] + m_pivotTranslateCamera->m_translateSpeedFn = [] { return SandboxEditor::CameraTranslateSpeed(); }; - m_orbitTranslateCamera->m_boostMultiplierFn = [] + m_pivotTranslateCamera->m_boostMultiplierFn = [] { return SandboxEditor::CameraBoostMultiplier(); }; - m_orbitDollyScrollCamera = AZStd::make_shared(); + m_pivotDollyScrollCamera = AZStd::make_shared(); - m_orbitDollyScrollCamera->m_scrollSpeedFn = [] + m_pivotDollyScrollCamera->m_scrollSpeedFn = [] { return SandboxEditor::CameraScrollSpeed(); }; - m_orbitDollyMoveCamera = - AZStd::make_shared(SandboxEditor::CameraOrbitDollyChannelId()); + m_pivotDollyMoveCamera = AZStd::make_shared(SandboxEditor::CameraPivotDollyChannelId()); - m_orbitDollyMoveCamera->m_cursorSpeedFn = [] + m_pivotDollyMoveCamera->m_motionSpeedFn = [] { return SandboxEditor::CameraDollyMotionSpeed(); }; - m_orbitPanCamera = AZStd::make_shared(SandboxEditor::CameraOrbitPanChannelId(), AzFramework::OrbitPan); + m_pivotPanCamera = AZStd::make_shared( + SandboxEditor::CameraPivotPanChannelId(), AzFramework::LookPan, AzFramework::TranslateOffset); - m_orbitPanCamera->m_panSpeedFn = [] + m_pivotPanCamera->m_panSpeedFn = [] { return SandboxEditor::CameraPanSpeed(); }; - m_orbitPanCamera->m_invertPanXFn = [] + m_pivotPanCamera->m_invertPanXFn = [] { return SandboxEditor::CameraPanInvertedX(); }; - m_orbitPanCamera->m_invertPanYFn = [] + m_pivotPanCamera->m_invertPanYFn = [] { return SandboxEditor::CameraPanInvertedY(); }; - m_orbitCamera->m_orbitCameras.AddCamera(m_orbitRotateCamera); - m_orbitCamera->m_orbitCameras.AddCamera(m_orbitTranslateCamera); - m_orbitCamera->m_orbitCameras.AddCamera(m_orbitDollyScrollCamera); - m_orbitCamera->m_orbitCameras.AddCamera(m_orbitDollyMoveCamera); - m_orbitCamera->m_orbitCameras.AddCamera(m_orbitPanCamera); + m_pivotCamera->m_pivotCameras.AddCamera(m_pivotRotateCamera); + m_pivotCamera->m_pivotCameras.AddCamera(m_pivotTranslateCamera); + m_pivotCamera->m_pivotCameras.AddCamera(m_pivotDollyScrollCamera); + m_pivotCamera->m_pivotCameras.AddCamera(m_pivotDollyMoveCamera); + m_pivotCamera->m_pivotCameras.AddCamera(m_pivotPanCamera); } void EditorModularViewportCameraComposer::OnEditorModularViewportCameraComposerSettingsChanged() { const auto translateCameraInputChannelIds = BuildTranslateCameraInputChannelIds(); m_firstPersonTranslateCamera->SetTranslateCameraInputChannelIds(translateCameraInputChannelIds); - m_orbitTranslateCamera->SetTranslateCameraInputChannelIds(translateCameraInputChannelIds); - m_firstPersonPanCamera->SetPanInputChannelId(SandboxEditor::CameraFreePanChannelId()); - m_orbitPanCamera->SetPanInputChannelId(SandboxEditor::CameraOrbitPanChannelId()); m_firstPersonRotateCamera->SetRotateInputChannelId(SandboxEditor::CameraFreeLookChannelId()); - m_orbitRotateCamera->SetRotateInputChannelId(SandboxEditor::CameraOrbitLookChannelId()); - m_orbitCamera->SetOrbitInputChannelId(SandboxEditor::CameraOrbitChannelId()); - m_orbitDollyMoveCamera->SetDollyInputChannelId(SandboxEditor::CameraOrbitDollyChannelId()); + + m_pivotCamera->SetPivotInputChannelId(SandboxEditor::CameraPivotChannelId()); + m_pivotTranslateCamera->SetTranslateCameraInputChannelIds(translateCameraInputChannelIds); + m_pivotPanCamera->SetPanInputChannelId(SandboxEditor::CameraPivotPanChannelId()); + m_pivotRotateCamera->SetRotateInputChannelId(SandboxEditor::CameraPivotLookChannelId()); + m_pivotDollyMoveCamera->SetDollyInputChannelId(SandboxEditor::CameraPivotDollyChannelId()); } void EditorModularViewportCameraComposer::OnViewportViewEntityChanged(const AZ::EntityId& viewEntityId) @@ -295,8 +285,7 @@ namespace SandboxEditor AZ::TransformBus::EventResult(worldFromLocal, viewEntityId, &AZ::TransformBus::Events::GetWorldTM); AtomToolsFramework::ModularViewportCameraControllerRequestBus::Event( - m_viewportId, &AtomToolsFramework::ModularViewportCameraControllerRequestBus::Events::SetReferenceFrame, - worldFromLocal); + m_viewportId, &AtomToolsFramework::ModularViewportCameraControllerRequestBus::Events::SetReferenceFrame, worldFromLocal); } else { diff --git a/Code/Editor/EditorModularViewportCameraComposer.h b/Code/Editor/EditorModularViewportCameraComposer.h index e6e71c976c..e691ca1c89 100644 --- a/Code/Editor/EditorModularViewportCameraComposer.h +++ b/Code/Editor/EditorModularViewportCameraComposer.h @@ -42,12 +42,12 @@ namespace SandboxEditor AZStd::shared_ptr m_firstPersonPanCamera; AZStd::shared_ptr m_firstPersonTranslateCamera; AZStd::shared_ptr m_firstPersonScrollCamera; - AZStd::shared_ptr m_orbitCamera; - AZStd::shared_ptr m_orbitRotateCamera; - AZStd::shared_ptr m_orbitTranslateCamera; - AZStd::shared_ptr m_orbitDollyScrollCamera; - AZStd::shared_ptr m_orbitDollyMoveCamera; - AZStd::shared_ptr m_orbitPanCamera; + AZStd::shared_ptr m_pivotCamera; + AZStd::shared_ptr m_pivotRotateCamera; + AZStd::shared_ptr m_pivotTranslateCamera; + AZStd::shared_ptr m_pivotDollyScrollCamera; + AZStd::shared_ptr m_pivotDollyMoveCamera; + AZStd::shared_ptr m_pivotPanCamera; AzFramework::ViewportId m_viewportId; }; diff --git a/Code/Editor/EditorPreferencesPageViewportCamera.cpp b/Code/Editor/EditorPreferencesPageViewportCamera.cpp index 368d2769f8..55b631e1f6 100644 --- a/Code/Editor/EditorPreferencesPageViewportCamera.cpp +++ b/Code/Editor/EditorPreferencesPageViewportCamera.cpp @@ -61,7 +61,7 @@ static AZStd::vector GetEditorInputNames() void CEditorPreferencesPage_ViewportCamera::Reflect(AZ::SerializeContext& serialize) { serialize.Class() - ->Version(2) + ->Version(3) ->Field("TranslateSpeed", &CameraMovementSettings::m_translateSpeed) ->Field("RotateSpeed", &CameraMovementSettings::m_rotateSpeed) ->Field("BoostMultiplier", &CameraMovementSettings::m_boostMultiplier) @@ -73,12 +73,12 @@ void CEditorPreferencesPage_ViewportCamera::Reflect(AZ::SerializeContext& serial ->Field("TranslateSmoothing", &CameraMovementSettings::m_translateSmoothing) ->Field("TranslateSmoothness", &CameraMovementSettings::m_translateSmoothness) ->Field("CaptureCursorLook", &CameraMovementSettings::m_captureCursorLook) - ->Field("OrbitYawRotationInverted", &CameraMovementSettings::m_orbitYawRotationInverted) + ->Field("PivotYawRotationInverted", &CameraMovementSettings::m_pivotYawRotationInverted) ->Field("PanInvertedX", &CameraMovementSettings::m_panInvertedX) ->Field("PanInvertedY", &CameraMovementSettings::m_panInvertedY); serialize.Class() - ->Version(1) + ->Version(2) ->Field("TranslateForward", &CameraInputSettings::m_translateForwardChannelId) ->Field("TranslateBackward", &CameraInputSettings::m_translateBackwardChannelId) ->Field("TranslateLeft", &CameraInputSettings::m_translateLeftChannelId) @@ -86,12 +86,12 @@ void CEditorPreferencesPage_ViewportCamera::Reflect(AZ::SerializeContext& serial ->Field("TranslateUp", &CameraInputSettings::m_translateUpChannelId) ->Field("TranslateDown", &CameraInputSettings::m_translateDownChannelId) ->Field("Boost", &CameraInputSettings::m_boostChannelId) - ->Field("Orbit", &CameraInputSettings::m_orbitChannelId) + ->Field("Pivot", &CameraInputSettings::m_pivotChannelId) ->Field("FreeLook", &CameraInputSettings::m_freeLookChannelId) ->Field("FreePan", &CameraInputSettings::m_freePanChannelId) - ->Field("OrbitLook", &CameraInputSettings::m_orbitLookChannelId) - ->Field("OrbitDolly", &CameraInputSettings::m_orbitDollyChannelId) - ->Field("OrbitPan", &CameraInputSettings::m_orbitPanChannelId); + ->Field("PivotLook", &CameraInputSettings::m_pivotLookChannelId) + ->Field("PivotDolly", &CameraInputSettings::m_pivotDollyChannelId) + ->Field("PivotPan", &CameraInputSettings::m_pivotPanChannelId); serialize.Class() ->Version(1) @@ -143,8 +143,8 @@ void CEditorPreferencesPage_ViewportCamera::Reflect(AZ::SerializeContext& serial ->Attribute(AZ::Edit::Attributes::Min, minValue) ->Attribute(AZ::Edit::Attributes::Visibility, &CameraMovementSettings::TranslateSmoothingVisibility) ->DataElement( - AZ::Edit::UIHandlers::CheckBox, &CameraMovementSettings::m_orbitYawRotationInverted, "Camera Orbit Yaw Inverted", - "Inverted yaw rotation while orbiting") + AZ::Edit::UIHandlers::CheckBox, &CameraMovementSettings::m_pivotYawRotationInverted, "Camera Pivot Yaw Inverted", + "Inverted yaw rotation while pivoting") ->DataElement( AZ::Edit::UIHandlers::CheckBox, &CameraMovementSettings::m_panInvertedX, "Invert Pan X", "Invert direction of pan in local X axis") @@ -185,8 +185,8 @@ void CEditorPreferencesPage_ViewportCamera::Reflect(AZ::SerializeContext& serial "Key/button to move the camera more quickly") ->Attribute(AZ::Edit::Attributes::StringList, &GetEditorInputNames) ->DataElement( - AZ::Edit::UIHandlers::ComboBox, &CameraInputSettings::m_orbitChannelId, "Orbit", - "Key/button to begin the camera orbit behavior") + AZ::Edit::UIHandlers::ComboBox, &CameraInputSettings::m_pivotChannelId, "Pivot", + "Key/button to begin the camera pivot behavior") ->Attribute(AZ::Edit::Attributes::StringList, &GetEditorInputNames) ->DataElement( AZ::Edit::UIHandlers::ComboBox, &CameraInputSettings::m_freeLookChannelId, "Free Look", @@ -196,16 +196,16 @@ void CEditorPreferencesPage_ViewportCamera::Reflect(AZ::SerializeContext& serial AZ::Edit::UIHandlers::ComboBox, &CameraInputSettings::m_freePanChannelId, "Free Pan", "Key/button to begin camera free pan") ->Attribute(AZ::Edit::Attributes::StringList, &GetEditorInputNames) ->DataElement( - AZ::Edit::UIHandlers::ComboBox, &CameraInputSettings::m_orbitLookChannelId, "Orbit Look", - "Key/button to begin camera orbit look") + AZ::Edit::UIHandlers::ComboBox, &CameraInputSettings::m_pivotLookChannelId, "Pivot Look", + "Key/button to begin camera pivot look") ->Attribute(AZ::Edit::Attributes::StringList, &GetEditorInputNames) ->DataElement( - AZ::Edit::UIHandlers::ComboBox, &CameraInputSettings::m_orbitDollyChannelId, "Orbit Dolly", - "Key/button to begin camera orbit dolly") + AZ::Edit::UIHandlers::ComboBox, &CameraInputSettings::m_pivotDollyChannelId, "Pivot Dolly", + "Key/button to begin camera pivot dolly") ->Attribute(AZ::Edit::Attributes::StringList, &GetEditorInputNames) ->DataElement( - AZ::Edit::UIHandlers::ComboBox, &CameraInputSettings::m_orbitPanChannelId, "Orbit Pan", - "Key/button to begin camera orbit pan") + AZ::Edit::UIHandlers::ComboBox, &CameraInputSettings::m_pivotPanChannelId, "Pivot Pan", + "Key/button to begin camera pivot pan") ->Attribute(AZ::Edit::Attributes::StringList, &GetEditorInputNames); editContext->Class("Viewport Preferences", "Viewport Preferences") @@ -264,7 +264,7 @@ void CEditorPreferencesPage_ViewportCamera::OnApply() SandboxEditor::SetCameraTranslateSmoothness(m_cameraMovementSettings.m_translateSmoothness); SandboxEditor::SetCameraTranslateSmoothingEnabled(m_cameraMovementSettings.m_translateSmoothing); SandboxEditor::SetCameraCaptureCursorForLook(m_cameraMovementSettings.m_captureCursorLook); - SandboxEditor::SetCameraOrbitYawRotationInverted(m_cameraMovementSettings.m_orbitYawRotationInverted); + SandboxEditor::SetCameraPivotYawRotationInverted(m_cameraMovementSettings.m_pivotYawRotationInverted); SandboxEditor::SetCameraPanInvertedX(m_cameraMovementSettings.m_panInvertedX); SandboxEditor::SetCameraPanInvertedY(m_cameraMovementSettings.m_panInvertedY); @@ -275,12 +275,12 @@ void CEditorPreferencesPage_ViewportCamera::OnApply() SandboxEditor::SetCameraTranslateUpChannelId(m_cameraInputSettings.m_translateUpChannelId); SandboxEditor::SetCameraTranslateDownChannelId(m_cameraInputSettings.m_translateDownChannelId); SandboxEditor::SetCameraTranslateBoostChannelId(m_cameraInputSettings.m_boostChannelId); - SandboxEditor::SetCameraOrbitChannelId(m_cameraInputSettings.m_orbitChannelId); + SandboxEditor::SetCameraPivotChannelId(m_cameraInputSettings.m_pivotChannelId); SandboxEditor::SetCameraFreeLookChannelId(m_cameraInputSettings.m_freeLookChannelId); SandboxEditor::SetCameraFreePanChannelId(m_cameraInputSettings.m_freePanChannelId); - SandboxEditor::SetCameraOrbitLookChannelId(m_cameraInputSettings.m_orbitLookChannelId); - SandboxEditor::SetCameraOrbitDollyChannelId(m_cameraInputSettings.m_orbitDollyChannelId); - SandboxEditor::SetCameraOrbitPanChannelId(m_cameraInputSettings.m_orbitPanChannelId); + SandboxEditor::SetCameraPivotLookChannelId(m_cameraInputSettings.m_pivotLookChannelId); + SandboxEditor::SetCameraPivotDollyChannelId(m_cameraInputSettings.m_pivotDollyChannelId); + SandboxEditor::SetCameraPivotPanChannelId(m_cameraInputSettings.m_pivotPanChannelId); SandboxEditor::EditorModularViewportCameraComposerNotificationBus::Broadcast( &SandboxEditor::EditorModularViewportCameraComposerNotificationBus::Events::OnEditorModularViewportCameraComposerSettingsChanged); @@ -299,7 +299,7 @@ void CEditorPreferencesPage_ViewportCamera::InitializeSettings() m_cameraMovementSettings.m_translateSmoothness = SandboxEditor::CameraTranslateSmoothness(); m_cameraMovementSettings.m_translateSmoothing = SandboxEditor::CameraTranslateSmoothingEnabled(); m_cameraMovementSettings.m_captureCursorLook = SandboxEditor::CameraCaptureCursorForLook(); - m_cameraMovementSettings.m_orbitYawRotationInverted = SandboxEditor::CameraOrbitYawRotationInverted(); + m_cameraMovementSettings.m_pivotYawRotationInverted = SandboxEditor::CameraPivotYawRotationInverted(); m_cameraMovementSettings.m_panInvertedX = SandboxEditor::CameraPanInvertedX(); m_cameraMovementSettings.m_panInvertedY = SandboxEditor::CameraPanInvertedY(); @@ -310,10 +310,10 @@ void CEditorPreferencesPage_ViewportCamera::InitializeSettings() m_cameraInputSettings.m_translateUpChannelId = SandboxEditor::CameraTranslateUpChannelId().GetName(); m_cameraInputSettings.m_translateDownChannelId = SandboxEditor::CameraTranslateDownChannelId().GetName(); m_cameraInputSettings.m_boostChannelId = SandboxEditor::CameraTranslateBoostChannelId().GetName(); - m_cameraInputSettings.m_orbitChannelId = SandboxEditor::CameraOrbitChannelId().GetName(); + m_cameraInputSettings.m_pivotChannelId = SandboxEditor::CameraPivotChannelId().GetName(); m_cameraInputSettings.m_freeLookChannelId = SandboxEditor::CameraFreeLookChannelId().GetName(); m_cameraInputSettings.m_freePanChannelId = SandboxEditor::CameraFreePanChannelId().GetName(); - m_cameraInputSettings.m_orbitLookChannelId = SandboxEditor::CameraOrbitLookChannelId().GetName(); - m_cameraInputSettings.m_orbitDollyChannelId = SandboxEditor::CameraOrbitDollyChannelId().GetName(); - m_cameraInputSettings.m_orbitPanChannelId = SandboxEditor::CameraOrbitPanChannelId().GetName(); + m_cameraInputSettings.m_pivotLookChannelId = SandboxEditor::CameraPivotLookChannelId().GetName(); + m_cameraInputSettings.m_pivotDollyChannelId = SandboxEditor::CameraPivotDollyChannelId().GetName(); + m_cameraInputSettings.m_pivotPanChannelId = SandboxEditor::CameraPivotPanChannelId().GetName(); } diff --git a/Code/Editor/EditorPreferencesPageViewportCamera.h b/Code/Editor/EditorPreferencesPageViewportCamera.h index 31a728599f..01dc4664f6 100644 --- a/Code/Editor/EditorPreferencesPageViewportCamera.h +++ b/Code/Editor/EditorPreferencesPageViewportCamera.h @@ -54,7 +54,7 @@ private: float m_translateSmoothness; bool m_translateSmoothing; bool m_captureCursorLook; - bool m_orbitYawRotationInverted; + bool m_pivotYawRotationInverted; bool m_panInvertedX; bool m_panInvertedY; @@ -80,12 +80,12 @@ private: AZStd::string m_translateUpChannelId; AZStd::string m_translateDownChannelId; AZStd::string m_boostChannelId; - AZStd::string m_orbitChannelId; + AZStd::string m_pivotChannelId; AZStd::string m_freeLookChannelId; AZStd::string m_freePanChannelId; - AZStd::string m_orbitLookChannelId; - AZStd::string m_orbitDollyChannelId; - AZStd::string m_orbitPanChannelId; + AZStd::string m_pivotLookChannelId; + AZStd::string m_pivotDollyChannelId; + AZStd::string m_pivotPanChannelId; }; CameraMovementSettings m_cameraMovementSettings; diff --git a/Code/Editor/EditorViewportSettings.cpp b/Code/Editor/EditorViewportSettings.cpp index 063ec125ba..fe8efecd17 100644 --- a/Code/Editor/EditorViewportSettings.cpp +++ b/Code/Editor/EditorViewportSettings.cpp @@ -28,7 +28,7 @@ namespace SandboxEditor constexpr AZStd::string_view CameraRotateSpeedSetting = "/Amazon/Preferences/Editor/Camera/RotateSpeed"; constexpr AZStd::string_view CameraScrollSpeedSetting = "/Amazon/Preferences/Editor/Camera/DollyScrollSpeed"; constexpr AZStd::string_view CameraDollyMotionSpeedSetting = "/Amazon/Preferences/Editor/Camera/DollyMotionSpeed"; - constexpr AZStd::string_view CameraOrbitYawRotationInvertedSetting = "/Amazon/Preferences/Editor/Camera/YawRotationInverted"; + constexpr AZStd::string_view CameraPivotYawRotationInvertedSetting = "/Amazon/Preferences/Editor/Camera/YawRotationInverted"; constexpr AZStd::string_view CameraPanInvertedXSetting = "/Amazon/Preferences/Editor/Camera/PanInvertedX"; constexpr AZStd::string_view CameraPanInvertedYSetting = "/Amazon/Preferences/Editor/Camera/PanInvertedY"; constexpr AZStd::string_view CameraPanSpeedSetting = "/Amazon/Preferences/Editor/Camera/PanSpeed"; @@ -44,12 +44,12 @@ namespace SandboxEditor constexpr AZStd::string_view CameraTranslateUpIdSetting = "/Amazon/Preferences/Editor/Camera/CameraTranslateUpId"; constexpr AZStd::string_view CameraTranslateDownIdSetting = "/Amazon/Preferences/Editor/Camera/CameraTranslateUpDownId"; constexpr AZStd::string_view CameraTranslateBoostIdSetting = "/Amazon/Preferences/Editor/Camera/TranslateBoostId"; - constexpr AZStd::string_view CameraOrbitIdSetting = "/Amazon/Preferences/Editor/Camera/OrbitId"; + constexpr AZStd::string_view CameraPivotIdSetting = "/Amazon/Preferences/Editor/Camera/PivotId"; constexpr AZStd::string_view CameraFreeLookIdSetting = "/Amazon/Preferences/Editor/Camera/FreeLookId"; constexpr AZStd::string_view CameraFreePanIdSetting = "/Amazon/Preferences/Editor/Camera/FreePanId"; - constexpr AZStd::string_view CameraOrbitLookIdSetting = "/Amazon/Preferences/Editor/Camera/OrbitLookId"; - constexpr AZStd::string_view CameraOrbitDollyIdSetting = "/Amazon/Preferences/Editor/Camera/OrbitDollyId"; - constexpr AZStd::string_view CameraOrbitPanIdSetting = "/Amazon/Preferences/Editor/Camera/OrbitPanId"; + constexpr AZStd::string_view CameraPivotLookIdSetting = "/Amazon/Preferences/Editor/Camera/PivotLookId"; + constexpr AZStd::string_view CameraPivotDollyIdSetting = "/Amazon/Preferences/Editor/Camera/PivotDollyId"; + constexpr AZStd::string_view CameraPivotPanIdSetting = "/Amazon/Preferences/Editor/Camera/PivotPanId"; template void SetRegistry(const AZStd::string_view setting, T&& value) @@ -239,14 +239,14 @@ namespace SandboxEditor SetRegistry(CameraDollyMotionSpeedSetting, speed); } - bool CameraOrbitYawRotationInverted() + bool CameraPivotYawRotationInverted() { - return GetRegistry(CameraOrbitYawRotationInvertedSetting, false); + return GetRegistry(CameraPivotYawRotationInvertedSetting, false); } - void SetCameraOrbitYawRotationInverted(const bool inverted) + void SetCameraPivotYawRotationInverted(const bool inverted) { - SetRegistry(CameraOrbitYawRotationInvertedSetting, inverted); + SetRegistry(CameraPivotYawRotationInvertedSetting, inverted); } bool CameraPanInvertedX() @@ -403,14 +403,14 @@ namespace SandboxEditor SetRegistry(CameraTranslateBoostIdSetting, cameraTranslateBoostId); } - AzFramework::InputChannelId CameraOrbitChannelId() + AzFramework::InputChannelId CameraPivotChannelId() { - return AzFramework::InputChannelId(GetRegistry(CameraOrbitIdSetting, AZStd::string("keyboard_key_modifier_alt_l")).c_str()); + return AzFramework::InputChannelId(GetRegistry(CameraPivotIdSetting, AZStd::string("keyboard_key_modifier_alt_l")).c_str()); } - void SetCameraOrbitChannelId(AZStd::string_view cameraOrbitId) + void SetCameraPivotChannelId(AZStd::string_view cameraPivotId) { - SetRegistry(CameraOrbitIdSetting, cameraOrbitId); + SetRegistry(CameraPivotIdSetting, cameraPivotId); } AzFramework::InputChannelId CameraFreeLookChannelId() @@ -433,33 +433,33 @@ namespace SandboxEditor SetRegistry(CameraFreePanIdSetting, cameraFreePanId); } - AzFramework::InputChannelId CameraOrbitLookChannelId() + AzFramework::InputChannelId CameraPivotLookChannelId() { - return AzFramework::InputChannelId(GetRegistry(CameraOrbitLookIdSetting, AZStd::string("mouse_button_left")).c_str()); + return AzFramework::InputChannelId(GetRegistry(CameraPivotLookIdSetting, AZStd::string("mouse_button_left")).c_str()); } - void SetCameraOrbitLookChannelId(AZStd::string_view cameraOrbitLookId) + void SetCameraPivotLookChannelId(AZStd::string_view cameraPivotLookId) { - SetRegistry(CameraOrbitLookIdSetting, cameraOrbitLookId); + SetRegistry(CameraPivotLookIdSetting, cameraPivotLookId); } - AzFramework::InputChannelId CameraOrbitDollyChannelId() + AzFramework::InputChannelId CameraPivotDollyChannelId() { - return AzFramework::InputChannelId(GetRegistry(CameraOrbitDollyIdSetting, AZStd::string("mouse_button_right")).c_str()); + return AzFramework::InputChannelId(GetRegistry(CameraPivotDollyIdSetting, AZStd::string("mouse_button_right")).c_str()); } - void SetCameraOrbitDollyChannelId(AZStd::string_view cameraOrbitDollyId) + void SetCameraPivotDollyChannelId(AZStd::string_view cameraPivotDollyId) { - SetRegistry(CameraOrbitDollyIdSetting, cameraOrbitDollyId); + SetRegistry(CameraPivotDollyIdSetting, cameraPivotDollyId); } - AzFramework::InputChannelId CameraOrbitPanChannelId() + AzFramework::InputChannelId CameraPivotPanChannelId() { - return AzFramework::InputChannelId(GetRegistry(CameraOrbitPanIdSetting, AZStd::string("mouse_button_middle")).c_str()); + return AzFramework::InputChannelId(GetRegistry(CameraPivotPanIdSetting, AZStd::string("mouse_button_middle")).c_str()); } - void SetCameraOrbitPanChannelId(AZStd::string_view cameraOrbitPanId) + void SetCameraPivotPanChannelId(AZStd::string_view cameraPivotPanId) { - SetRegistry(CameraOrbitPanIdSetting, cameraOrbitPanId); + SetRegistry(CameraPivotPanIdSetting, cameraPivotPanId); } } // namespace SandboxEditor diff --git a/Code/Editor/EditorViewportSettings.h b/Code/Editor/EditorViewportSettings.h index 20d397a29e..d1271017c6 100644 --- a/Code/Editor/EditorViewportSettings.h +++ b/Code/Editor/EditorViewportSettings.h @@ -71,8 +71,8 @@ namespace SandboxEditor SANDBOX_API float CameraDollyMotionSpeed(); SANDBOX_API void SetCameraDollyMotionSpeed(float speed); - SANDBOX_API bool CameraOrbitYawRotationInverted(); - SANDBOX_API void SetCameraOrbitYawRotationInverted(bool inverted); + SANDBOX_API bool CameraPivotYawRotationInverted(); + SANDBOX_API void SetCameraPivotYawRotationInverted(bool inverted); SANDBOX_API bool CameraPanInvertedX(); SANDBOX_API void SetCameraPanInvertedX(bool inverted); @@ -119,8 +119,8 @@ namespace SandboxEditor SANDBOX_API AzFramework::InputChannelId CameraTranslateBoostChannelId(); SANDBOX_API void SetCameraTranslateBoostChannelId(AZStd::string_view cameraTranslateBoostId); - SANDBOX_API AzFramework::InputChannelId CameraOrbitChannelId(); - SANDBOX_API void SetCameraOrbitChannelId(AZStd::string_view cameraOrbitId); + SANDBOX_API AzFramework::InputChannelId CameraPivotChannelId(); + SANDBOX_API void SetCameraPivotChannelId(AZStd::string_view cameraPivotId); SANDBOX_API AzFramework::InputChannelId CameraFreeLookChannelId(); SANDBOX_API void SetCameraFreeLookChannelId(AZStd::string_view cameraFreeLookId); @@ -128,12 +128,12 @@ namespace SandboxEditor SANDBOX_API AzFramework::InputChannelId CameraFreePanChannelId(); SANDBOX_API void SetCameraFreePanChannelId(AZStd::string_view cameraFreePanId); - SANDBOX_API AzFramework::InputChannelId CameraOrbitLookChannelId(); - SANDBOX_API void SetCameraOrbitLookChannelId(AZStd::string_view cameraOrbitLookId); + SANDBOX_API AzFramework::InputChannelId CameraPivotLookChannelId(); + SANDBOX_API void SetCameraPivotLookChannelId(AZStd::string_view cameraPivotLookId); - SANDBOX_API AzFramework::InputChannelId CameraOrbitDollyChannelId(); - SANDBOX_API void SetCameraOrbitDollyChannelId(AZStd::string_view cameraOrbitDollyId); + SANDBOX_API AzFramework::InputChannelId CameraPivotDollyChannelId(); + SANDBOX_API void SetCameraPivotDollyChannelId(AZStd::string_view cameraPivotDollyId); - SANDBOX_API AzFramework::InputChannelId CameraOrbitPanChannelId(); - SANDBOX_API void SetCameraOrbitPanChannelId(AZStd::string_view cameraOrbitPanId); + SANDBOX_API AzFramework::InputChannelId CameraPivotPanChannelId(); + SANDBOX_API void SetCameraPivotPanChannelId(AZStd::string_view cameraPivotPanId); } // namespace SandboxEditor diff --git a/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.cpp b/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.cpp index 18667ac153..a3eb4d8329 100644 --- a/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.cpp +++ b/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.cpp @@ -17,15 +17,6 @@ namespace AzFramework { - AZ_CVAR( - float, - ed_cameraSystemDefaultPlaneHeight, - 34.0f, - nullptr, - AZ::ConsoleFunctorFlags::Null, - "The default height of the ground plane to do intersection tests against when orbiting"); - AZ_CVAR(float, ed_cameraSystemMinOrbitDistance, 10.0f, nullptr, AZ::ConsoleFunctorFlags::Null, ""); - AZ_CVAR(float, ed_cameraSystemMaxOrbitDistance, 50.0f, nullptr, AZ::ConsoleFunctorFlags::Null, ""); AZ_CVAR( bool, ed_cameraSystemUseCursor, @@ -135,8 +126,8 @@ namespace AzFramework camera.m_pitch = eulerAngles.GetX(); camera.m_yaw = eulerAngles.GetZ(); - // note: m_lookDist is negative so we must invert it here - camera.m_lookAt = transform.GetTranslation() + (camera.Rotation().GetBasisY() * -camera.m_lookDist); + camera.m_pivot = transform.GetTranslation(); + camera.m_offset = AZ::Vector3::CreateZero(); } bool CameraSystem::HandleEvents(const InputEvent& event) @@ -320,14 +311,8 @@ namespace AzFramework nextCamera.m_pitch -= float(cursorDelta.m_y) * rotateSpeed * Invert(m_invertPitchFn()); nextCamera.m_yaw -= float(cursorDelta.m_x) * rotateSpeed * Invert(m_invertYawFn()); - const auto clampRotation = [](const float angle) - { - return AZStd::fmod(angle + AZ::Constants::TwoPi, AZ::Constants::TwoPi); - }; - - nextCamera.m_yaw = clampRotation(nextCamera.m_yaw); - // clamp pitch to be +/-90 degrees - nextCamera.m_pitch = AZ::GetClamp(nextCamera.m_pitch, -AZ::Constants::HalfPi, AZ::Constants::HalfPi); + nextCamera.m_yaw = WrapYawRotation(nextCamera.m_yaw); + nextCamera.m_pitch = ClampPitchRotation(nextCamera.m_pitch); return nextCamera; } @@ -337,9 +322,10 @@ namespace AzFramework m_rotateChannelId = rotateChannelId; } - PanCameraInput::PanCameraInput(const InputChannelId& panChannelId, PanAxesFn panAxesFn) + PanCameraInput::PanCameraInput(const InputChannelId& panChannelId, PanAxesFn panAxesFn, TranslationDeltaFn translationDeltaFn) : m_panAxesFn(AZStd::move(panAxesFn)) , m_panChannelId(panChannelId) + , m_translationDeltaFn(translationDeltaFn) { m_panSpeedFn = []() constexpr { @@ -375,11 +361,11 @@ namespace AzFramework const auto panAxes = m_panAxesFn(nextCamera); const float panSpeed = m_panSpeedFn(); - const auto deltaPanX = float(cursorDelta.m_x) * panAxes.m_horizontalAxis * panSpeed; - const auto deltaPanY = float(cursorDelta.m_y) * panAxes.m_verticalAxis * panSpeed; + const auto deltaPanX = aznumeric_cast(cursorDelta.m_x) * panAxes.m_horizontalAxis * panSpeed; + const auto deltaPanY = aznumeric_cast(cursorDelta.m_y) * panAxes.m_verticalAxis * panSpeed; - nextCamera.m_lookAt += deltaPanX * Invert(m_invertPanXFn()); - nextCamera.m_lookAt += deltaPanY * -Invert(m_invertPanYFn()); + m_translationDeltaFn(nextCamera, deltaPanX * Invert(m_invertPanXFn())); + m_translationDeltaFn(nextCamera, deltaPanY * -Invert(m_invertPanYFn())); return nextCamera; } @@ -426,8 +412,11 @@ namespace AzFramework } TranslateCameraInput::TranslateCameraInput( - TranslationAxesFn translationAxesFn, const TranslateCameraInputChannelIds& translateCameraInputChannelIds) + const TranslateCameraInputChannelIds& translateCameraInputChannelIds, + TranslationAxesFn translationAxesFn, + TranslationDeltaFn translateDeltaFn) : m_translationAxesFn(AZStd::move(translationAxesFn)) + , m_translateDeltaFn(AZStd::move(translateDeltaFn)) , m_translateCameraInputChannelIds(translateCameraInputChannelIds) { m_translateSpeedFn = []() constexpr @@ -497,32 +486,32 @@ namespace AzFramework if ((m_translation & TranslationType::Forward) == TranslationType::Forward) { - nextCamera.m_lookAt += axisY * speed * deltaTime; + m_translateDeltaFn(nextCamera, axisY * speed * deltaTime); } if ((m_translation & TranslationType::Backward) == TranslationType::Backward) { - nextCamera.m_lookAt -= axisY * speed * deltaTime; + m_translateDeltaFn(nextCamera, -axisY * speed * deltaTime); } if ((m_translation & TranslationType::Left) == TranslationType::Left) { - nextCamera.m_lookAt -= axisX * speed * deltaTime; + m_translateDeltaFn(nextCamera, -axisX * speed * deltaTime); } if ((m_translation & TranslationType::Right) == TranslationType::Right) { - nextCamera.m_lookAt += axisX * speed * deltaTime; + m_translateDeltaFn(nextCamera, axisX * speed * deltaTime); } if ((m_translation & TranslationType::Up) == TranslationType::Up) { - nextCamera.m_lookAt += axisZ * speed * deltaTime; + m_translateDeltaFn(nextCamera, axisZ * speed * deltaTime); } if ((m_translation & TranslationType::Down) == TranslationType::Down) { - nextCamera.m_lookAt -= axisZ * speed * deltaTime; + m_translateDeltaFn(nextCamera, -axisZ * speed * deltaTime); } if (Ending()) @@ -544,16 +533,20 @@ namespace AzFramework m_translateCameraInputChannelIds = translateCameraInputChannelIds; } - OrbitCameraInput::OrbitCameraInput(const InputChannelId& orbitChannelId) - : m_orbitChannelId(orbitChannelId) + PivotCameraInput::PivotCameraInput(const InputChannelId& pivotChannelId) + : m_pivotChannelId(pivotChannelId) { + m_pivotFn = []([[maybe_unused]] const AZ::Vector3& position, [[maybe_unused]] const AZ::Vector3& direction) + { + return AZ::Vector3::CreateZero(); + }; } - bool OrbitCameraInput::HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, const float scrollDelta) + bool PivotCameraInput::HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, const float scrollDelta) { if (const auto* input = AZStd::get_if(&event)) { - if (input->m_channelId == m_orbitChannelId) + if (input->m_channelId == m_pivotChannelId) { if (input->m_state == InputChannel::State::Began) { @@ -568,85 +561,46 @@ namespace AzFramework if (Active()) { - return m_orbitCameras.HandleEvents(event, cursorDelta, scrollDelta); + return m_pivotCameras.HandleEvents(event, cursorDelta, scrollDelta); } return !Idle(); } - Camera OrbitCameraInput::StepCamera( + Camera PivotCameraInput::StepCamera( const Camera& targetCamera, const ScreenVector& cursorDelta, const float scrollDelta, const float deltaTime) { Camera nextCamera = targetCamera; if (Beginning()) { - const auto hasLookAt = [&nextCamera, &targetCamera, &lookAtFn = m_lookAtFn] - { - if (lookAtFn) - { - // pass through the camera's position and look vector for use in the lookAt function - if (const auto lookAt = lookAtFn(targetCamera.Translation(), targetCamera.Rotation().GetBasisY())) - { - // default to internal look at behavior if the look at point matches the camera translation - if (targetCamera.m_lookAt.IsClose(*lookAt)) - { - return false; - } - - auto transform = AZ::Transform::CreateLookAt(targetCamera.m_lookAt, *lookAt); - nextCamera.m_lookDist = -lookAt->GetDistance(targetCamera.m_lookAt); - UpdateCameraFromTransform(nextCamera, transform); - - return true; - } - } - return false; - }(); - - if (!hasLookAt) - { - float hit_distance = 0.0f; - AZ::Plane::CreateFromNormalAndPoint(AZ::Vector3::CreateAxisZ(), AZ::Vector3::CreateAxisZ(ed_cameraSystemDefaultPlaneHeight)) - .CastRay(targetCamera.Translation(), targetCamera.Rotation().GetBasisY(), hit_distance); - - if (hit_distance > 0.0f) - { - hit_distance = AZStd::min(hit_distance, ed_cameraSystemMaxOrbitDistance); - nextCamera.m_lookDist = -hit_distance; - nextCamera.m_lookAt = targetCamera.Translation() + targetCamera.Rotation().GetBasisY() * hit_distance; - } - else - { - nextCamera.m_lookDist = -ed_cameraSystemMinOrbitDistance; - nextCamera.m_lookAt = - targetCamera.Translation() + targetCamera.Rotation().GetBasisY() * ed_cameraSystemMinOrbitDistance; - } - } + nextCamera.m_pivot = m_pivotFn(targetCamera.Translation(), targetCamera.Rotation().GetBasisY()); + nextCamera.m_offset = nextCamera.View().TransformPoint(targetCamera.Translation()); } if (Active()) { - nextCamera = m_orbitCameras.StepCamera(nextCamera, cursorDelta, scrollDelta, deltaTime); + MovePivotDetached(nextCamera, m_pivotFn(targetCamera.Translation(), targetCamera.Rotation().GetBasisY())); + nextCamera = m_pivotCameras.StepCamera(nextCamera, cursorDelta, scrollDelta, deltaTime); } if (Ending()) { - m_orbitCameras.Reset(); + m_pivotCameras.Reset(); - nextCamera.m_lookAt = nextCamera.Translation(); - nextCamera.m_lookDist = 0.0f; + nextCamera.m_pivot = nextCamera.Translation(); + nextCamera.m_offset = AZ::Vector3::CreateZero(); } return nextCamera; } - void OrbitCameraInput::SetOrbitInputChannelId(const InputChannelId& orbitChanneId) + void PivotCameraInput::SetPivotInputChannelId(const InputChannelId& pivotChanneId) { - m_orbitChannelId = orbitChanneId; + m_pivotChannelId = pivotChanneId; } - OrbitDollyScrollCameraInput::OrbitDollyScrollCameraInput() + PivotDollyScrollCameraInput::PivotDollyScrollCameraInput() { m_scrollSpeedFn = []() constexpr { @@ -654,7 +608,7 @@ namespace AzFramework }; } - bool OrbitDollyScrollCameraInput::HandleEvents( + bool PivotDollyScrollCameraInput::HandleEvents( const InputEvent& event, [[maybe_unused]] const ScreenVector& cursorDelta, [[maybe_unused]] const float scrollDelta) { if (const auto* scroll = AZStd::get_if(&event)) @@ -665,46 +619,61 @@ namespace AzFramework return !Idle(); } - Camera OrbitDollyScrollCameraInput::StepCamera( + static Camera PivotDolly(const Camera& targetCamera, const float delta) + { + Camera nextCamera = targetCamera; + + const auto pivotDirection = targetCamera.m_offset.GetNormalized(); + nextCamera.m_offset -= pivotDirection * delta; + const auto pivotDot = targetCamera.m_offset.Dot(nextCamera.m_offset); + const auto distance = nextCamera.m_offset.GetLength() * AZ::GetSign(pivotDot); + + const auto minDistance = 0.01f; + if (distance < minDistance || pivotDot < 0.0f) + { + nextCamera.m_offset = pivotDirection * minDistance; + } + + return nextCamera; + } + + Camera PivotDollyScrollCameraInput::StepCamera( const Camera& targetCamera, [[maybe_unused]] const ScreenVector& cursorDelta, const float scrollDelta, [[maybe_unused]] const float deltaTime) { - Camera nextCamera = targetCamera; - nextCamera.m_lookDist = AZ::GetMin(nextCamera.m_lookDist + scrollDelta * m_scrollSpeedFn(), 0.0f); + const auto nextCamera = PivotDolly(targetCamera, aznumeric_cast(scrollDelta) * m_scrollSpeedFn()); EndActivation(); return nextCamera; } - OrbitDollyCursorMoveCameraInput::OrbitDollyCursorMoveCameraInput(const InputChannelId& dollyChannelId) + PivotDollyMotionCameraInput::PivotDollyMotionCameraInput(const InputChannelId& dollyChannelId) : m_dollyChannelId(dollyChannelId) { - m_cursorSpeedFn = []() constexpr + m_motionSpeedFn = []() constexpr { return 0.01f; }; } - bool OrbitDollyCursorMoveCameraInput::HandleEvents( + bool PivotDollyMotionCameraInput::HandleEvents( const InputEvent& event, [[maybe_unused]] const ScreenVector& cursorDelta, [[maybe_unused]] const float scrollDelta) { HandleActivationEvents(event, m_dollyChannelId, cursorDelta, m_clickDetector, *this); return CameraInputUpdatingAfterMotion(*this); } - Camera OrbitDollyCursorMoveCameraInput::StepCamera( + Camera PivotDollyMotionCameraInput::StepCamera( const Camera& targetCamera, const ScreenVector& cursorDelta, [[maybe_unused]] const float scrollDelta, [[maybe_unused]] const float deltaTime) { - Camera nextCamera = targetCamera; - nextCamera.m_lookDist = AZ::GetMin(nextCamera.m_lookDist + float(cursorDelta.m_y) * m_cursorSpeedFn(), 0.0f); - return nextCamera; + return PivotDolly(targetCamera, aznumeric_cast(cursorDelta.m_y) * m_motionSpeedFn()); } - void OrbitDollyCursorMoveCameraInput::SetDollyInputChannelId(const InputChannelId& dollyChannelId) + void PivotDollyMotionCameraInput::SetDollyInputChannelId(const InputChannelId& dollyChannelId) { m_dollyChannelId = dollyChannelId; } @@ -739,7 +708,7 @@ namespace AzFramework const auto translation_basis = LookTranslation(nextCamera); const auto axisY = translation_basis.GetBasisY(); - nextCamera.m_lookAt += axisY * scrollDelta * m_scrollSpeedFn(); + nextCamera.m_pivot += axisY * scrollDelta * m_scrollSpeedFn(); EndActivation(); @@ -790,13 +759,13 @@ namespace AzFramework { const float moveRate = AZStd::exp2(cameraProps.m_translateSmoothnessFn()); const float moveTime = AZStd::exp2(-moveRate * deltaTime); - camera.m_lookDist = AZ::Lerp(targetCamera.m_lookDist, currentCamera.m_lookDist, moveTime); - camera.m_lookAt = targetCamera.m_lookAt.Lerp(currentCamera.m_lookAt, moveTime); + camera.m_pivot = targetCamera.m_pivot.Lerp(currentCamera.m_pivot, moveTime); + camera.m_offset = targetCamera.m_offset.Lerp(currentCamera.m_offset, moveTime); } else { - camera.m_lookDist = targetCamera.m_lookDist; - camera.m_lookAt = targetCamera.m_lookAt; + camera.m_pivot = targetCamera.m_pivot; + camera.m_offset = targetCamera.m_offset; } return camera; diff --git a/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.h b/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.h index 69e8b66434..c44d951292 100644 --- a/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.h +++ b/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.h @@ -29,17 +29,15 @@ namespace AzFramework //! @note Order of rotation is Z, Y, X. AZ::Vector3 EulerAngles(const AZ::Matrix3x3& orientation); - //! A simple camera representation using spherical coordinates as input (pitch, yaw and look distance). + //! A simple camera representation using spherical coordinates as input (pitch, yaw, pivot and offset). //! The cameras transform and view can be obtained through accessor functions that use the internal //! spherical coordinates to calculate the position and orientation. struct Camera { - AZ::Vector3 m_lookAt = AZ::Vector3::CreateZero(); //!< Position of camera when m_lookDist is zero, - //!< or position of m_lookAt when m_lookDist is greater - //!< than zero. - float m_yaw{ 0.0 }; //!< Yaw rotation of camera (stored in radians) usually clamped to 0-360 degrees (0-2Pi radians). - float m_pitch{ 0.0 }; //!< Pitch rotation of the camera (stored in radians) usually clamped to +/-90 degrees (-Pi/2 - Pi/2 radians). - float m_lookDist{ 0.0 }; //!< Zero gives first person free look, otherwise orbit about m_lookAt + AZ::Vector3 m_pivot = AZ::Vector3::CreateZero(); //!< Pivot point to rotate about (modified in world space). + AZ::Vector3 m_offset = AZ::Vector3::CreateZero(); //!< Offset relative to pivot (modified in camera space). + float m_yaw = 0.0f; //!< Yaw rotation of camera (stored in radians) usually clamped to 0-360 degrees (0-2Pi radians). + float m_pitch = 0.0f; //!< Pitch rotation of the camera (stored in radians) usually clamped to +/-90 degrees (-Pi/2 - Pi/2 radians). //! View camera transform (V in model-view-projection matrix (MVP)). AZ::Transform View() const; @@ -51,6 +49,15 @@ namespace AzFramework AZ::Vector3 Translation() const; }; + //! Helper to allow the pivot to be positioned without altering the camera's position. + inline void MovePivotDetached(Camera& camera, const AZ::Vector3& pivot) + { + const auto& view = camera.View(); + const auto delta = view.TransformPoint(pivot) - view.TransformPoint(camera.m_pivot); + camera.m_offset -= delta; + camera.m_pivot = pivot; + } + inline AZ::Transform Camera::View() const { return Transform().GetInverse(); @@ -58,8 +65,8 @@ namespace AzFramework inline AZ::Transform Camera::Transform() const { - return AZ::Transform::CreateTranslation(m_lookAt) * AZ::Transform::CreateRotationZ(m_yaw) * - AZ::Transform::CreateRotationX(m_pitch) * AZ::Transform::CreateTranslation(AZ::Vector3::CreateAxisY(m_lookDist)); + return AZ::Transform::CreateTranslation(m_pivot) * AZ::Transform::CreateRotationZ(m_yaw) * AZ::Transform::CreateRotationX(m_pitch) * + AZ::Transform::CreateTranslation(m_offset); } inline AZ::Matrix3x3 Camera::Rotation() const @@ -279,21 +286,37 @@ namespace AzFramework public: bool HandleEvents(const InputEvent& event); Camera StepCamera(const Camera& targetCamera, float deltaTime); - bool HandlingEvents() const - { - return m_handlingEvents; - } + bool HandlingEvents() const; Cameras m_cameras; //!< Represents a collection of camera inputs that together provide a camera controller. private: - ScreenVector m_motionDelta; //!< The delta used for look/orbit/pan (rotation + translation) - two dimensional. + ScreenVector m_motionDelta; //!< The delta used for look/pivot/pan (rotation + translation) - two dimensional. CursorState m_cursorState; //!< The current and previous position of the cursor (used to calculate movement delta). float m_scrollDelta = 0.0f; //!< The delta used for dolly/movement (translation) - one dimensional. bool m_handlingEvents = false; //!< Is the camera system currently handling events (events are consumed and not propagated). }; - //! A camera input to handle motion deltas that can rotate or orbit the camera. + inline bool CameraSystem::HandlingEvents() const + { + return m_handlingEvents; + } + + //! Clamps pitch to be +/-90 degrees (-Pi/2, Pi/2). + //! @param pitch Pitch angle in radians. + inline float ClampPitchRotation(const float pitch) + { + return AZ::GetClamp(pitch, -AZ::Constants::HalfPi, AZ::Constants::HalfPi); + } + + //! Ensures yaw wraps between 0 and 360 degrees (0, 2Pi). + //! @param yaw Yaw angle in radians. + inline float WrapYawRotation(const float yaw) + { + return AZStd::fmod(yaw + AZ::Constants::TwoPi, AZ::Constants::TwoPi); + } + + //! A camera input to handle motion deltas that can rotate or pivot the camera. class RotateCameraInput : public CameraInput { public: @@ -332,8 +355,8 @@ namespace AzFramework return { orientation.GetBasisX(), orientation.GetBasisZ() }; } - //! PanAxes to use while in 'orbit' camera behavior. - inline PanAxes OrbitPan(const Camera& camera) + //! PanAxes to use while in 'pivot' camera behavior. + inline PanAxes PivotPan(const Camera& camera) { const AZ::Matrix3x3 orientation = camera.Rotation(); @@ -347,11 +370,23 @@ namespace AzFramework return { basisX, basisY }; } + using TranslationDeltaFn = AZStd::function; + + inline void TranslatePivot(Camera& camera, const AZ::Vector3& delta) + { + camera.m_pivot += delta; + } + + inline void TranslateOffset(Camera& camera, const AZ::Vector3& delta) + { + camera.m_offset += camera.View().TransformVector(delta); + } + //! A camera input to handle motion deltas that can pan the camera (translate in two axes). class PanCameraInput : public CameraInput { public: - PanCameraInput(const InputChannelId& panChannelId, PanAxesFn panAxesFn); + PanCameraInput(const InputChannelId& panChannelId, PanAxesFn panAxesFn, TranslationDeltaFn translationDeltaFn); // CameraInput overrides ... bool HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override; @@ -365,6 +400,7 @@ namespace AzFramework private: PanAxesFn m_panAxesFn; //!< Builder for the particular pan axes (provided in the constructor). + TranslationDeltaFn m_translationDeltaFn; //!< How to apply the translation delta to the camera offset or pivot. InputChannelId m_panChannelId; //!< Input channel to begin the pan camera input. ClickDetector m_clickDetector; //!< Used to determine when a sufficient motion delta has occurred after an initial discrete input //!< event has started (press and move event). @@ -385,8 +421,8 @@ namespace AzFramework return AZ::Matrix3x3::CreateFromColumns(basisX, basisY, basisZ); } - //! TranslationAxes to use while in 'orbit' camera behavior. - inline AZ::Matrix3x3 OrbitTranslation(const Camera& camera) + //! TranslationAxes to use while in 'pivot' camera behavior. + inline AZ::Matrix3x3 PivotTranslation(const Camera& camera) { const AZ::Matrix3x3 orientation = camera.Rotation(); @@ -417,8 +453,10 @@ namespace AzFramework class TranslateCameraInput : public CameraInput { public: - explicit TranslateCameraInput( - TranslationAxesFn translationAxesFn, const TranslateCameraInputChannelIds& translateCameraInputChannelIds); + TranslateCameraInput( + const TranslateCameraInputChannelIds& translateCameraInputChannelIds, + TranslationAxesFn translationAxesFn, + TranslationDeltaFn translateDeltaFn); // CameraInput overrides ... bool HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override; @@ -492,15 +530,16 @@ namespace AzFramework TranslationType m_translation = TranslationType::Nil; //!< Types of translation the camera input is under. TranslationAxesFn m_translationAxesFn; //!< Builder for translation axes. + TranslationDeltaFn m_translateDeltaFn; //!< How to apply the translation delta to the camera offset or pivot. TranslateCameraInputChannelIds m_translateCameraInputChannelIds; //!< Input channel ids that map to internal translation types. bool m_boost = false; //!< Is the translation speed currently being multiplied/scaled upwards. }; - //! A camera input to handle discrete scroll events that can modify the camera look distance. - class OrbitDollyScrollCameraInput : public CameraInput + //! A camera input to handle discrete scroll events that can modify the camera pivot distance. + class PivotDollyScrollCameraInput : public CameraInput { public: - OrbitDollyScrollCameraInput(); + PivotDollyScrollCameraInput(); // CameraInput overrides ... bool HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override; @@ -509,11 +548,11 @@ namespace AzFramework AZStd::function m_scrollSpeedFn; }; - //! A camera input to handle motion deltas that can modify the camera look distance. - class OrbitDollyCursorMoveCameraInput : public CameraInput + //! A camera input to handle motion deltas that can modify the camera pivot distance. + class PivotDollyMotionCameraInput : public CameraInput { public: - explicit OrbitDollyCursorMoveCameraInput(const InputChannelId& dollyChannelId); + explicit PivotDollyMotionCameraInput(const InputChannelId& dollyChannelId); // CameraInput overrides ... bool HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override; @@ -521,7 +560,7 @@ namespace AzFramework void SetDollyInputChannelId(const InputChannelId& dollyChannelId); - AZStd::function m_cursorSpeedFn; + AZStd::function m_motionSpeedFn; private: InputChannelId m_dollyChannelId; //!< Input channel to begin the dolly cursor camera input. @@ -544,36 +583,36 @@ namespace AzFramework //! A camera input that doubles as its own set of camera inputs. //! It is 'exclusive', so does not overlap with other sibling camera inputs - it runs its own set of camera inputs as 'children'. - class OrbitCameraInput : public CameraInput + class PivotCameraInput : public CameraInput { public: - using LookAtFn = AZStd::function(const AZ::Vector3& position, const AZ::Vector3& direction)>; + using PivotFn = AZStd::function; - explicit OrbitCameraInput(const InputChannelId& orbitChannelId); + explicit PivotCameraInput(const InputChannelId& pivotChannelId); // CameraInput overrides ... bool HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override; Camera StepCamera(const Camera& targetCamera, const ScreenVector& cursorDelta, float scrollDelta, float deltaTime) override; bool Exclusive() const override; - void SetOrbitInputChannelId(const InputChannelId& orbitChanneId); + void SetPivotInputChannelId(const InputChannelId& pivotChanneId); - Cameras m_orbitCameras; //!< The camera inputs to run when this camera input is active (only these will run as it is exclusive). + Cameras m_pivotCameras; //!< The camera inputs to run when this camera input is active (only these will run as it is exclusive). - //! Override the default behavior for how a look-at point is calculated. - void SetLookAtFn(const LookAtFn& lookAtFn); + //! Override the default behavior for how a pivot point is calculated. + void SetPivotFn(PivotFn pivotFn); private: - InputChannelId m_orbitChannelId; //!< Input channel to begin the orbit camera input. - LookAtFn m_lookAtFn; //!< The look-at behavior to use for this orbit camera (how is the look-at point calculated/retrieved). + InputChannelId m_pivotChannelId; //!< Input channel to begin the pivot camera input. + PivotFn m_pivotFn; //!< The pivot position to use for this pivot camera (how is the pivot point calculated/retrieved). }; - inline void OrbitCameraInput::SetLookAtFn(const LookAtFn& lookAtFn) + inline void PivotCameraInput::SetPivotFn(PivotFn pivotFn) { - m_lookAtFn = lookAtFn; + m_pivotFn = AZStd::move(pivotFn); } - inline bool OrbitCameraInput::Exclusive() const + inline bool PivotCameraInput::Exclusive() const { return true; } diff --git a/Code/Framework/AzFramework/Tests/CameraInputTests.cpp b/Code/Framework/AzFramework/Tests/CameraInputTests.cpp index df7d22f7ca..1e0c805587 100644 --- a/Code/Framework/AzFramework/Tests/CameraInputTests.cpp +++ b/Code/Framework/AzFramework/Tests/CameraInputTests.cpp @@ -26,7 +26,8 @@ namespace UnitTest { constexpr float deltaTime = 0.01666f; // 60fps const bool consumed = m_cameraSystem->HandleEvents(event); - m_camera = m_cameraSystem->StepCamera(m_targetCamera, deltaTime); + m_targetCamera = m_cameraSystem->StepCamera(m_targetCamera, deltaTime); + m_camera = m_targetCamera; // no smoothing return consumed; } @@ -45,20 +46,38 @@ namespace UnitTest m_translateCameraInputChannelIds.m_boostChannelId = AzFramework::InputChannelId("keyboard_key_modifier_shift_l"); m_firstPersonRotateCamera = AZStd::make_shared(AzFramework::InputDeviceMouse::Button::Right); - m_firstPersonTranslateCamera = - AZStd::make_shared(AzFramework::LookTranslation, m_translateCameraInputChannelIds); + // set rotate speed to be a value that will scale motion delta (pixels moved) by a thousandth. + m_firstPersonRotateCamera->m_rotateSpeedFn = []() + { + return 0.001f; + }; - m_orbitCamera = AZStd::make_shared(m_orbitChannelId); - auto orbitRotateCamera = AZStd::make_shared(AzFramework::InputDeviceMouse::Button::Left); - auto orbitTranslateCamera = - AZStd::make_shared(AzFramework::OrbitTranslation, m_translateCameraInputChannelIds); + m_firstPersonTranslateCamera = AZStd::make_shared( + m_translateCameraInputChannelIds, AzFramework::LookTranslation, AzFramework::TranslatePivot); - m_orbitCamera->m_orbitCameras.AddCamera(orbitRotateCamera); - m_orbitCamera->m_orbitCameras.AddCamera(orbitTranslateCamera); + m_pivotCamera = AZStd::make_shared(m_pivotChannelId); + m_pivotCamera->SetPivotFn( + [this](const AZ::Vector3&, const AZ::Vector3&) + { + return m_pivot; + }); + + auto pivotRotateCamera = AZStd::make_shared(AzFramework::InputDeviceMouse::Button::Left); + // set rotate speed to be a value that will scale motion delta (pixels moved) by a thousandth. + pivotRotateCamera->m_rotateSpeedFn = []() + { + return 0.001f; + }; + + auto pivotTranslateCamera = AZStd::make_shared( + m_translateCameraInputChannelIds, AzFramework::PivotTranslation, AzFramework::TranslateOffset); + + m_pivotCamera->m_pivotCameras.AddCamera(pivotRotateCamera); + m_pivotCamera->m_pivotCameras.AddCamera(pivotTranslateCamera); m_cameraSystem->m_cameras.AddCamera(m_firstPersonRotateCamera); m_cameraSystem->m_cameras.AddCamera(m_firstPersonTranslateCamera); - m_cameraSystem->m_cameras.AddCamera(m_orbitCamera); + m_cameraSystem->m_cameras.AddCamera(m_pivotCamera); // these tests rely on using motion delta, not cursor positions (default is true) AzFramework::ed_cameraSystemUseCursor = false; @@ -68,7 +87,7 @@ namespace UnitTest { AzFramework::ed_cameraSystemUseCursor = true; - m_orbitCamera.reset(); + m_pivotCamera.reset(); m_firstPersonRotateCamera.reset(); m_firstPersonTranslateCamera.reset(); @@ -78,24 +97,29 @@ namespace UnitTest AllocatorsTestFixture::TearDown(); } - AzFramework::InputChannelId m_orbitChannelId = AzFramework::InputChannelId("keyboard_key_modifier_alt_l"); + AzFramework::InputChannelId m_pivotChannelId = AzFramework::InputChannelId("keyboard_key_modifier_alt_l"); AzFramework::TranslateCameraInputChannelIds m_translateCameraInputChannelIds; AZStd::shared_ptr m_firstPersonRotateCamera; AZStd::shared_ptr m_firstPersonTranslateCamera; - AZStd::shared_ptr m_orbitCamera; + AZStd::shared_ptr m_pivotCamera; + AZ::Vector3 m_pivot = AZ::Vector3::CreateZero(); + + //! This is approximately Pi/2 * 1000 - this can be used to rotate the camera 90 degrees (pitch or yaw based + //! on vertical or horizontal motion) as the rotate speed function is set to be 1/1000. + inline static const int PixelMotionDelta = 1570; }; - TEST_F(CameraInputFixture, BeginAndEndOrbitCameraInputConsumesCorrectEvents) + TEST_F(CameraInputFixture, BeginAndEndPivotCameraInputConsumesCorrectEvents) { - // begin orbit camera + // begin pivot camera const bool consumed1 = HandleEventAndUpdate(AzFramework::DiscreteInputEvent{ AzFramework::InputDeviceKeyboard::Key::ModifierAltL, AzFramework::InputChannel::State::Began }); - // begin listening for orbit rotate (click detector) - event is not consumed + // begin listening for pivot rotate (click detector) - event is not consumed const bool consumed2 = HandleEventAndUpdate( AzFramework::DiscreteInputEvent{ AzFramework::InputDeviceMouse::Button::Left, AzFramework::InputChannel::State::Began }); - // begin orbit rotate (mouse has moved sufficient distance to initiate) + // begin pivot rotate (mouse has moved sufficient distance to initiate) const bool consumed3 = HandleEventAndUpdate(AzFramework::HorizontalMotionEvent{ 5 }); - // end orbit (mouse up) - event is not consumed + // end pivot (mouse up) - event is not consumed const bool consumed4 = HandleEventAndUpdate( AzFramework::DiscreteInputEvent{ AzFramework::InputDeviceMouse::Button::Left, AzFramework::InputChannel::State::Ended }); @@ -236,29 +260,110 @@ namespace UnitTest EXPECT_TRUE(activationEnded); } - TEST_F(CameraInputFixture, OrbitCameraInputHandlesLookAtPointAndSelfAtSamePositionWhenOrbiting) + TEST_F(CameraInputFixture, PivotCameraInputHandlesLookAtPointAndSelfAtSamePositionWhenPivoting) { // create pathological lookAtFn that just returns the same position as the camera - m_orbitCamera->SetLookAtFn( + m_pivotCamera->SetPivotFn( [](const AZ::Vector3& position, [[maybe_unused]] const AZ::Vector3& direction) { return position; }); + const auto expectedCameraPosition = AZ::Vector3(10.0f, 10.0f, 10.0f); AzFramework::UpdateCameraFromTransform( m_targetCamera, AZ::Transform::CreateFromQuaternionAndTranslation( - AZ::Quaternion::CreateFromEulerAnglesDegrees(AZ::Vector3(0.0f, 0.0f, 90.0f)), AZ::Vector3(10.0f, 10.0f, 10.0f))); + AZ::Quaternion::CreateFromEulerAnglesDegrees(AZ::Vector3(0.0f, 0.0f, 90.0f)), expectedCameraPosition)); - m_camera = m_targetCamera; + HandleEventAndUpdate(AzFramework::DiscreteInputEvent{ m_pivotChannelId, AzFramework::InputChannel::State::Began }); - HandleEventAndUpdate(AzFramework::DiscreteInputEvent{ m_orbitChannelId, AzFramework::InputChannel::State::Began }); + // verify the camera yaw has not changed and pivot point matches the expected camera position + using ::testing::FloatNear; + EXPECT_THAT(m_camera.m_yaw, FloatNear(AZ::DegToRad(90.0f), 0.001f)); + EXPECT_THAT(m_camera.m_pitch, FloatNear(0.0f, 0.001f)); + EXPECT_THAT(m_camera.m_offset, IsClose(AZ::Vector3::CreateZero())); + EXPECT_THAT(m_camera.m_pivot, IsClose(expectedCameraPosition)); + } - // verify the camera yaw has not changed and the look at point - // does not match that of the camera translation - using ::testing::Eq; - using ::testing::Not; - EXPECT_THAT(m_camera.m_yaw, Eq(AZ::DegToRad(90.0f))); - EXPECT_THAT(m_camera.m_lookAt, Not(IsClose(m_camera.Translation()))); + TEST_F(CameraInputFixture, FirstPersonRotateCameraInputRotatesYawByNinetyDegreesWithRequiredPixelDelta) + { + const auto cameraStartingPosition = AZ::Vector3::CreateAxisY(-10.0f); + m_targetCamera.m_pivot = cameraStartingPosition; + + HandleEventAndUpdate( + AzFramework::DiscreteInputEvent{ AzFramework::InputDeviceMouse::Button::Right, AzFramework::InputChannel::State::Began }); + HandleEventAndUpdate(AzFramework::HorizontalMotionEvent{ PixelMotionDelta }); + + const float expectedYaw = AzFramework::WrapYawRotation(-AZ::Constants::HalfPi); + + using ::testing::FloatNear; + EXPECT_THAT(m_camera.m_yaw, FloatNear(expectedYaw, 0.001f)); + EXPECT_THAT(m_camera.m_pitch, FloatNear(0.0f, 0.001f)); + EXPECT_THAT(m_camera.m_pivot, IsClose(cameraStartingPosition)); + EXPECT_THAT(m_camera.m_offset, IsClose(AZ::Vector3::CreateZero())); + } + + TEST_F(CameraInputFixture, FirstPersonRotateCameraInputRotatesPitchByNinetyDegreesWithRequiredPixelDelta) + { + const auto cameraStartingPosition = AZ::Vector3::CreateAxisY(-10.0f); + m_targetCamera.m_pivot = cameraStartingPosition; + + HandleEventAndUpdate( + AzFramework::DiscreteInputEvent{ AzFramework::InputDeviceMouse::Button::Right, AzFramework::InputChannel::State::Began }); + HandleEventAndUpdate(AzFramework::VerticalMotionEvent{ PixelMotionDelta }); + + const float expectedPitch = AzFramework::ClampPitchRotation(-AZ::Constants::HalfPi); + + using ::testing::FloatNear; + EXPECT_THAT(m_camera.m_yaw, FloatNear(0.0f, 0.001f)); + EXPECT_THAT(m_camera.m_pitch, FloatNear(expectedPitch, 0.001f)); + EXPECT_THAT(m_camera.m_pivot, IsClose(cameraStartingPosition)); + EXPECT_THAT(m_camera.m_offset, IsClose(AZ::Vector3::CreateZero())); + } + + TEST_F(CameraInputFixture, PivotRotateCameraInputRotatesPitchOffsetByNinetyDegreesWithRequiredPixelDelta) + { + const auto cameraStartingPosition = AZ::Vector3::CreateAxisY(-20.0f); + m_targetCamera.m_pivot = cameraStartingPosition; + + m_pivot = AZ::Vector3::CreateAxisY(-10.0f); + + HandleEventAndUpdate(AzFramework::DiscreteInputEvent{ m_pivotChannelId, AzFramework::InputChannel::State::Began }); + HandleEventAndUpdate( + AzFramework::DiscreteInputEvent{ AzFramework::InputDeviceMouse::Button::Left, AzFramework::InputChannel::State::Began }); + HandleEventAndUpdate(AzFramework::VerticalMotionEvent{ PixelMotionDelta }); + + const auto expectedCameraEndingPosition = AZ::Vector3(0.0f, -10.0f, 10.0f); + const float expectedPitch = AzFramework::ClampPitchRotation(-AZ::Constants::HalfPi); + + using ::testing::FloatNear; + EXPECT_THAT(m_camera.m_yaw, FloatNear(0.0f, 0.001f)); + EXPECT_THAT(m_camera.m_pitch, FloatNear(expectedPitch, 0.001f)); + EXPECT_THAT(m_camera.m_pivot, IsClose(m_pivot)); + EXPECT_THAT(m_camera.m_offset, IsClose(AZ::Vector3::CreateAxisY(-10.0f))); + EXPECT_THAT(m_camera.Translation(), IsCloseTolerance(expectedCameraEndingPosition, 0.01f)); + } + + TEST_F(CameraInputFixture, PivotRotateCameraInputRotatesYawOffsetByNinetyDegreesWithRequiredPixelDelta) + { + const auto cameraStartingPosition = AZ::Vector3(15.0f, -20.0f, 0.0f); + m_targetCamera.m_pivot = cameraStartingPosition; + + m_pivot = AZ::Vector3(10.0f, -10.0f, 0.0f); + + HandleEventAndUpdate(AzFramework::DiscreteInputEvent{ m_pivotChannelId, AzFramework::InputChannel::State::Began }); + HandleEventAndUpdate( + AzFramework::DiscreteInputEvent{ AzFramework::InputDeviceMouse::Button::Left, AzFramework::InputChannel::State::Began }); + HandleEventAndUpdate(AzFramework::HorizontalMotionEvent{ -PixelMotionDelta }); + + const auto expectedCameraEndingPosition = AZ::Vector3(20.0f, -5.0f, 0.0f); + const float expectedYaw = AzFramework::WrapYawRotation(AZ::Constants::HalfPi); + + using ::testing::FloatNear; + EXPECT_THAT(m_camera.m_yaw, FloatNear(expectedYaw, 0.001f)); + EXPECT_THAT(m_camera.m_pitch, FloatNear(0.0f, 0.001f)); + EXPECT_THAT(m_camera.m_pivot, IsClose(m_pivot)); + EXPECT_THAT(m_camera.m_offset, IsClose(AZ::Vector3(5.0f, -10.0f, 0.0f))); + EXPECT_THAT(m_camera.Translation(), IsCloseTolerance(expectedCameraEndingPosition, 0.01f)); } } // namespace UnitTest diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Viewport/ModularViewportCameraController.h b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Viewport/ModularViewportCameraController.h index 4956fbc1dc..32897ab4d9 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Viewport/ModularViewportCameraController.h +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Viewport/ModularViewportCameraController.h @@ -10,7 +10,6 @@ #include #include -#include #include #include @@ -103,7 +102,6 @@ namespace AtomToolsFramework class ModularViewportCameraControllerInstance final : public AzFramework::MultiViewportControllerInstanceInterface , public ModularViewportCameraControllerRequestBus::Handler - , private AzFramework::ViewportDebugDisplayEventBus::Handler { public: explicit ModularViewportCameraControllerInstance(AzFramework::ViewportId viewportId, ModularViewportCameraController* controller); @@ -121,9 +119,6 @@ namespace AtomToolsFramework void ClearReferenceFrame() override; private: - // AzFramework::ViewportDebugDisplayEventBus overrides ... - void DisplayViewport(const AzFramework::ViewportInfo& viewportInfo, AzFramework::DebugDisplayRequests& debugDisplay) override; - //! Update the reference frame after a change has been made to the camera //! view without updating the internal camera via user input. void RefreshReferenceFrame(); diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Viewport/ModularViewportCameraController.cpp b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Viewport/ModularViewportCameraController.cpp index bcc9a08f77..0bdd6fb55c 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Viewport/ModularViewportCameraController.cpp +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Viewport/ModularViewportCameraController.cpp @@ -21,15 +21,6 @@ namespace AtomToolsFramework { - AZ_CVAR( - AZ::Color, - ed_cameraSystemOrbitPointColor, - AZ::Color::CreateFromRgba(255, 255, 255, 255), - nullptr, - AZ::ConsoleFunctorFlags::Null, - ""); - AZ_CVAR(float, ed_cameraSystemOrbitPointSize, 0.1f, nullptr, AZ::ConsoleFunctorFlags::Null, ""); - AZ::Transform TransformFromMatrix4x4(const AZ::Matrix4x4& matrix) { const auto rotation = AZ::Matrix3x3::CreateFromMatrix4x4(matrix); @@ -200,14 +191,12 @@ namespace AtomToolsFramework m_cameraViewMatrixChangeHandler = AZ::RPI::ViewportContext::MatrixChangedEvent::Handler(handleCameraChange); m_modularCameraViewportContext->ConnectViewMatrixChangedHandler(m_cameraViewMatrixChangeHandler); - AzFramework::ViewportDebugDisplayEventBus::Handler::BusConnect(AzToolsFramework::GetEntityContextId()); ModularViewportCameraControllerRequestBus::Handler::BusConnect(viewportId); } ModularViewportCameraControllerInstance::~ModularViewportCameraControllerInstance() { ModularViewportCameraControllerRequestBus::Handler::BusDisconnect(); - AzFramework::ViewportDebugDisplayEventBus::Handler::BusDisconnect(); } bool ModularViewportCameraControllerInstance::HandleInputChannelEvent(const AzFramework::ViewportControllerInputEvent& event) @@ -272,7 +261,8 @@ namespace AtomToolsFramework const AZ::Vector3 eulerAngles = AzFramework::EulerAngles(AZ::Matrix3x3::CreateFromTransform(current)); m_camera.m_pitch = eulerAngles.GetX(); m_camera.m_yaw = eulerAngles.GetZ(); - m_camera.m_lookAt = current.GetTranslation(); + m_camera.m_pivot = current.GetTranslation(); + m_camera.m_offset = AZ::Vector3::CreateZero(); m_targetCamera = m_camera; m_modularCameraViewportContext->SetCameraTransform(current); @@ -287,17 +277,6 @@ namespace AtomToolsFramework m_updatingTransformInternally = false; } - void ModularViewportCameraControllerInstance::DisplayViewport( - [[maybe_unused]] const AzFramework::ViewportInfo& viewportInfo, AzFramework::DebugDisplayRequests& debugDisplay) - { - if (const float alpha = AZStd::min(-m_camera.m_lookDist / 5.0f, 1.0f); alpha > AZ::Constants::FloatEpsilon) - { - const AZ::Color orbitPointColor = ed_cameraSystemOrbitPointColor; - debugDisplay.SetColor(orbitPointColor.GetR(), orbitPointColor.GetG(), orbitPointColor.GetB(), alpha); - debugDisplay.DrawWireSphere(m_camera.m_lookAt, ed_cameraSystemOrbitPointSize); - } - } - void ModularViewportCameraControllerInstance::InterpolateToTransform(const AZ::Transform& worldFromLocal, const float lookAtDistance) { m_cameraMode = CameraMode::Animation; @@ -325,8 +304,8 @@ namespace AtomToolsFramework m_referenceFrameOverride = worldFromLocal; m_targetCamera.m_pitch = 0.0f; m_targetCamera.m_yaw = 0.0f; - m_targetCamera.m_lookAt = AZ::Vector3::CreateZero(); - m_targetCamera.m_lookDist = 0.0f; + m_targetCamera.m_offset = AZ::Vector3::CreateZero(); + m_targetCamera.m_pivot = AZ::Vector3::CreateZero(); m_camera = m_targetCamera; }