Merge remote-tracking branch 'upstream/stabilization/2110' into nvsickle/FixEntityOrdering

This commit is contained in:
nvsickle
2021-11-08 09:12:51 -08:00
222 changed files with 2256 additions and 1401 deletions
@@ -2,6 +2,7 @@
"gem_name": "PythonCoverage",
"display_name": "PythonCoverage",
"license": "Apache-2.0 Or MIT",
"license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt",
"origin": "Open 3D Engine - o3de.org",
"type": "Tool",
"summary": "A tool for generating gem coverage for Python tests.",
@@ -50,6 +50,7 @@ def NvCloth_AddClothSimulationToActor():
# Constants
FRAMES_IN_GAME_MODE = 200
CLOTH_GEM_ERROR_WARNING_LIST = ["Cloth", "NvCloth", "ClothComponentMesh", "ActorClothSkinning", "ActorClothSkinning", "TangentSpaceHelper", "MeshAssetHelper", "ActorAssetHelper", "ClothDebugDisplay"]
helper.init_idle()
# 1) Load the level
@@ -64,14 +65,16 @@ def NvCloth_AddClothSimulationToActor():
general.idle_wait_frames(FRAMES_IN_GAME_MODE)
# 5) Verify there are no errors and warnings in the logs
success_condition = not (section_tracer.has_errors or section_tracer.has_warnings)
Report.result(Tests.no_errors_and_warnings_found, success_condition)
if not success_condition:
if section_tracer.has_warnings:
Report.info(f"Warnings found: {section_tracer.warnings}")
if section_tracer.has_errors:
Report.info(f"Errors found: {section_tracer.errors}")
Report.failure(Tests.no_errors_and_warnings_found)
has_errors_or_warnings = False
for error_msg in section_tracer.errors:
if error_msg.window in CLOTH_GEM_ERROR_WARNING_LIST:
has_errors_or_warnings = True
Report.info(f"Cloth error found: {error_msg}")
for warning_msg in section_tracer.warnings:
if warning_msg.window in CLOTH_GEM_ERROR_WARNING_LIST:
has_errors_or_warnings = True
Report.info(f"Cloth warning found: {warning_msg}")
Report.result(Tests.no_errors_and_warnings_found, not has_errors_or_warnings)
# 6) Exit game mode
helper.exit_game_mode(Tests.exit_game_mode)
@@ -50,6 +50,7 @@ def NvCloth_AddClothSimulationToMesh():
# Constants
FRAMES_IN_GAME_MODE = 200
CLOTH_GEM_ERROR_WARNING_LIST = ["Cloth", "NvCloth", "ClothComponentMesh", "ActorClothSkinning", "ActorClothSkinning", "TangentSpaceHelper", "MeshAssetHelper", "ActorAssetHelper", "ClothDebugDisplay"]
helper.init_idle()
# 1) Load the level
@@ -64,14 +65,16 @@ def NvCloth_AddClothSimulationToMesh():
general.idle_wait_frames(FRAMES_IN_GAME_MODE)
# 5) Verify there are no errors and warnings in the logs
success_condition = not (section_tracer.has_errors or section_tracer.has_warnings)
Report.result(Tests.no_errors_and_warnings_found, success_condition)
if not success_condition:
if section_tracer.has_warnings:
Report.info(f"Warnings found: {section_tracer.warnings}")
if section_tracer.has_errors:
Report.info(f"Errors found: {section_tracer.errors}")
Report.failure(Tests.no_errors_and_warnings_found)
has_errors_or_warnings = False
for error_msg in section_tracer.errors:
if error_msg.window in CLOTH_GEM_ERROR_WARNING_LIST:
has_errors_or_warnings = True
Report.info(f"Cloth error found: {error_msg}")
for warning_msg in section_tracer.warnings:
if warning_msg.window in CLOTH_GEM_ERROR_WARNING_LIST:
has_errors_or_warnings = True
Report.info(f"Cloth warning found: {warning_msg}")
Report.result(Tests.no_errors_and_warnings_found, not has_errors_or_warnings)
# 6) Exit game mode
helper.exit_game_mode(Tests.exit_game_mode)
+4 -1
View File
@@ -2,10 +2,13 @@
"gem_name": "AutomatedTesting",
"display_name": "AutomatedTesting",
"license": "Apache-2.0 Or MIT",
"license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt",
"origin": "Amazon Web Services, Inc.",
"type": "Code",
"summary": "Project Gem for customizing the AutomatedTesting project functionality.",
"canonical_tags": ["Gem"],
"canonical_tags": [
"Gem"
],
"user_tags": [],
"icon_path": "preview.png",
"requirements": ""
@@ -13,9 +13,32 @@
#include <AzCore/std/smart_ptr/make_shared.h>
#include <AzFramework/Render/IntersectorInterface.h>
#include <AzToolsFramework/Viewport/ViewportMessages.h>
#include <AzToolsFramework/ViewportSelection/EditorSelectionUtil.h>
#include <AzToolsFramework/ViewportSelection/EditorTransformComponentSelectionRequestBus.h>
#include <EditorViewportSettings.h>
AZ_CVAR(
bool,
ed_cameraPinDefaultOrbit,
true,
nullptr,
AZ::ConsoleFunctorFlags::Null,
"Sets whether the default orbit point moves with the camera or not");
AZ_CVAR(
bool,
ed_cameraDefaultOrbitAxesOrtho,
true,
nullptr,
AZ::ConsoleFunctorFlags::Null,
"Sets whether to draw the default orbit point as orthographic or not");
AZ_CVAR(
float,
ed_cameraDefaultOrbitFadeDuration,
0.5f,
nullptr,
AZ::ConsoleFunctorFlags::Null,
"Sets how long the default orbit point should take to appear and disappear");
namespace SandboxEditor
{
static AzFramework::TranslateCameraInputChannelIds BuildTranslateCameraInputChannelIds()
@@ -174,7 +197,7 @@ namespace SandboxEditor
return SandboxEditor::CameraScrollSpeed();
};
const auto pivotFn = []
const auto pivotFn = []() -> AZStd::optional<AZ::Vector3>
{
// use the manipulator transform as the pivot point
AZStd::optional<AZ::Transform> entityPivot;
@@ -187,8 +210,7 @@ namespace SandboxEditor
return entityPivot->GetTranslation();
}
// otherwise just use the identity
return AZ::Vector3::CreateZero();
return AZStd::nullopt;
};
m_firstPersonFocusCamera =
@@ -199,9 +221,26 @@ namespace SandboxEditor
m_orbitCamera = AZStd::make_shared<AzFramework::OrbitCameraInput>(SandboxEditor::CameraOrbitChannelId());
m_orbitCamera->SetPivotFn(
[pivotFn]([[maybe_unused]] const AZ::Vector3& position, [[maybe_unused]] const AZ::Vector3& direction)
[this, pivotFn](const AZ::Vector3& position, const AZ::Vector3& direction)
{
return pivotFn();
// return the pivot
if (auto pivot = pivotFn())
{
return pivot.value();
}
// start ticking and drawing (for the default pivot)
AZ::TickBus::Handler::BusConnect();
AzFramework::ViewportDebugDisplayEventBus::Handler::BusConnect(AzToolsFramework::GetEntityContextId());
m_defaultOrbiting = true;
// calculate the default orbit point
if (!ed_cameraPinDefaultOrbit || m_orbitCamera->Beginning())
{
m_defaultOrbitPoint = position + direction * SandboxEditor::CameraDefaultOrbitDistance();
}
return m_defaultOrbitPoint;
});
m_orbitRotateCamera = AZStd::make_shared<AzFramework::RotateCameraInput>(SandboxEditor::CameraOrbitLookChannelId());
@@ -306,4 +345,67 @@ namespace SandboxEditor
m_viewportId, &AtomToolsFramework::ModularViewportCameraControllerRequestBus::Events::ClearReferenceFrame);
}
}
void EditorModularViewportCameraComposer::OnTick(const float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint time)
{
const float delta = [duration = &ed_cameraDefaultOrbitFadeDuration, deltaTime] {
if (*duration == 0.0f) {
return 1.0f;
}
return deltaTime / *duration;
}();
if (m_defaultOrbiting)
{
m_defaultOrbitOpacity = AZStd::min(m_defaultOrbitOpacity + delta, 1.0f);
}
else
{
m_defaultOrbitOpacity = AZStd::max(m_defaultOrbitOpacity - delta, 0.0f);
if (m_defaultOrbitOpacity == 0.0f)
{
AZ::TickBus::Handler::BusDisconnect();
AzFramework::ViewportDebugDisplayEventBus::Handler::BusDisconnect();
}
}
m_defaultOrbiting = false;
}
static void DrawTransformAxis(
AzFramework::DebugDisplayRequests& display,
const AzFramework::CameraState& cameraState,
const AZ::Vector3& pivot,
const float axisLength,
const float alpha)
{
const int prevState = display.GetState();
display.DepthWriteOff();
display.DepthTestOff();
display.CullOff();
const float orthoScale =
ed_cameraDefaultOrbitAxesOrtho ? AzToolsFramework::CalculateScreenToWorldMultiplier(pivot, cameraState) : 1.0f;
display.SetColor(AZ::Color::CreateFromVector3AndFloat(AZ::Colors::Red.GetAsVector3(), alpha));
display.DrawLine(pivot, pivot + AZ::Vector3::CreateAxisX() * axisLength * orthoScale);
display.SetColor(AZ::Color::CreateFromVector3AndFloat(AZ::Colors::LawnGreen.GetAsVector3(), alpha));
display.DrawLine(pivot, pivot + AZ::Vector3::CreateAxisY() * axisLength * orthoScale);
display.SetColor(AZ::Color::CreateFromVector3AndFloat(AZ::Colors::Blue.GetAsVector3(), alpha));
display.DrawLine(pivot, pivot + AZ::Vector3::CreateAxisZ() * axisLength * orthoScale);
display.DepthWriteOn();
display.DepthTestOn();
display.CullOn();
display.SetState(prevState);
}
void EditorModularViewportCameraComposer::DisplayViewport(
[[maybe_unused]] const AzFramework::ViewportInfo& viewportInfo, AzFramework::DebugDisplayRequests& debugDisplay)
{
DrawTransformAxis(
debugDisplay, AzToolsFramework::GetCameraState(viewportInfo.m_viewportId), m_defaultOrbitPoint, 1.0f, m_defaultOrbitOpacity);
}
} // namespace SandboxEditor
@@ -9,6 +9,8 @@
#pragma once
#include <AtomToolsFramework/Viewport/ModularViewportCameraController.h>
#include <AzCore/Component/TickBus.h>
#include <AzFramework/Entity/EntityDebugDisplayBus.h>
#include <AzFramework/Viewport/CameraInput.h>
#include <AzToolsFramework/API/EditorCameraBus.h>
#include <EditorModularViewportCameraComposerBus.h>
@@ -20,6 +22,8 @@ namespace SandboxEditor
class EditorModularViewportCameraComposer
: private EditorModularViewportCameraComposerNotificationBus::Handler
, private Camera::EditorCameraNotificationBus::Handler
, private AzFramework::ViewportDebugDisplayEventBus::Handler
, private AZ::TickBus::Handler
{
public:
SANDBOX_API explicit EditorModularViewportCameraComposer(AzFramework::ViewportId viewportId);
@@ -29,6 +33,12 @@ namespace SandboxEditor
SANDBOX_API AZStd::shared_ptr<AtomToolsFramework::ModularViewportCameraController> CreateModularViewportCameraController();
private:
// AzFramework::ViewportDebugDisplayEventBus overrides ...
void DisplayViewport(const AzFramework::ViewportInfo& viewportInfo, AzFramework::DebugDisplayRequests& debugDisplay) override;
// AZ::TickBus overrides ...
void OnTick(float deltaTime, AZ::ScriptTimePoint time) override;
//! Setup all internal camera inputs.
void SetupCameras();
@@ -52,5 +62,9 @@ namespace SandboxEditor
AZStd::shared_ptr<AzFramework::FocusCameraInput> m_orbitFocusCamera;
AzFramework::ViewportId m_viewportId;
float m_defaultOrbitOpacity = 0.0f; //!< The default orbit axes opacity (to fade in and out).
AZ::Vector3 m_defaultOrbitPoint = AZ::Vector3::CreateZero(); //!< The orbit point to use when no entity is selected.
bool m_defaultOrbiting = false; //!< Is the camera default orbiting (orbiting when there's no selected entity).
};
} // namespace SandboxEditor
@@ -61,7 +61,7 @@ static AZStd::vector<AZStd::string> GetEditorInputNames()
void CEditorPreferencesPage_ViewportCamera::Reflect(AZ::SerializeContext& serialize)
{
serialize.Class<CameraMovementSettings>()
->Version(3)
->Version(4)
->Field("TranslateSpeed", &CameraMovementSettings::m_translateSpeed)
->Field("RotateSpeed", &CameraMovementSettings::m_rotateSpeed)
->Field("BoostMultiplier", &CameraMovementSettings::m_boostMultiplier)
@@ -76,9 +76,8 @@ void CEditorPreferencesPage_ViewportCamera::Reflect(AZ::SerializeContext& serial
->Field("OrbitYawRotationInverted", &CameraMovementSettings::m_orbitYawRotationInverted)
->Field("PanInvertedX", &CameraMovementSettings::m_panInvertedX)
->Field("PanInvertedY", &CameraMovementSettings::m_panInvertedY)
->Field("DefaultPositionX", &CameraMovementSettings::m_defaultCameraPositionX)
->Field("DefaultPositionY", &CameraMovementSettings::m_defaultCameraPositionY)
->Field("DefaultPositionZ", &CameraMovementSettings::m_defaultCameraPositionZ);
->Field("DefaultPosition", &CameraMovementSettings::m_defaultPosition)
->Field("DefaultOrbitDistance", &CameraMovementSettings::m_defaultOrbitDistance);
serialize.Class<CameraInputSettings>()
->Version(2)
@@ -159,14 +158,12 @@ void CEditorPreferencesPage_ViewportCamera::Reflect(AZ::SerializeContext& serial
AZ::Edit::UIHandlers::CheckBox, &CameraMovementSettings::m_captureCursorLook, "Camera Capture Look Cursor",
"Should the cursor be captured (hidden) while performing free look")
->DataElement(
AZ::Edit::UIHandlers::SpinBox, &CameraMovementSettings::m_defaultCameraPositionX, "Default X Camera Position",
"Default X Camera Position when a level is opened")
AZ::Edit::UIHandlers::Vector3, &CameraMovementSettings::m_defaultPosition, "Default Camera Position",
"Default Camera Position when a level is first opened")
->DataElement(
AZ::Edit::UIHandlers::SpinBox, &CameraMovementSettings::m_defaultCameraPositionY, "Default Y Camera Position",
"Default Y Camera Position when a level is opened")
->DataElement(
AZ::Edit::UIHandlers::SpinBox, &CameraMovementSettings::m_defaultCameraPositionZ, "Default Z Camera Position",
"Default Z Camera Position when a level is opened");
AZ::Edit::UIHandlers::SpinBox, &CameraMovementSettings::m_defaultOrbitDistance, "Default Orbit Distance",
"The default distance to orbit about when there is no entity selected")
->Attribute(AZ::Edit::Attributes::Min, minValue);
editContext->Class<CameraInputSettings>("Camera Input Settings", "")
->DataElement(
@@ -283,12 +280,8 @@ void CEditorPreferencesPage_ViewportCamera::OnApply()
SandboxEditor::SetCameraOrbitYawRotationInverted(m_cameraMovementSettings.m_orbitYawRotationInverted);
SandboxEditor::SetCameraPanInvertedX(m_cameraMovementSettings.m_panInvertedX);
SandboxEditor::SetCameraPanInvertedY(m_cameraMovementSettings.m_panInvertedY);
SandboxEditor::SetDefaultCameraEditorPosition(
AZ::Vector3(
m_cameraMovementSettings.m_defaultCameraPositionX,
m_cameraMovementSettings.m_defaultCameraPositionY,
m_cameraMovementSettings.m_defaultCameraPositionZ
));
SandboxEditor::SetCameraDefaultEditorPosition(m_cameraMovementSettings.m_defaultPosition);
SandboxEditor::SetCameraDefaultOrbitDistance(m_cameraMovementSettings.m_defaultOrbitDistance);
SandboxEditor::SetCameraTranslateForwardChannelId(m_cameraInputSettings.m_translateForwardChannelId);
SandboxEditor::SetCameraTranslateBackwardChannelId(m_cameraInputSettings.m_translateBackwardChannelId);
@@ -325,11 +318,8 @@ void CEditorPreferencesPage_ViewportCamera::InitializeSettings()
m_cameraMovementSettings.m_orbitYawRotationInverted = SandboxEditor::CameraOrbitYawRotationInverted();
m_cameraMovementSettings.m_panInvertedX = SandboxEditor::CameraPanInvertedX();
m_cameraMovementSettings.m_panInvertedY = SandboxEditor::CameraPanInvertedY();
AZ::Vector3 defaultCameraPosition = SandboxEditor::DefaultEditorCameraPosition();
m_cameraMovementSettings.m_defaultCameraPositionX = defaultCameraPosition.GetX();
m_cameraMovementSettings.m_defaultCameraPositionY = defaultCameraPosition.GetY();
m_cameraMovementSettings.m_defaultCameraPositionZ = defaultCameraPosition.GetZ();
m_cameraMovementSettings.m_defaultPosition = SandboxEditor::CameraDefaultEditorPosition();
m_cameraMovementSettings.m_defaultOrbitDistance = SandboxEditor::CameraDefaultOrbitDistance();
m_cameraInputSettings.m_translateForwardChannelId = SandboxEditor::CameraTranslateForwardChannelId().GetName();
m_cameraInputSettings.m_translateBackwardChannelId = SandboxEditor::CameraTranslateBackwardChannelId().GetName();
@@ -9,9 +9,12 @@
#pragma once
#include "Include/IPreferencesPage.h"
#include <AzCore/Math/Vector3.h>
#include <AzCore/RTTI/RTTI.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <QIcon>
inline AZ::Crc32 EditorPropertyVisibility(const bool enabled)
@@ -43,6 +46,7 @@ private:
{
AZ_TYPE_INFO(CameraMovementSettings, "{60B8C07E-5F48-4171-A50B-F45558B5CCA1}")
AZ::Vector3 m_defaultPosition;
float m_translateSpeed;
float m_rotateSpeed;
float m_scrollSpeed;
@@ -50,16 +54,14 @@ private:
float m_panSpeed;
float m_boostMultiplier;
float m_rotateSmoothness;
bool m_rotateSmoothing;
float m_translateSmoothness;
bool m_translateSmoothing;
float m_defaultOrbitDistance;
bool m_captureCursorLook;
bool m_orbitYawRotationInverted;
bool m_panInvertedX;
bool m_panInvertedY;
float m_defaultCameraPositionX;
float m_defaultCameraPositionY;
float m_defaultCameraPositionZ;
bool m_rotateSmoothing;
bool m_translateSmoothing;
AZ::Crc32 RotateSmoothingVisibility() const
{
+17 -6
View File
@@ -38,6 +38,7 @@ namespace SandboxEditor
constexpr AZStd::string_view CameraTranslateSmoothingSetting = "/Amazon/Preferences/Editor/Camera/TranslateSmoothing";
constexpr AZStd::string_view CameraRotateSmoothingSetting = "/Amazon/Preferences/Editor/Camera/RotateSmoothing";
constexpr AZStd::string_view CameraCaptureCursorLookSetting = "/Amazon/Preferences/Editor/Camera/CaptureCursorLook";
constexpr AZStd::string_view CameraDefaultOrbitDistanceSetting = "/Amazon/Preferences/Editor/Camera/DefaultOrbitDistance";
constexpr AZStd::string_view CameraTranslateForwardIdSetting = "/Amazon/Preferences/Editor/Camera/CameraTranslateForwardId";
constexpr AZStd::string_view CameraTranslateBackwardIdSetting = "/Amazon/Preferences/Editor/Camera/CameraTranslateBackwardId";
constexpr AZStd::string_view CameraTranslateLeftIdSetting = "/Amazon/Preferences/Editor/Camera/CameraTranslateLeftId";
@@ -114,15 +115,15 @@ namespace SandboxEditor
return AZStd::make_unique<EditorViewportSettingsCallbacksImpl>();
}
AZ::Vector3 DefaultEditorCameraPosition()
AZ::Vector3 CameraDefaultEditorPosition()
{
float xPosition = aznumeric_cast<float>(GetRegistry(CameraDefaultStartingPositionX, 0.0));
float yPosition = aznumeric_cast<float>(GetRegistry(CameraDefaultStartingPositionY, -10.0));
float zPosition = aznumeric_cast<float>(GetRegistry(CameraDefaultStartingPositionZ, 4.0));
return AZ::Vector3(xPosition, yPosition, zPosition);
return AZ::Vector3(
aznumeric_cast<float>(GetRegistry(CameraDefaultStartingPositionX, 0.0)),
aznumeric_cast<float>(GetRegistry(CameraDefaultStartingPositionY, -10.0)),
aznumeric_cast<float>(GetRegistry(CameraDefaultStartingPositionZ, 4.0)));
}
void SetDefaultCameraEditorPosition(const AZ::Vector3 defaultCameraPosition)
void SetCameraDefaultEditorPosition(const AZ::Vector3& defaultCameraPosition)
{
SetRegistry(CameraDefaultStartingPositionX, defaultCameraPosition.GetX());
SetRegistry(CameraDefaultStartingPositionY, defaultCameraPosition.GetY());
@@ -359,6 +360,16 @@ namespace SandboxEditor
SetRegistry(CameraCaptureCursorLookSetting, capture);
}
float CameraDefaultOrbitDistance()
{
return aznumeric_cast<float>(GetRegistry(CameraDefaultOrbitDistanceSetting, 20.0));
}
void SetCameraDefaultOrbitDistance(const float distance)
{
SetRegistry(CameraDefaultOrbitDistanceSetting, distance);
}
AzFramework::InputChannelId CameraTranslateForwardChannelId()
{
return AzFramework::InputChannelId(
+6 -3
View File
@@ -33,9 +33,6 @@ namespace SandboxEditor
//! event will fire when a value in the settings registry (editorpreferences.setreg) is modified.
SANDBOX_API AZStd::unique_ptr<EditorViewportSettingsCallbacks> CreateEditorViewportSettingsCallbacks();
SANDBOX_API AZ::Vector3 DefaultEditorCameraPosition();
SANDBOX_API void SetDefaultCameraEditorPosition(AZ::Vector3 defaultCameraPosition);
SANDBOX_API AZ::u64 MaxItemsShownInAssetBrowserSearch();
SANDBOX_API void SetMaxItemsShownInAssetBrowserSearch(AZ::u64 numberOfItemsShown);
@@ -105,6 +102,12 @@ namespace SandboxEditor
SANDBOX_API bool CameraCaptureCursorForLook();
SANDBOX_API void SetCameraCaptureCursorForLook(bool capture);
SANDBOX_API AZ::Vector3 CameraDefaultEditorPosition();
SANDBOX_API void SetCameraDefaultEditorPosition(const AZ::Vector3& position);
SANDBOX_API float CameraDefaultOrbitDistance();
SANDBOX_API void SetCameraDefaultOrbitDistance(float distance);
SANDBOX_API AzFramework::InputChannelId CameraTranslateForwardChannelId();
SANDBOX_API void SetCameraTranslateForwardChannelId(AZStd::string_view cameraTranslateForwardId);
+9 -9
View File
@@ -620,9 +620,9 @@ void EditorViewportWidget::OnEditorNotifyEvent(EEditorNotifyEvent event)
break;
case eNotify_OnEndNewScene:
PopDisableRendering();
{
PopDisableRendering();
Matrix34 viewTM;
viewTM.SetIdentity();
@@ -638,9 +638,9 @@ void EditorViewportWidget::OnEditorNotifyEvent(EEditorNotifyEvent event)
break;
case eNotify_OnEndTerrainCreate:
PopDisableRendering();
{
PopDisableRendering();
Matrix34 viewTM;
viewTM.SetIdentity();
@@ -2021,10 +2021,6 @@ void EditorViewportWidget::SetDefaultCamera()
GetViewManager()->SetCameraObjectId(GUID_NULL);
SetName(m_defaultViewName);
// Set the default Editor Camera position.
m_defaultViewTM.SetTranslation(Vec3(m_editorViewportSettings.DefaultEditorCameraPosition()));
SetViewTM(m_defaultViewTM);
// Synchronize the configured editor viewport FOV to the default camera
if (m_viewPane)
{
@@ -2041,6 +2037,10 @@ void EditorViewportWidget::SetDefaultCamera()
atomViewportRequests->PushView(contextName, m_defaultView);
}
// Set the default Editor Camera position.
m_defaultViewTM.SetTranslation(Vec3(m_editorViewportSettings.DefaultEditorCameraPosition()));
SetViewTM(m_defaultViewTM);
PostCameraSet();
}
@@ -2527,7 +2527,7 @@ bool EditorViewportSettings::StickySelectEnabled() const
AZ::Vector3 EditorViewportSettings::DefaultEditorCameraPosition() const
{
return SandboxEditor::DefaultEditorCameraPosition();
return SandboxEditor::CameraDefaultEditorPosition();
}
AZ_CVAR_EXTERNED(bool, ed_previewGameInFullscreen_once);
@@ -10,6 +10,8 @@
#ifdef PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB
#include <AzFramework/XcbEventHandler.h>
#include <AzFramework/XcbConnectionManager.h>
#include <qpa/qplatformnativeinterface.h>
#endif
namespace Editor
@@ -23,16 +25,34 @@ namespace Editor
return nullptr;
}
xcb_connection_t* EditorQtApplicationXcb::GetXcbConnectionFromQt()
{
QPlatformNativeInterface* native = platformNativeInterface();
AZ_Warning("EditorQtApplicationXcb", native, "Unable to retrieve the native platform interface");
if (!native)
{
return nullptr;
}
return reinterpret_cast<xcb_connection_t*>(native->nativeResourceForIntegration(QByteArray("connection")));
}
void EditorQtApplicationXcb::OnStartPlayInEditor()
{
auto* interface = AzFramework::XcbConnectionManagerInterface::Get();
interface->SetEnableXInput(GetXcbConnectionFromQt(), true);
}
void EditorQtApplicationXcb::OnStopPlayInEditor()
{
auto* interface = AzFramework::XcbConnectionManagerInterface::Get();
interface->SetEnableXInput(GetXcbConnectionFromQt(), false);
}
bool EditorQtApplicationXcb::nativeEventFilter([[maybe_unused]] const QByteArray& eventType, void* message, long*)
{
if (GetIEditor()->IsInGameMode())
{
#ifdef PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB
// We need to handle RAW Input events in a separate loop. This is a workaround to enable XInput2 RAW Inputs using Editor mode.
// TODO To have this call here might be not be perfect.
AzFramework::XcbEventHandlerBus::Broadcast(&AzFramework::XcbEventHandler::PollSpecialEvents);
// Now handle the rest of the events.
AzFramework::XcbEventHandlerBus::Broadcast(
&AzFramework::XcbEventHandler::HandleXcbEvent, static_cast<xcb_generic_event_t*>(message));
#endif
@@ -6,19 +6,35 @@
*
*/
#if !defined(Q_MOC_RUN)
#include <Editor/Core/QtEditorApplication.h>
#include <AzToolsFramework/Entity/EditorEntityContextBus.h>
#endif
using xcb_connection_t = struct xcb_connection_t;
namespace Editor
{
class EditorQtApplicationXcb : public EditorQtApplication
class EditorQtApplicationXcb
: public EditorQtApplication
, public AzToolsFramework::EditorEntityContextNotificationBus::Handler
{
Q_OBJECT
public:
EditorQtApplicationXcb(int& argc, char** argv)
: EditorQtApplication(argc, argv)
{
// Connect bus to listen for OnStart/StopPlayInEditor events
AzToolsFramework::EditorEntityContextNotificationBus::Handler::BusConnect();
}
xcb_connection_t* GetXcbConnectionFromQt();
///////////////////////////////////////////////////////////////////////
// AzToolsFramework::EditorEntityContextNotificationBus overrides
void OnStartPlayInEditor() override;
void OnStopPlayInEditor() override;
// QAbstractNativeEventFilter:
bool nativeEventFilter(const QByteArray& eventType, void* message, long* result) override;
};
@@ -806,12 +806,20 @@ namespace AzFramework
[[maybe_unused]] float scrollDelta,
[[maybe_unused]] float deltaTime)
{
const auto pivot = m_pivotFn();
if (!pivot.has_value())
{
EndActivation();
return targetCamera;
}
if (Beginning())
{
// as the camera starts, record the camera we would like to end up as
m_nextCamera.m_offset = m_offsetFn(m_pivotFn().GetDistance(targetCamera.Translation()));
m_nextCamera.m_offset = m_offsetFn(pivot.value().GetDistance(targetCamera.Translation()));
const auto angles =
EulerAngles(AZ::Matrix3x3::CreateFromMatrix3x4(AZ::Matrix3x4::CreateLookAt(targetCamera.Translation(), m_pivotFn())));
EulerAngles(AZ::Matrix3x3::CreateFromMatrix3x4(AZ::Matrix3x4::CreateLookAt(targetCamera.Translation(), pivot.value())));
m_nextCamera.m_pitch = angles.GetX();
m_nextCamera.m_yaw = angles.GetZ();
m_nextCamera.m_pivot = targetCamera.m_pivot;
@@ -651,7 +651,7 @@ namespace AzFramework
class FocusCameraInput : public CameraInput
{
public:
using PivotFn = AZStd::function<AZ::Vector3()>;
using PivotFn = AZStd::function<AZStd::optional<AZ::Vector3>()>;
FocusCameraInput(const InputChannelId& focusChannelId, FocusOffsetFn offsetFn);
@@ -10,6 +10,8 @@
#include <AzFramework/XcbEventHandler.h>
#include <AzFramework/XcbInterface.h>
#include <xcb/xinput.h>
namespace AzFramework
{
////////////////////////////////////////////////////////////////////////////////////////////////
@@ -34,6 +36,31 @@ namespace AzFramework
return m_xcbConnection.get();
}
void SetEnableXInput(xcb_connection_t* connection, bool enable) override
{
struct Mask
{
xcb_input_event_mask_t head;
xcb_input_xi_event_mask_t mask;
};
const Mask mask {
/*.head=*/{
/*.device_id=*/XCB_INPUT_DEVICE_ALL_MASTER,
/*.mask_len=*/1
},
/*.mask=*/ enable ?
(xcb_input_xi_event_mask_t)(XCB_INPUT_XI_EVENT_MASK_RAW_MOTION | XCB_INPUT_XI_EVENT_MASK_RAW_BUTTON_PRESS | XCB_INPUT_XI_EVENT_MASK_RAW_BUTTON_RELEASE) :
(xcb_input_xi_event_mask_t)XCB_NONE
};
const xcb_setup_t* xcbSetup = xcb_get_setup(connection);
const xcb_screen_t* xcbScreen = xcb_setup_roots_iterator(xcbSetup).data;
xcb_input_xi_select_events(connection, xcbScreen->root, 1, &mask.head);
xcb_flush(connection);
}
private:
XcbUniquePtr<xcb_connection_t, xcb_disconnect> m_xcbConnection = nullptr;
};
@@ -24,6 +24,9 @@ namespace AzFramework
virtual ~XcbConnectionManager() = default;
virtual xcb_connection_t* GetXcbConnection() const = 0;
//! Enables/Disables XInput Raw Input events.
virtual void SetEnableXInput(xcb_connection_t* connection, bool enable) = 0;
};
class XcbConnectionManagerBusTraits
@@ -23,9 +23,6 @@ namespace AzFramework
virtual ~XcbEventHandler() = default;
virtual void HandleXcbEvent(xcb_generic_event_t* event) = 0;
// ATTN This is used as a workaround for RAW Input events when using the Editor.
virtual void PollSpecialEvents(){};
};
class XcbEventHandlerBusTraits : public AZ::EBusTraits
@@ -13,21 +13,68 @@
namespace AzFramework
{
xcb_window_t GetSystemCursorFocusWindow()
xcb_window_t GetSystemCursorFocusWindow(xcb_connection_t* connection)
{
void* systemCursorFocusWindow = nullptr;
AzFramework::InputSystemCursorConstraintRequestBus::BroadcastResult(
systemCursorFocusWindow, &AzFramework::InputSystemCursorConstraintRequests::GetSystemCursorConstraintWindow);
if (!systemCursorFocusWindow)
if (systemCursorFocusWindow)
{
return XCB_NONE;
return static_cast<xcb_window_t>(reinterpret_cast<uint64_t>(systemCursorFocusWindow));
}
// TODO Clang compile error because cast .... loses information. On GNU/Linux HWND is void* and on 64-bit
// machines its obviously 64 bit but we receive the window id from m_renderOverlay.winId() which is xcb_window_t 32-bit.
// EWMH-compliant window managers set the "_NET_ACTIVE_WINDOW" property
// of the X server's root window to the currently active window. This
// retrieves value of that property.
return static_cast<xcb_window_t>(reinterpret_cast<uint64_t>(systemCursorFocusWindow));
// Get the atom for the _NET_ACTIVE_WINDOW property
constexpr int propertyNameLength = 18;
xcb_generic_error_t* error = nullptr;
XcbStdFreePtr<xcb_intern_atom_reply_t> activeWindowAtom {xcb_intern_atom_reply(
connection,
xcb_intern_atom(connection, /*only_if_exists=*/ 1, propertyNameLength, "_NET_ACTIVE_WINDOW"),
&error
)};
if (!activeWindowAtom || error)
{
if (error)
{
AZ_Warning("XcbInput", false, "Retrieving _NET_ACTIVE_WINDOW atom failed : Error code %d", error->error_code);
free(error);
}
return XCB_WINDOW_NONE;
}
// Get the root window
const xcb_window_t rootWId = xcb_setup_roots_iterator(xcb_get_setup(connection)).data->root;
// Fetch the value of the root window's _NET_ACTIVE_WINDOW property
XcbStdFreePtr<xcb_get_property_reply_t> property {xcb_get_property_reply(
connection,
xcb_get_property(
/*c=*/connection,
/*_delete=*/ 0,
/*window=*/rootWId,
/*property=*/activeWindowAtom->atom,
/*type=*/XCB_ATOM_WINDOW,
/*long_offset=*/0,
/*long_length=*/1
),
&error
)};
if (!property || error)
{
if (error)
{
AZ_Warning("XcbInput", false, "Retrieving _NET_ACTIVE_WINDOW atom failed : Error code %d", error->error_code);
free(error);
}
return XCB_WINDOW_NONE;
}
return *static_cast<xcb_window_t*>(xcb_get_property_value(property.get()));
}
xcb_connection_t* XcbInputDeviceMouse::s_xcbConnection = nullptr;
@@ -39,8 +86,7 @@ namespace AzFramework
: InputDeviceMouse::Implementation(inputDevice)
, m_systemCursorState(SystemCursorState::Unknown)
, m_systemCursorPositionNormalized(0.5f, 0.5f)
, m_prevConstraintWindow(XCB_NONE)
, m_focusWindow(XCB_NONE)
, m_focusWindow(XCB_WINDOW_NONE)
, m_cursorShown(true)
{
XcbEventHandlerBus::Handler::BusConnect();
@@ -57,14 +103,14 @@ namespace AzFramework
InputDeviceMouse::Implementation* XcbInputDeviceMouse::Create(InputDeviceMouse& inputDevice)
{
auto* interface = AzFramework::XcbConnectionManagerInterface::Get();
const auto* interface = AzFramework::XcbConnectionManagerInterface::Get();
if (!interface)
{
AZ_Warning("XcbInput", false, "XCB interface not available");
return nullptr;
}
s_xcbConnection = AzFramework::XcbConnectionManagerInterface::Get()->GetXcbConnection();
s_xcbConnection = interface->GetXcbConnection();
if (!s_xcbConnection)
{
AZ_Warning("XcbInput", false, "XCB connection not available");
@@ -126,7 +172,7 @@ namespace AzFramework
// Get window information.
const XcbStdFreePtr<xcb_get_geometry_reply_t> xcbGeometryReply{ xcb_get_geometry_reply(
s_xcbConnection, xcb_get_geometry(s_xcbConnection, window), NULL) };
s_xcbConnection, xcb_get_geometry(s_xcbConnection, window), nullptr) };
if (!xcbGeometryReply)
{
@@ -137,7 +183,7 @@ namespace AzFramework
xcb_translate_coordinates(s_xcbConnection, window, s_xcbScreen->root, 0, 0);
const XcbStdFreePtr<xcb_translate_coordinates_reply_t> xkbTranslateCoordReply{ xcb_translate_coordinates_reply(
s_xcbConnection, translate_coord, NULL) };
s_xcbConnection, translate_coord, nullptr) };
if (!xkbTranslateCoordReply)
{
@@ -173,11 +219,11 @@ namespace AzFramework
for (const auto& barrier : m_activeBarriers)
{
xcb_void_cookie_t cookie = xcb_xfixes_create_pointer_barrier_checked(
s_xcbConnection, barrier.id, window, barrier.x0, barrier.y0, barrier.x1, barrier.y1, barrier.direction, 0, NULL);
const XcbStdFreePtr<xcb_generic_error_t> xkbError{ xcb_request_check(s_xcbConnection, cookie) };
s_xcbConnection, barrier.id, window, barrier.x0, barrier.y0, barrier.x1, barrier.y1, barrier.direction, 0, nullptr);
const XcbStdFreePtr<xcb_generic_error_t> xcbError{ xcb_request_check(s_xcbConnection, cookie) };
AZ_Warning(
"XcbInput", !xkbError, "XFixes, failed to create barrier %d at (%d %d %d %d)", barrier.id, barrier.x0, barrier.y0,
"XcbInput", !xcbError, "XFixes, failed to create barrier %d at (%d %d %d %d)", barrier.id, barrier.x0, barrier.y0,
barrier.x1, barrier.y1);
}
}
@@ -207,7 +253,7 @@ namespace AzFramework
const xcb_xfixes_query_version_cookie_t query_cookie = xcb_xfixes_query_version(s_xcbConnection, 5, 0);
xcb_generic_error_t* error = NULL;
xcb_generic_error_t* error = nullptr;
const XcbStdFreePtr<xcb_xfixes_query_version_reply_t> xkbQueryRequestReply{ xcb_xfixes_query_version_reply(
s_xcbConnection, query_cookie, &error) };
@@ -244,7 +290,7 @@ namespace AzFramework
const xcb_input_xi_query_version_cookie_t query_version_cookie = xcb_input_xi_query_version(s_xcbConnection, 2, 2);
xcb_generic_error_t* error = NULL;
xcb_generic_error_t* error = nullptr;
const XcbStdFreePtr<xcb_input_xi_query_version_reply_t> xkbQueryRequestReply{ xcb_input_xi_query_version_reply(
s_xcbConnection, query_version_cookie, &error) };
@@ -268,40 +314,13 @@ namespace AzFramework
return m_xInputInitialized;
}
void XcbInputDeviceMouse::SetEnableXInput(bool enable)
{
struct
{
xcb_input_event_mask_t head;
int mask;
} mask;
mask.head.deviceid = XCB_INPUT_DEVICE_ALL;
mask.head.mask_len = 1;
if (enable)
{
mask.mask = XCB_INPUT_XI_EVENT_MASK_RAW_MOTION | XCB_INPUT_XI_EVENT_MASK_RAW_BUTTON_PRESS |
XCB_INPUT_XI_EVENT_MASK_RAW_BUTTON_RELEASE | XCB_INPUT_XI_EVENT_MASK_MOTION | XCB_INPUT_XI_EVENT_MASK_BUTTON_PRESS |
XCB_INPUT_XI_EVENT_MASK_BUTTON_RELEASE;
}
else
{
mask.mask = XCB_NONE;
}
xcb_input_xi_select_events(s_xcbConnection, s_xcbScreen->root, 1, &mask.head);
xcb_flush(s_xcbConnection);
}
void XcbInputDeviceMouse::SetSystemCursorState(SystemCursorState systemCursorState)
{
if (systemCursorState != m_systemCursorState)
{
m_systemCursorState = systemCursorState;
m_focusWindow = GetSystemCursorFocusWindow();
m_focusWindow = GetSystemCursorFocusWindow(s_xcbConnection);
HandleCursorState(m_focusWindow, systemCursorState);
}
@@ -309,52 +328,10 @@ namespace AzFramework
void XcbInputDeviceMouse::HandleCursorState(xcb_window_t window, SystemCursorState systemCursorState)
{
bool confined = false, cursorShown = true;
switch (systemCursorState)
{
case SystemCursorState::ConstrainedAndHidden:
{
//!< Constrained to the application's main window and hidden
confined = true;
cursorShown = false;
}
break;
case SystemCursorState::ConstrainedAndVisible:
{
//!< Constrained to the application's main window and visible
confined = true;
}
break;
case SystemCursorState::UnconstrainedAndHidden:
{
//!< Free to move outside the main window but hidden while inside
cursorShown = false;
}
break;
case SystemCursorState::UnconstrainedAndVisible:
{
//!< Free to move outside the application's main window and visible
}
case SystemCursorState::Unknown:
default:
break;
}
// ATTN GetSystemCursorFocusWindow when getting out of the play in editor will return XCB_NONE
// We need however the window id to reset the cursor.
if (XCB_NONE == window && (confined || cursorShown))
{
// Reuse the previous window to reset states.
window = m_prevConstraintWindow;
m_prevConstraintWindow = XCB_NONE;
}
else
{
// Remember the window we used to modify cursor and barrier states.
m_prevConstraintWindow = window;
}
SetEnableXInput(!cursorShown);
const bool confined = (systemCursorState == SystemCursorState::ConstrainedAndHidden) ||
(systemCursorState == SystemCursorState::ConstrainedAndVisible);
const bool cursorShown = (systemCursorState == SystemCursorState::ConstrainedAndVisible) ||
(systemCursorState == SystemCursorState::UnconstrainedAndVisible);
CreateBarriers(window, confined);
ShowCursor(window, cursorShown);
@@ -368,26 +345,26 @@ namespace AzFramework
void XcbInputDeviceMouse::SetSystemCursorPositionNormalizedInternal(xcb_window_t window, AZ::Vector2 positionNormalized)
{
// TODO Basically not done at all. Added only the basic functions needed.
const XcbStdFreePtr<xcb_get_geometry_reply_t> xkbGeometryReply{ xcb_get_geometry_reply(
s_xcbConnection, xcb_get_geometry(s_xcbConnection, window), NULL) };
const XcbStdFreePtr<xcb_get_geometry_reply_t> xcbGeometryReply{ xcb_get_geometry_reply(
s_xcbConnection, xcb_get_geometry(s_xcbConnection, window), nullptr) };
if (!xkbGeometryReply)
if (!xcbGeometryReply)
{
return;
}
const int16_t x = static_cast<int16_t>(positionNormalized.GetX() * xkbGeometryReply->width);
const int16_t y = static_cast<int16_t>(positionNormalized.GetY() * xkbGeometryReply->height);
const int16_t x = static_cast<int16_t>(positionNormalized.GetX() * xcbGeometryReply->width);
const int16_t y = static_cast<int16_t>(positionNormalized.GetY() * xcbGeometryReply->height);
xcb_warp_pointer(s_xcbConnection, XCB_NONE, window, 0, 0, 0, 0, x, y);
xcb_warp_pointer(s_xcbConnection, XCB_WINDOW_NONE, window, 0, 0, 0, 0, x, y);
xcb_flush(s_xcbConnection);
}
void XcbInputDeviceMouse::SetSystemCursorPositionNormalized(AZ::Vector2 positionNormalized)
{
const xcb_window_t window = GetSystemCursorFocusWindow();
if (XCB_NONE == window)
const xcb_window_t window = GetSystemCursorFocusWindow(s_xcbConnection);
if (XCB_WINDOW_NONE == window)
{
return;
}
@@ -401,7 +378,7 @@ namespace AzFramework
const xcb_query_pointer_cookie_t pointer = xcb_query_pointer(s_xcbConnection, window);
const XcbStdFreePtr<xcb_query_pointer_reply_t> xkbQueryPointerReply{ xcb_query_pointer_reply(s_xcbConnection, pointer, NULL) };
const XcbStdFreePtr<xcb_query_pointer_reply_t> xkbQueryPointerReply{ xcb_query_pointer_reply(s_xcbConnection, pointer, nullptr) };
if (!xkbQueryPointerReply)
{
@@ -409,7 +386,7 @@ namespace AzFramework
}
const XcbStdFreePtr<xcb_get_geometry_reply_t> xkbGeometryReply{ xcb_get_geometry_reply(
s_xcbConnection, xcb_get_geometry(s_xcbConnection, window), NULL) };
s_xcbConnection, xcb_get_geometry(s_xcbConnection, window), nullptr) };
if (!xkbGeometryReply)
{
@@ -429,8 +406,8 @@ namespace AzFramework
AZ::Vector2 XcbInputDeviceMouse::GetSystemCursorPositionNormalized() const
{
const xcb_window_t window = GetSystemCursorFocusWindow();
if (XCB_NONE == window)
const xcb_window_t window = GetSystemCursorFocusWindow(s_xcbConnection);
if (XCB_WINDOW_NONE == window)
{
return AZ::Vector2::CreateZero();
}
@@ -455,11 +432,11 @@ namespace AzFramework
cookie = xcb_xfixes_hide_cursor_checked(s_xcbConnection, window);
}
const XcbStdFreePtr<xcb_generic_error_t> xkbError{ xcb_request_check(s_xcbConnection, cookie) };
const XcbStdFreePtr<xcb_generic_error_t> xcbError{ xcb_request_check(s_xcbConnection, cookie) };
if (xkbError)
if (xcbError)
{
AZ_Warning("XcbInput", false, "ShowCursor failed: %d", xkbError->error_code);
AZ_Warning("XcbInput", false, "ShowCursor failed: %d", xcbError->error_code);
return;
}
@@ -500,14 +477,6 @@ namespace AzFramework
}
}
void XcbInputDeviceMouse::HandlePointerMotionEvents(const xcb_generic_event_t* event)
{
const xcb_input_motion_event_t* mouseMotionEvent = reinterpret_cast<const xcb_input_motion_event_t*>(event);
m_systemCursorPosition[0] = mouseMotionEvent->event_x;
m_systemCursorPosition[1] = mouseMotionEvent->event_y;
}
void XcbInputDeviceMouse::HandleRawInputEvents(const xcb_ge_generic_event_t* event)
{
const xcb_ge_generic_event_t* genericEvent = reinterpret_cast<const xcb_ge_generic_event_t*>(event);
@@ -552,78 +521,20 @@ namespace AzFramework
}
}
void XcbInputDeviceMouse::PollSpecialEvents()
{
while (xcb_generic_event_t* genericEvent = xcb_poll_for_queued_event(s_xcbConnection))
{
// TODO Is the following correct? If we are showing the cursor, don't poll RAW Input events.
switch (genericEvent->response_type & ~0x80)
{
case XCB_GE_GENERIC:
{
const xcb_ge_generic_event_t* geGenericEvent = reinterpret_cast<const xcb_ge_generic_event_t*>(genericEvent);
// Only handle raw inputs if we have focus.
// Handle Raw Input events first.
if ((geGenericEvent->event_type == XCB_INPUT_RAW_BUTTON_PRESS) ||
(geGenericEvent->event_type == XCB_INPUT_RAW_BUTTON_RELEASE) ||
(geGenericEvent->event_type == XCB_INPUT_RAW_MOTION))
{
HandleRawInputEvents(geGenericEvent);
free(genericEvent);
}
}
break;
}
}
}
void XcbInputDeviceMouse::HandleXcbEvent(xcb_generic_event_t* event)
{
switch (event->response_type & ~0x80)
{
// QT5 is using by default XInput which means we do need to check for XCB_GE_GENERIC event to parse all mouse related events.
// XInput raw events are sent from the server as a XCB_GE_GENERIC
// event. A XCB_GE_GENERIC event is typecast to a
// xcb_ge_generic_event_t, which is distinct from a
// xcb_generic_event_t, and exists so that X11 extensions can extend
// the event emission beyond the size that a normal X11 event could
// contain.
case XCB_GE_GENERIC:
{
const xcb_ge_generic_event_t* genericEvent = reinterpret_cast<const xcb_ge_generic_event_t*>(event);
// Handling RAW Inputs here works in GameMode but not in Editor mode because QT is
// not handling RAW input events and passing to.
if (!m_cursorShown)
{
// Handle Raw Input events first.
if ((genericEvent->event_type == XCB_INPUT_RAW_BUTTON_PRESS) ||
(genericEvent->event_type == XCB_INPUT_RAW_BUTTON_RELEASE) || (genericEvent->event_type == XCB_INPUT_RAW_MOTION))
{
HandleRawInputEvents(genericEvent);
}
}
else
{
switch (genericEvent->event_type)
{
case XCB_INPUT_BUTTON_PRESS:
{
const xcb_input_button_press_event_t* mouseButtonEvent =
reinterpret_cast<const xcb_input_button_press_event_t*>(genericEvent);
HandleButtonPressEvents(mouseButtonEvent->detail, true);
}
break;
case XCB_INPUT_BUTTON_RELEASE:
{
const xcb_input_button_release_event_t* mouseButtonEvent =
reinterpret_cast<const xcb_input_button_release_event_t*>(genericEvent);
HandleButtonPressEvents(mouseButtonEvent->detail, false);
}
break;
case XCB_INPUT_MOTION:
{
HandlePointerMotionEvents(event);
}
break;
}
}
HandleRawInputEvents(genericEvent);
}
break;
case XCB_FOCUS_IN:
@@ -634,6 +545,9 @@ namespace AzFramework
m_focusWindow = focusInEvent->event;
HandleCursorState(m_focusWindow, m_systemCursorState);
}
auto* interface = AzFramework::XcbConnectionManagerInterface::Get();
interface->SetEnableXInput(interface->GetXcbConnection(), true);
}
break;
case XCB_FOCUS_OUT:
@@ -644,7 +558,10 @@ namespace AzFramework
ProcessRawEventQueues();
ResetInputChannelStates();
m_focusWindow = XCB_NONE;
m_focusWindow = XCB_WINDOW_NONE;
auto* interface = AzFramework::XcbConnectionManagerInterface::Get();
interface->SetEnableXInput(interface->GetXcbConnection(), false);
}
break;
}
@@ -65,9 +65,6 @@ namespace AzFramework
//! \ref AzFramework::InputDeviceMouse::Implementation::TickInputDevice
void TickInputDevice() override;
//! This method is called by the Editor to accommodate some events with the Editor. Never called in Game mode.
void PollSpecialEvents() override;
//! Handle X11 events.
void HandleXcbEvent(xcb_generic_event_t* event) override;
@@ -77,9 +74,6 @@ namespace AzFramework
//! Initialize XInput extension. Used for raw input during confinement and showing/hiding the cursor.
static bool InitializeXInput();
//! Enables/Disables XInput Raw Input events.
void SetEnableXInput(bool enable);
//! Create barriers.
void CreateBarriers(xcb_window_t window, bool create);
@@ -98,9 +92,6 @@ namespace AzFramework
//! Handle button press/release events.
void HandleButtonPressEvents(uint32_t detail, bool pressed);
//! Handle motion notify events.
void HandlePointerMotionEvents(const xcb_generic_event_t* event);
//! Will set cursor states and confinement modes.
void HandleCursorState(xcb_window_t window, SystemCursorState systemCursorState);
@@ -160,7 +151,6 @@ namespace AzFramework
AZ::Vector2 m_cursorHiddenPosition;
AZ::Vector2 m_systemCursorPositionNormalized;
uint32_t m_systemCursorPosition[MAX_XI_RAW_AXIS];
static xcb_connection_t* s_xcbConnection;
static xcb_screen_t* s_xcbScreen;
@@ -171,9 +161,6 @@ namespace AzFramework
//! Will be true if the xinput2 extension could be initialized.
static bool m_xInputInitialized;
//! The window that had focus
xcb_window_t m_prevConstraintWindow;
//! The current window that has focus
xcb_window_t m_focusWindow;
@@ -10,6 +10,8 @@
#include <AzCore/IO/SystemFile.h>
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
#include <AzFramework/Process/ProcessWatcher.h>
#include <sys/types.h>
#include <unistd.h>
@@ -24,14 +26,20 @@ namespace AzFramework::AssetSystem::Platform
AZ::IO::FixedMaxPath assetProcessorPath{ executableDirectory };
// In Mac the Editor and game is within a bundle, so the path to the sibling app
// has to go up from the Contents/MacOS folder the binary is in
assetProcessorPath /= "../../../AssetProcessor.app";
assetProcessorPath /= "../../../AssetProcessor.app/Contents/MacOS/AssetProcessor";
assetProcessorPath = assetProcessorPath.LexicallyNormal();
if (!AZ::IO::SystemFile::Exists(assetProcessorPath.c_str()))
{
// Check for existence of one under a "bin" directory, i.e. engineRoot is an SDK structure.
assetProcessorPath =
AZ::IO::FixedMaxPath{engineRoot} / "bin" / AZ_TRAIT_OS_PLATFORM_NAME / AZ_BUILD_CONFIGURATION_TYPE / "AssetProcessor.app";
if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr)
{
if (AZ::IO::FixedMaxPath installedBinariesPath;
settingsRegistry->Get(installedBinariesPath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_InstalledBinaryFolder))
{
// Check for existence of one under a "bin" directory, i.e. engineRoot is an SDK structure.
assetProcessorPath = AZ::IO::FixedMaxPath{ engineRoot } / installedBinariesPath / "AssetProcessor.app/Contents/MacOS/AssetProcessor";
}
}
if (!AZ::IO::SystemFile::Exists(assetProcessorPath.c_str()))
{
@@ -39,23 +47,21 @@ namespace AzFramework::AssetSystem::Platform
}
}
auto fullLaunchCommand = AZ::IO::FixedMaxPathString::format(R"(open -g "%s" --args --start-hidden)", assetProcessorPath.c_str());
AZStd::string commandLineParams;
// Add the engine path to the launch command if not empty
if (!engineRoot.empty())
{
fullLaunchCommand += R"( --engine-path=")";
fullLaunchCommand += engineRoot;
fullLaunchCommand += '"';
commandLineParams += AZStd::string::format("\"--engine-path=\"%s\"\"", engineRoot.data());
}
// Add the active project path to the launch command if not empty
if (!projectPath.empty())
{
fullLaunchCommand += R"( --project-path=")";
fullLaunchCommand += projectPath;
fullLaunchCommand += '"';
commandLineParams += AZStd::string::format(" \"--regset=/Amazon/AzCore/Bootstrap/project_path=\"%s\"\"", projectPath.data());
}
return system(fullLaunchCommand.c_str()) == 0;
AzFramework::ProcessLauncher::ProcessLaunchInfo processLaunchInfo;
processLaunchInfo.m_processExecutableString = AZStd::move(assetProcessorPath.Native());
processLaunchInfo.m_commandlineParameters = commandLineParams;
return AzFramework::ProcessLauncher::LaunchUnwatchedProcess(processLaunchInfo);
}
}
@@ -11,6 +11,13 @@
#include <gtest/gtest.h>
#include <gmock/gmock.h>
ACTION_TEMPLATE(ReturnMalloc,
HAS_1_TEMPLATE_PARAMS(typename, T),
AND_0_VALUE_PARAMS()) {
T* value = static_cast<T*>(malloc(sizeof(T)));
*value = T{};
return value;
}
ACTION_TEMPLATE(ReturnMalloc,
HAS_1_TEMPLATE_PARAMS(typename, T),
AND_1_VALUE_PARAMS(p0)) {
@@ -25,3 +32,38 @@ ACTION_TEMPLATE(ReturnMalloc,
*value = T{ p0, p1 };
return value;
}
ACTION_TEMPLATE(ReturnMalloc,
HAS_1_TEMPLATE_PARAMS(typename, T),
AND_3_VALUE_PARAMS(p0, p1, p2)) {
T* value = static_cast<T*>(malloc(sizeof(T)));
*value = T{ p0, p1, p2 };
return value;
}
ACTION_TEMPLATE(ReturnMalloc,
HAS_1_TEMPLATE_PARAMS(typename, T),
AND_4_VALUE_PARAMS(p0, p1, p2, p3)) {
T* value = static_cast<T*>(malloc(sizeof(T)));
*value = T{ p0, p1, p2, p3 };
return value;
}
ACTION_TEMPLATE(ReturnMalloc,
HAS_1_TEMPLATE_PARAMS(typename, T),
AND_5_VALUE_PARAMS(p0, p1, p2, p3, p4)) {
T* value = static_cast<T*>(malloc(sizeof(T)));
*value = T{ p0, p1, p2, p3, p4 };
return value;
}
ACTION_TEMPLATE(ReturnMalloc,
HAS_1_TEMPLATE_PARAMS(typename, T),
AND_6_VALUE_PARAMS(p0, p1, p2, p3, p4, p5)) {
T* value = static_cast<T*>(malloc(sizeof(T)));
*value = T{ p0, p1, p2, p3, p4, p5 };
return value;
}
ACTION_TEMPLATE(ReturnMalloc,
HAS_1_TEMPLATE_PARAMS(typename, T),
AND_7_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6)) {
T* value = static_cast<T*>(malloc(sizeof(T)));
*value = T{ p0, p1, p2, p3, p4, p5, p6 };
return value;
}
@@ -32,6 +32,82 @@ xcb_generic_error_t* xcb_request_check(xcb_connection_t* c, xcb_void_cookie_t co
{
return MockXcbInterface::Instance()->xcb_request_check(c, cookie);
}
const xcb_setup_t* xcb_get_setup(xcb_connection_t *c)
{
return MockXcbInterface::Instance()->xcb_get_setup(c);
}
xcb_screen_iterator_t xcb_setup_roots_iterator(const xcb_setup_t* R)
{
return MockXcbInterface::Instance()->xcb_setup_roots_iterator(R);
}
const xcb_query_extension_reply_t* xcb_get_extension_data(xcb_connection_t* c, xcb_extension_t* ext)
{
return MockXcbInterface::Instance()->xcb_get_extension_data(c, ext);
}
int xcb_flush(xcb_connection_t *c)
{
return MockXcbInterface::Instance()->xcb_flush(c);
}
xcb_query_pointer_cookie_t xcb_query_pointer(xcb_connection_t* c, xcb_window_t window)
{
return MockXcbInterface::Instance()->xcb_query_pointer(c, window);
}
xcb_query_pointer_reply_t* xcb_query_pointer_reply(xcb_connection_t* c, xcb_query_pointer_cookie_t cookie, xcb_generic_error_t** e)
{
return MockXcbInterface::Instance()->xcb_query_pointer_reply(c, cookie, e);
}
xcb_get_geometry_cookie_t xcb_get_geometry(xcb_connection_t* c, xcb_drawable_t drawable)
{
return MockXcbInterface::Instance()->xcb_get_geometry(c, drawable);
}
xcb_get_geometry_reply_t* xcb_get_geometry_reply(xcb_connection_t* c, xcb_get_geometry_cookie_t cookie, xcb_generic_error_t** e)
{
return MockXcbInterface::Instance()->xcb_get_geometry_reply(c, cookie, e);
}
xcb_void_cookie_t xcb_warp_pointer(
xcb_connection_t* c,
xcb_window_t src_window,
xcb_window_t dst_window,
int16_t src_x,
int16_t src_y,
uint16_t src_width,
uint16_t src_height,
int16_t dst_x,
int16_t dst_y)
{
return MockXcbInterface::Instance()->xcb_warp_pointer(c, src_window, dst_window, src_x, src_y, src_width, src_height, dst_x, dst_y);
}
xcb_intern_atom_cookie_t xcb_intern_atom(xcb_connection_t* c, uint8_t only_if_exists, uint16_t name_len, const char* name)
{
return MockXcbInterface::Instance()->xcb_intern_atom(c, only_if_exists, name_len, name);
}
xcb_intern_atom_reply_t* xcb_intern_atom_reply(xcb_connection_t* c, xcb_intern_atom_cookie_t cookie, xcb_generic_error_t** e)
{
return MockXcbInterface::Instance()->xcb_intern_atom_reply(c, cookie, e);
}
xcb_get_property_cookie_t xcb_get_property(
xcb_connection_t* c,
uint8_t _delete,
xcb_window_t window,
xcb_atom_t property,
xcb_atom_t type,
uint32_t long_offset,
uint32_t long_length)
{
return MockXcbInterface::Instance()->xcb_get_property(c, _delete, window, property, type, long_offset, long_length);
}
xcb_get_property_reply_t* xcb_get_property_reply(xcb_connection_t* c, xcb_get_property_cookie_t cookie, xcb_generic_error_t** e)
{
return MockXcbInterface::Instance()->xcb_get_property_reply(c, cookie, e);
}
void* xcb_get_property_value(const xcb_get_property_reply_t* R)
{
return MockXcbInterface::Instance()->xcb_get_property_value(R);
}
uint32_t xcb_generate_id(xcb_connection_t *c)
{
return MockXcbInterface::Instance()->xcb_generate_id(c);
}
// ----------------------------------------------------------------------------
// xcb-xkb
@@ -116,4 +192,76 @@ xkb_state_component xkb_state_update_mask(
state, depressed_mods, latched_mods, locked_mods, depressed_layout, latched_layout, locked_layout);
}
// ----------------------------------------------------------------------------
// xcb-xfixes
xcb_xfixes_query_version_cookie_t xcb_xfixes_query_version(
xcb_connection_t* c, uint32_t client_major_version, uint32_t client_minor_version)
{
return MockXcbInterface::Instance()->xcb_xfixes_query_version(c, client_major_version, client_minor_version);
}
xcb_xfixes_query_version_reply_t* xcb_xfixes_query_version_reply(
xcb_connection_t* c, xcb_xfixes_query_version_cookie_t cookie, xcb_generic_error_t** e)
{
return MockXcbInterface::Instance()->xcb_xfixes_query_version_reply(c, cookie, e);
}
xcb_void_cookie_t xcb_xfixes_show_cursor_checked(xcb_connection_t* c, xcb_window_t window)
{
return MockXcbInterface::Instance()->xcb_xfixes_show_cursor_checked(c, window);
}
xcb_void_cookie_t xcb_xfixes_hide_cursor_checked(xcb_connection_t* c, xcb_window_t window)
{
return MockXcbInterface::Instance()->xcb_xfixes_hide_cursor_checked(c, window);
}
xcb_void_cookie_t xcb_xfixes_delete_pointer_barrier_checked(xcb_connection_t* c, xcb_xfixes_barrier_t barrier)
{
return MockXcbInterface::Instance()->xcb_xfixes_delete_pointer_barrier_checked(c, barrier);
}
xcb_translate_coordinates_cookie_t xcb_translate_coordinates(xcb_connection_t* c, xcb_window_t src_window, xcb_window_t dst_window, int16_t src_x, int16_t src_y)
{
return MockXcbInterface::Instance()->xcb_translate_coordinates(c, src_window, dst_window, src_x, src_y);
}
xcb_translate_coordinates_reply_t* xcb_translate_coordinates_reply(xcb_connection_t* c, xcb_translate_coordinates_cookie_t cookie, xcb_generic_error_t** e)
{
return MockXcbInterface::Instance()->xcb_translate_coordinates_reply(c, cookie, e);
}
xcb_void_cookie_t xcb_xfixes_create_pointer_barrier_checked(
xcb_connection_t* c,
xcb_xfixes_barrier_t barrier,
xcb_window_t window,
uint16_t x1,
uint16_t y1,
uint16_t x2,
uint16_t y2,
uint32_t directions,
uint16_t num_devices,
const uint16_t* devices)
{
return MockXcbInterface::Instance()->xcb_xfixes_create_pointer_barrier_checked(c, barrier, window, x1, y1, x2, y2, directions, num_devices, devices);
}
// ----------------------------------------------------------------------------
// xcb-xinput
xcb_input_xi_query_version_cookie_t xcb_input_xi_query_version(xcb_connection_t* c, uint16_t major_version, uint16_t minor_version)
{
return MockXcbInterface::Instance()->xcb_input_xi_query_version(c, major_version, minor_version);
}
xcb_input_xi_query_version_reply_t* xcb_input_xi_query_version_reply(
xcb_connection_t* c, xcb_input_xi_query_version_cookie_t cookie, xcb_generic_error_t** e)
{
return MockXcbInterface::Instance()->xcb_input_xi_query_version_reply(c, cookie, e);
}
xcb_void_cookie_t xcb_input_xi_select_events(
xcb_connection_t* c, xcb_window_t window, uint16_t num_mask, const xcb_input_event_mask_t* masks)
{
return MockXcbInterface::Instance()->xcb_input_xi_select_events(c, window, num_mask, masks);
}
int xcb_input_raw_button_press_axisvalues_length (const xcb_input_raw_button_press_event_t *R)
{
return MockXcbInterface::Instance()->xcb_input_raw_button_press_axisvalues_length(R);
}
xcb_input_fp3232_t* xcb_input_raw_button_press_axisvalues_raw(const xcb_input_raw_button_press_event_t* R)
{
return MockXcbInterface::Instance()->xcb_input_raw_button_press_axisvalues_raw(R);
}
}
@@ -18,6 +18,8 @@
#undef explicit
#include <xkbcommon/xkbcommon.h>
#include <xkbcommon/xkbcommon-x11.h>
#include <xcb/xfixes.h>
#include <xcb/xinput.h>
#include "Printers.h"
@@ -62,6 +64,37 @@ public:
MOCK_CONST_METHOD1(xcb_disconnect, void(xcb_connection_t* c));
MOCK_CONST_METHOD1(xcb_poll_for_event, xcb_generic_event_t*(xcb_connection_t* c));
MOCK_CONST_METHOD2(xcb_request_check, xcb_generic_error_t*(xcb_connection_t* c, xcb_void_cookie_t cookie));
MOCK_CONST_METHOD1(xcb_get_setup, const xcb_setup_t*(xcb_connection_t *c));
MOCK_CONST_METHOD1(xcb_setup_roots_iterator, xcb_screen_iterator_t(const xcb_setup_t* R));
MOCK_CONST_METHOD2(xcb_get_extension_data, const xcb_query_extension_reply_t*(xcb_connection_t* c, xcb_extension_t* ext));
MOCK_CONST_METHOD1(xcb_flush, int(xcb_connection_t *c));
MOCK_CONST_METHOD2(xcb_query_pointer, xcb_query_pointer_cookie_t(xcb_connection_t* c, xcb_window_t window));
MOCK_CONST_METHOD3(xcb_query_pointer_reply, xcb_query_pointer_reply_t*(xcb_connection_t* c, xcb_query_pointer_cookie_t cookie, xcb_generic_error_t** e));
MOCK_CONST_METHOD2(xcb_get_geometry, xcb_get_geometry_cookie_t(xcb_connection_t* c, xcb_drawable_t drawable));
MOCK_CONST_METHOD3(xcb_get_geometry_reply, xcb_get_geometry_reply_t*(xcb_connection_t* c, xcb_get_geometry_cookie_t cookie, xcb_generic_error_t** e));
MOCK_CONST_METHOD9(xcb_warp_pointer, xcb_void_cookie_t(
xcb_connection_t* c,
xcb_window_t src_window,
xcb_window_t dst_window,
int16_t src_x,
int16_t src_y,
uint16_t src_width,
uint16_t src_height,
int16_t dst_x,
int16_t dst_y));
MOCK_CONST_METHOD4(xcb_intern_atom, xcb_intern_atom_cookie_t(xcb_connection_t* c, uint8_t only_if_exists, uint16_t name_len, const char* name));
MOCK_CONST_METHOD3(xcb_intern_atom_reply, xcb_intern_atom_reply_t*(xcb_connection_t* c, xcb_intern_atom_cookie_t cookie, xcb_generic_error_t** e));
MOCK_CONST_METHOD7(xcb_get_property, xcb_get_property_cookie_t(
xcb_connection_t* c,
uint8_t _delete,
xcb_window_t window,
xcb_atom_t property,
xcb_atom_t type,
uint32_t long_offset,
uint32_t long_length));
MOCK_CONST_METHOD3(xcb_get_property_reply, xcb_get_property_reply_t*(xcb_connection_t* c, xcb_get_property_cookie_t cookie, xcb_generic_error_t** e));
MOCK_CONST_METHOD1(xcb_get_property_value, void*(const xcb_get_property_reply_t* R));
MOCK_CONST_METHOD1(xcb_generate_id, uint32_t(xcb_connection_t *c));
// xcb-xkb
MOCK_CONST_METHOD3(xcb_xkb_use_extension, xcb_xkb_use_extension_cookie_t(xcb_connection_t* c, uint16_t wantedMajor, uint16_t wantedMinor));
@@ -83,6 +116,33 @@ public:
MOCK_CONST_METHOD4(xkb_state_key_get_utf8, int(xkb_state* state, xkb_keycode_t key, char* buffer, size_t size));
MOCK_CONST_METHOD7(xkb_state_update_mask, xkb_state_component(xkb_state* state, xkb_mod_mask_t depressed_mods, xkb_mod_mask_t latched_mods, xkb_mod_mask_t locked_mods, xkb_layout_index_t depressed_layout, xkb_layout_index_t latched_layout, xkb_layout_index_t locked_layout));
// xcb-xfixes
MOCK_CONST_METHOD3(xcb_xfixes_query_version, xcb_xfixes_query_version_cookie_t(xcb_connection_t* c, uint32_t client_major_version, uint32_t client_minor_version));
MOCK_CONST_METHOD3(xcb_xfixes_query_version_reply, xcb_xfixes_query_version_reply_t*(xcb_connection_t* c, xcb_xfixes_query_version_cookie_t cookie, xcb_generic_error_t** e));
MOCK_CONST_METHOD2(xcb_xfixes_show_cursor_checked, xcb_void_cookie_t(xcb_connection_t* c, xcb_window_t window));
MOCK_CONST_METHOD2(xcb_xfixes_hide_cursor_checked, xcb_void_cookie_t(xcb_connection_t* c, xcb_window_t window));
MOCK_CONST_METHOD2(xcb_xfixes_delete_pointer_barrier_checked, xcb_void_cookie_t(xcb_connection_t* c, xcb_xfixes_barrier_t barrier));
MOCK_CONST_METHOD5(xcb_translate_coordinates, xcb_translate_coordinates_cookie_t(xcb_connection_t* c, xcb_window_t src_window, xcb_window_t dst_window, int16_t src_x, int16_t src_y));
MOCK_CONST_METHOD3(xcb_translate_coordinates_reply, xcb_translate_coordinates_reply_t*(xcb_connection_t* c, xcb_translate_coordinates_cookie_t cookie, xcb_generic_error_t** e));
MOCK_CONST_METHOD10(xcb_xfixes_create_pointer_barrier_checked, xcb_void_cookie_t(
xcb_connection_t* c,
xcb_xfixes_barrier_t barrier,
xcb_window_t window,
uint16_t x1,
uint16_t y1,
uint16_t x2,
uint16_t y2,
uint32_t directions,
uint16_t num_devices,
const uint16_t* devices));
// xcb-xinput
MOCK_CONST_METHOD3(xcb_input_xi_query_version, xcb_input_xi_query_version_cookie_t(xcb_connection_t* c, uint16_t major_version, uint16_t minor_version));
MOCK_CONST_METHOD3(xcb_input_xi_query_version_reply, xcb_input_xi_query_version_reply_t*(xcb_connection_t* c, xcb_input_xi_query_version_cookie_t cookie, xcb_generic_error_t** e));
MOCK_CONST_METHOD4(xcb_input_xi_select_events, xcb_void_cookie_t(xcb_connection_t* c, xcb_window_t window, uint16_t num_mask, const xcb_input_event_mask_t* masks));
MOCK_CONST_METHOD1(xcb_input_raw_button_press_axisvalues_length, int(const xcb_input_raw_button_press_event_t* R));
MOCK_CONST_METHOD1(xcb_input_raw_button_press_axisvalues_raw, xcb_input_fp3232_t*(const xcb_input_raw_button_press_event_t* R));
private:
static inline MockXcbInterface* self = nullptr;
};
@@ -22,6 +22,12 @@ namespace AzFramework
public:
void SetUp() override;
template<typename T>
static xcb_generic_event_t MakeEvent(T event)
{
return *reinterpret_cast<xcb_generic_event_t*>(&event);
}
protected:
testing::NiceMock<MockXcbInterface> m_interface;
xcb_connection_t m_connection{};
@@ -21,12 +21,6 @@
#include "XcbBaseTestFixture.h"
#include "XcbTestApplication.h"
template<typename T>
xcb_generic_event_t MakeEvent(T event)
{
return *reinterpret_cast<xcb_generic_event_t*>(&event);
}
namespace AzFramework
{
// Sets up default behavior for mock keyboard responses to xcb methods
@@ -0,0 +1,545 @@
/*
* 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 <gtest/gtest.h>
#include <gmock/gmock.h>
#include <xcb/xcb.h>
#include <AzFramework/Input/Buses/Requests/InputSystemCursorRequestBus.h>
#include <AzFramework/Input/Devices/Mouse/InputDeviceMouse.h>
#include "XcbBaseTestFixture.h"
#include "XcbTestApplication.h"
#include "Matchers.h"
#include "Actions.h"
namespace AzFramework
{
// Sets up default behavior for mock keyboard responses to xcb methods
class XcbInputDeviceMouseTests
: public XcbBaseTestFixture
{
public:
void SetUp() override
{
using testing::Eq;
using testing::Field;
using testing::Return;
using testing::StrEq;
using testing::_;
XcbBaseTestFixture::SetUp();
ON_CALL(m_interface, xcb_get_setup(&m_connection))
.WillByDefault(Return(&s_xcbSetup));
ON_CALL(m_interface, xcb_setup_roots_iterator(&s_xcbSetup))
.WillByDefault(Return(xcb_screen_iterator_t{&s_xcbScreen}));
ON_CALL(m_interface, xcb_get_extension_data(&m_connection, &xcb_xfixes_id))
.WillByDefault(Return(&s_xfixesExtensionReply));
ON_CALL(m_interface, xcb_xfixes_query_version_reply(&m_connection, _, _))
.WillByDefault(ReturnMalloc<xcb_xfixes_query_version_reply_t>(
/*response_type=*/(uint8_t)XCB_XFIXES_QUERY_VERSION,
/*pad0=*/(uint8_t)0,
/*sequence=*/(uint16_t)1,
/*length=*/0u,
/*major_version=*/5u,
/*minor_version=*/0u
));
ON_CALL(m_interface, xcb_get_extension_data(&m_connection, &xcb_input_id))
.WillByDefault(Return(&s_xfixesExtensionReply));
ON_CALL(m_interface, xcb_input_xi_query_version_reply(&m_connection, _, _))
.WillByDefault(ReturnMalloc<xcb_input_xi_query_version_reply_t>(
/*response_type=*/(uint8_t)XCB_INPUT_XI_QUERY_VERSION,
/*pad0=*/(uint8_t)0,
/*sequence=*/(uint16_t)1,
/*length=*/0u,
/*major_version=*/(uint16_t)2,
/*minor_version=*/(uint16_t)2
));
// Set the default focus window
EXPECT_CALL(m_interface, xcb_intern_atom(&m_connection, 1, 18, StrEq("_NET_ACTIVE_WINDOW")))
.WillRepeatedly(Return(xcb_intern_atom_cookie_t{/*.sequence=*/ 1}));
ON_CALL(m_interface, xcb_intern_atom_reply(&m_connection, Field(&xcb_intern_atom_cookie_t::sequence, Eq(1)), _))
.WillByDefault(ReturnMalloc<xcb_intern_atom_reply_t>(
/*response_type=*/(uint8_t)XCB_INTERN_ATOM,
/*pad0=*/(uint8_t)0,
/*sequence=*/(uint16_t)1,
/*length=*/0u,
/*xcb_atom_t=*/s_netActiveWindowAtom
));
ON_CALL(m_interface, xcb_get_property(&m_connection, 0, s_rootWindow, s_netActiveWindowAtom, XCB_ATOM_WINDOW, 0, 1))
.WillByDefault(Return(xcb_get_property_cookie_t{/*.sequence=*/ s_getActiveWindowPropertySequence}));
ON_CALL(m_interface, xcb_get_property_reply(&m_connection, Field(&xcb_get_property_cookie_t::sequence, Eq(s_getActiveWindowPropertySequence)), _))
.WillByDefault(ReturnMalloc<xcb_get_property_reply_t>(
/*response_type=*/(uint8_t)XCB_GET_PROPERTY,
/*format=*/(uint8_t)0,
/*sequence=*/(uint16_t)s_getActiveWindowPropertySequence,
/*length=*/0u,
/*type=*/XCB_ATOM_WINDOW,
/*bytes_after=*/0u,
/*value_len=*/1u
));
ON_CALL(m_interface, xcb_get_property_value(Field(&xcb_get_property_reply_t::sequence, Eq(s_getActiveWindowPropertySequence))))
.WillByDefault(Return(const_cast<xcb_window_t*>(&s_nullWindow)));
ON_CALL(m_interface, xcb_get_geometry(&m_connection, _))
.WillByDefault(Return(xcb_get_geometry_cookie_t{/*.sequence=*/1}));
ON_CALL(m_interface, xcb_get_geometry_reply(&m_connection, Field(&xcb_get_geometry_cookie_t::sequence, Eq(1)), _))
.WillByDefault(ReturnMalloc<xcb_get_geometry_reply_t>(s_defaultWindowGeometry));
}
void PumpApplication()
{
m_application.PumpSystemEventLoopUntilEmpty();
m_application.TickSystem();
m_application.Tick();
}
protected:
static constexpr inline uint8_t s_xinputMajorOpcode = 131;
static constexpr inline xcb_window_t s_rootWindow = 1;
static constexpr inline xcb_window_t s_nullWindow = XCB_WINDOW_NONE;
static constexpr inline xcb_input_device_id_t s_virtualCorePointerId = 2;
static constexpr inline xcb_input_device_id_t s_physicalPointerDeviceId = 3;
static constexpr inline uint16_t s_screenWidthInPixels = 3840;
static constexpr inline uint16_t s_screenHeightInPixels = 2160;
static constexpr inline uint16_t s_getActiveWindowPropertySequence = 2160;
static constexpr inline xcb_atom_t s_netActiveWindowAtom = 1;
static constexpr inline xcb_setup_t s_xcbSetup{
/*.status=*/1,
/*.pad0=*/0,
/*.protocol_major_version=*/11,
/*.protocol_minor_version=*/0,
};
static inline xcb_screen_t s_xcbScreen{
/*.root=*/s_rootWindow,
/*.default_colormap=*/32,
/*.white_pixel=*/16777215,
/*.black_pixel=*/0,
/*.current_input_masks=*/0,
/*.width_in_pixels=*/s_screenWidthInPixels,
/*.height_in_pixels=*/s_screenHeightInPixels,
/*.width_in_millimeters=*/602,
/*.height_in_millimeters=*/341,
};
static constexpr inline xcb_query_extension_reply_t s_xfixesExtensionReply{
/*.response_type=*/XCB_QUERY_EXTENSION,
/*.pad0=*/0,
/*.sequence=*/1,
/*.length=*/0,
/*.present=*/1,
};
static constexpr inline xcb_query_extension_reply_t s_xinputExtensionReply{
/*.response_type=*/XCB_QUERY_EXTENSION,
/*.pad0=*/0,
/*.sequence=*/1,
/*.length=*/0,
/*.present=*/1,
/*.major_opcode=*/s_xinputMajorOpcode,
};
static constexpr inline xcb_get_geometry_reply_t s_defaultWindowGeometry{
/*.response_type=*/XCB_GET_GEOMETRY,
/*.depth=*/0,
/*.sequence=*/1,
/*.length=*/0,
/*.root=*/s_rootWindow,
/*.x=*/100,
/*.y=*/100,
/*.width=*/100,
/*.height=*/100,
/*.border_width=*/3,
/*.pad0[2]=*/{},
};
XcbTestApplication m_application{
/*enabledGamepadsCount=*/0,
/*keyboardEnabled=*/false,
/*motionEnabled=*/false,
/*mouseEnabled=*/true,
/*touchEnabled=*/false,
/*virtualKeyboardEnabled=*/false
};
};
struct MouseButtonTestData
{
xcb_button_index_t m_button;
};
class XcbInputDeviceMouseButtonTests
: public XcbInputDeviceMouseTests
, public testing::WithParamInterface<MouseButtonTestData>
{
public:
static InputChannelId GetInputChannelIdForButton(const xcb_button_index_t button)
{
switch (button)
{
case XCB_BUTTON_INDEX_1:
return InputDeviceMouse::Button::Left;
case XCB_BUTTON_INDEX_2:
return InputDeviceMouse::Button::Right;
case XCB_BUTTON_INDEX_3:
return InputDeviceMouse::Button::Middle;
}
return InputChannelId{};
}
AZStd::array<InputChannelId, 4> GetIdleChannelIdsForButton(const xcb_button_index_t button)
{
switch (button)
{
case XCB_BUTTON_INDEX_1:
return { InputDeviceMouse::Button::Right, InputDeviceMouse::Button::Middle, InputDeviceMouse::Button::Other1, InputDeviceMouse::Button::Other2 };
case XCB_BUTTON_INDEX_2:
return { InputDeviceMouse::Button::Left, InputDeviceMouse::Button::Middle, InputDeviceMouse::Button::Other1, InputDeviceMouse::Button::Other2 };
case XCB_BUTTON_INDEX_3:
return { InputDeviceMouse::Button::Left, InputDeviceMouse::Button::Right, InputDeviceMouse::Button::Other1, InputDeviceMouse::Button::Other2 };
case XCB_BUTTON_INDEX_4:
return { InputDeviceMouse::Button::Left, InputDeviceMouse::Button::Right, InputDeviceMouse::Button::Middle, InputDeviceMouse::Button::Other2 };
case XCB_BUTTON_INDEX_5:
return { InputDeviceMouse::Button::Left, InputDeviceMouse::Button::Right, InputDeviceMouse::Button::Middle, InputDeviceMouse::Button::Other1 };
}
return AZStd::array<InputChannelId, 4>();
}
};
TEST_P(XcbInputDeviceMouseButtonTests, ButtonInputChannelsUpdateStateFromXcbEvents)
{
using testing::Each;
using testing::Eq;
using testing::NotNull;
using testing::Property;
using testing::Return;
// Set the expectations for the events that will be generated
// nullptr entries represent when the event queue is empty, and will cause
// PumpSystemEventLoopUntilEmpty to return
//
// Event pointers are freed by the calling code, so these actions
// malloc new copies
//
// The xcb mouse does not react to the `XCB_BUTTON_PRESS` /
// `XCB_BUTTON_RELEASE` events, but it will still receive those events
// from the X server.
EXPECT_CALL(m_interface, xcb_poll_for_event(&m_connection))
.WillOnce(ReturnMalloc<xcb_generic_event_t>(MakeEvent(xcb_input_raw_button_press_event_t{
/*response_type=*/XCB_GE_GENERIC,
/*extension=*/s_xinputMajorOpcode,
/*sequence=*/4,
/*length=*/2,
/*event_type=*/XCB_INPUT_RAW_BUTTON_PRESS,
/*deviceid=*/s_virtualCorePointerId,
/*time=*/3984920,
/*detail=*/GetParam().m_button,
/*sourceid=*/s_physicalPointerDeviceId,
/*valuators_len=*/2,
/*flags=*/0,
/*pad0[4]=*/{},
/*full_sequence=*/4
})))
.WillOnce(Return(nullptr))
.WillOnce(ReturnMalloc<xcb_generic_event_t>(MakeEvent(xcb_button_press_event_t{
/*response_type=*/XCB_BUTTON_PRESS,
/*detail=*/static_cast<xcb_button_t>(GetParam().m_button),
/*sequence=*/4,
/*time=*/3984920,
/*root=*/s_rootWindow,
/*event=*/119537664,
/*child=*/0,
/*root_x=*/55,
/*root_y=*/1099,
/*event_x=*/55,
/*event_y=*/55,
/*state=*/0,
/*same_screen=*/1
})))
.WillOnce(Return(nullptr))
.WillOnce(ReturnMalloc<xcb_generic_event_t>(MakeEvent(xcb_input_raw_button_release_event_t{
/*response_type=*/XCB_GE_GENERIC,
/*extension=*/s_xinputMajorOpcode,
/*sequence=*/4,
/*length=*/2,
/*event_type=*/XCB_INPUT_RAW_BUTTON_RELEASE,
/*deviceid=*/s_virtualCorePointerId,
/*time=*/3984964,
/*detail=*/GetParam().m_button,
/*sourceid=*/s_physicalPointerDeviceId,
/*valuators_len=*/2,
/*flags=*/0,
/*pad0[4]=*/{},
/*full_sequence=*/4
})))
.WillOnce(Return(nullptr))
.WillOnce(ReturnMalloc<xcb_generic_event_t>(MakeEvent(xcb_button_release_event_t{
/*response_type=*/XCB_BUTTON_RELEASE,
/*detail=*/static_cast<xcb_button_t>(GetParam().m_button),
/*sequence=*/4,
/*time=*/3984964,
/*root=*/s_rootWindow,
/*event=*/119537664,
/*child=*/0,
/*root_x=*/55,
/*root_y=*/1099,
/*event_x=*/55,
/*event_y=*/55,
/*state=*/XCB_KEY_BUT_MASK_BUTTON_1,
/*same_screen=*/1
})))
.WillOnce(Return(nullptr))
;
m_application.Start();
InputSystemCursorRequestBus::Event(
InputDeviceMouse::Id,
&InputSystemCursorRequests::SetSystemCursorState,
SystemCursorState::ConstrainedAndHidden);
const InputChannel* activeButtonChannel = InputChannelRequests::FindInputChannel(GetInputChannelIdForButton(GetParam().m_button));
const auto inactiveButtonChannels = [this]()
{
const auto inactiveButtonChannelIds = GetIdleChannelIdsForButton(GetParam().m_button);
AZStd::array<const InputChannel*, 4> channels{};
AZStd::transform(begin(inactiveButtonChannelIds), end(inactiveButtonChannelIds), begin(channels), [](const InputChannelId& id)
{
return InputChannelRequests::FindInputChannel(id);
});
return channels;
}();
ASSERT_TRUE(activeButtonChannel);
ASSERT_THAT(inactiveButtonChannels, Each(NotNull()));
EXPECT_THAT(activeButtonChannel->GetState(), Eq(InputChannel::State::Idle));
EXPECT_THAT(inactiveButtonChannels, Each(Property(&InputChannel::GetState, Eq(InputChannel::State::Idle))));
PumpApplication();
EXPECT_THAT(activeButtonChannel->GetState(), Eq(InputChannel::State::Began));
EXPECT_THAT(inactiveButtonChannels, Each(Property(&InputChannel::GetState, Eq(InputChannel::State::Idle))));
PumpApplication();
EXPECT_THAT(activeButtonChannel->GetState(), Eq(InputChannel::State::Updated));
EXPECT_THAT(inactiveButtonChannels, Each(Property(&InputChannel::GetState, Eq(InputChannel::State::Idle))));
PumpApplication();
EXPECT_THAT(activeButtonChannel->GetState(), Eq(InputChannel::State::Ended));
EXPECT_THAT(inactiveButtonChannels, Each(Property(&InputChannel::GetState, Eq(InputChannel::State::Idle))));
PumpApplication();
EXPECT_THAT(activeButtonChannel->GetState(), Eq(InputChannel::State::Idle));
EXPECT_THAT(inactiveButtonChannels, Each(Property(&InputChannel::GetState, Eq(InputChannel::State::Idle))));
}
INSTANTIATE_TEST_CASE_P(
AllButtons,
XcbInputDeviceMouseButtonTests,
testing::Values(
MouseButtonTestData{ XCB_BUTTON_INDEX_1 },
MouseButtonTestData{ XCB_BUTTON_INDEX_2 },
MouseButtonTestData{ XCB_BUTTON_INDEX_3 }
// XCB_BUTTON_INDEX_4 and XCB_BUTTON_INDEX_5 map to positive and
// negative scroll wheel events, which are handled as motion events
)
);
TEST_F(XcbInputDeviceMouseTests, MovementInputChannelsUpdateStateFromXcbEvents)
{
using testing::Each;
using testing::Eq;
using testing::FloatEq;
using testing::NotNull;
using testing::Property;
using testing::Return;
// Set the expectations for the events that will be generated
// nullptr entries represent when the event queue is empty, and will cause
// PumpSystemEventLoopUntilEmpty to return
//
// Event pointers are freed by the calling code, so these actions
// malloc new copies
//
// The xcb mouse does not react to the `XCB_MOTION_NOTIFY` event, but
// it will still receive it from the X server.
EXPECT_CALL(m_interface, xcb_poll_for_event(&m_connection))
.WillOnce(ReturnMalloc<xcb_generic_event_t>(MakeEvent(xcb_input_raw_motion_event_t{
/*response_type=*/XCB_GE_GENERIC,
/*extension=*/s_xinputMajorOpcode,
/*sequence=*/5,
/*length=*/10,
/*event_type=*/XCB_INPUT_RAW_MOTION,
/*deviceid=*/s_virtualCorePointerId,
/*time=*/0, // use the time value to identify each event
/*detail=*/XCB_MOTION_NORMAL,
/*sourceid=*/s_physicalPointerDeviceId,
/*valuators_len=*/2, // number of axes that have values for this event
/*flags=*/0,
/*pad0[4]=*/{},
/*full_sequence=*/5,
})))
.WillOnce(Return(nullptr))
.WillOnce(ReturnMalloc<xcb_generic_event_t>(MakeEvent(xcb_motion_notify_event_t{
/*response_type=*/XCB_MOTION_NOTIFY,
/*detail=*/XCB_MOTION_NORMAL,
/*sequence=*/5,
/*time=*/1, // use the time value to identify each event
/*root=*/s_rootWindow,
/*event=*/127926272,
/*child=*/0,
/*root_x=*/95,
/*root_y=*/1079,
/*event_x=*/95,
/*event_y=*/20,
/*state=*/0,
/*same_screen=*/1,
})))
.WillOnce(Return(nullptr))
;
AZStd::array axisValues
{
xcb_input_fp3232_t{ /*.integral=*/ 1, /*.fraction=*/0 }, // x motion
xcb_input_fp3232_t{ /*.integral=*/ 2, /*.fraction=*/0 } // y motion
};
EXPECT_CALL(m_interface, xcb_input_raw_button_press_axisvalues_length(testing::Field(&xcb_input_raw_button_press_event_t::time, 0)))
.WillRepeatedly(testing::Return(2)); // x and y axis
EXPECT_CALL(m_interface, xcb_input_raw_button_press_axisvalues_raw(testing::Field(&xcb_input_raw_button_press_event_t::time, 0)))
.WillRepeatedly(testing::Return(axisValues.data())); // x and y axis
m_application.Start();
InputSystemCursorRequestBus::Event(
InputDeviceMouse::Id,
&InputSystemCursorRequests::SetSystemCursorState,
SystemCursorState::ConstrainedAndHidden);
const InputChannel* xMotionChannel = InputChannelRequests::FindInputChannel(InputDeviceMouse::Movement::X);
const InputChannel* yMotionChannel = InputChannelRequests::FindInputChannel(InputDeviceMouse::Movement::Y);
ASSERT_TRUE(xMotionChannel);
ASSERT_TRUE(yMotionChannel);
EXPECT_THAT(xMotionChannel->GetState(), Eq(InputChannel::State::Idle));
EXPECT_THAT(yMotionChannel->GetState(), Eq(InputChannel::State::Idle));
EXPECT_THAT(xMotionChannel->GetValue(), FloatEq(0.0f));
EXPECT_THAT(yMotionChannel->GetValue(), FloatEq(0.0f));
PumpApplication();
EXPECT_THAT(xMotionChannel->GetState(), Eq(InputChannel::State::Began));
EXPECT_THAT(yMotionChannel->GetState(), Eq(InputChannel::State::Began));
EXPECT_THAT(xMotionChannel->GetValue(), FloatEq(1.0f));
EXPECT_THAT(yMotionChannel->GetValue(), FloatEq(2.0f));
PumpApplication();
EXPECT_THAT(xMotionChannel->GetState(), Eq(InputChannel::State::Ended));
EXPECT_THAT(yMotionChannel->GetState(), Eq(InputChannel::State::Ended));
EXPECT_THAT(xMotionChannel->GetValue(), FloatEq(0.0f));
EXPECT_THAT(yMotionChannel->GetValue(), FloatEq(0.0f));
}
struct GetCursorPositionParam
{
int16_t m_x;
int16_t m_y;
};
class XcbGetSystemCursorPositionTests
: public XcbInputDeviceMouseTests
, public testing::WithParamInterface<GetCursorPositionParam>
{
};
TEST_P(XcbGetSystemCursorPositionTests, GetSystemCursorPositionNormalizedReturnsCorrectValue)
{
using testing::Eq;
using testing::Field;
using testing::Return;
using testing::_;
xcb_window_t focusWindow = 42;
const xcb_query_pointer_reply_t queryPointerReply{
/*.response_type=*/XCB_QUERY_POINTER,
/*.same_screen=*/1,
/*.sequence=*/0,
/*.length=*/1,
/*.root=*/s_rootWindow,
/*.child=*/focusWindow,
/*.root_x=*/static_cast<int16_t>(GetParam().m_x + s_defaultWindowGeometry.x),
/*.root_y=*/static_cast<int16_t>(GetParam().m_y + s_defaultWindowGeometry.y),
/*.win_x=*/GetParam().m_x,
/*.win_y=*/GetParam().m_y,
/*.mask=*/{},
/*.pad0[2]=*/{},
};
// Querying the root window's pointer gives its absolute value
const xcb_query_pointer_reply_t rootWindowQueryPointerReply{
/*.response_type=*/XCB_QUERY_POINTER,
/*.same_screen=*/1,
/*.sequence=*/0,
/*.length=*/1,
/*.root=*/s_rootWindow,
/*.child=*/s_rootWindow,
/*.root_x=*/static_cast<int16_t>(GetParam().m_x + s_defaultWindowGeometry.x),
/*.root_y=*/static_cast<int16_t>(GetParam().m_y + s_defaultWindowGeometry.y),
/*.win_x=*/static_cast<int16_t>(GetParam().m_x + s_defaultWindowGeometry.x),
/*.win_y=*/static_cast<int16_t>(GetParam().m_y + s_defaultWindowGeometry.y),
/*.mask=*/{},
/*.pad0[2]=*/{},
};
EXPECT_CALL(m_interface, xcb_get_property_value(Field(&xcb_get_property_reply_t::sequence, Eq(s_getActiveWindowPropertySequence))))
.WillRepeatedly(Return(&focusWindow));
EXPECT_CALL(m_interface, xcb_query_pointer(&m_connection, focusWindow))
.WillRepeatedly(Return(xcb_query_pointer_cookie_t{/*.sequence=*/1}));
EXPECT_CALL(m_interface, xcb_query_pointer_reply(&m_connection, Field(&xcb_query_pointer_cookie_t::sequence, 1), _))
.WillRepeatedly(ReturnMalloc<xcb_query_pointer_reply_t>(queryPointerReply));
EXPECT_CALL(m_interface, xcb_query_pointer(&m_connection, s_rootWindow))
.WillRepeatedly(Return(xcb_query_pointer_cookie_t{/*.sequence=*/2}));
EXPECT_CALL(m_interface, xcb_query_pointer_reply(&m_connection, Field(&xcb_query_pointer_cookie_t::sequence, 2), _))
.WillRepeatedly(ReturnMalloc<xcb_query_pointer_reply_t>(rootWindowQueryPointerReply));
m_application.Start();
InputSystemCursorRequestBus::Event(
InputDeviceMouse::Id,
&InputSystemCursorRequests::SetSystemCursorState,
SystemCursorState::ConstrainedAndHidden);
AZ::Vector2 systemCursorPositionNormalized = AZ::Vector2::CreateZero();
InputSystemCursorRequestBus::EventResult(
systemCursorPositionNormalized,
InputDeviceMouse::Id,
&InputSystemCursorRequests::GetSystemCursorPositionNormalized);
EXPECT_THAT(systemCursorPositionNormalized, ::testing::AllOf(
testing::Property(&AZ::Vector2::GetX, testing::FloatEq(static_cast<float>(GetParam().m_x) / s_defaultWindowGeometry.width)),
testing::Property(&AZ::Vector2::GetY, testing::FloatEq(static_cast<float>(GetParam().m_y) / s_defaultWindowGeometry.height))
));
}
INSTANTIATE_TEST_CASE_P(
AllPointerPositions,
XcbGetSystemCursorPositionTests,
testing::Values(
// Default mocked window geometry sets width and height to 100, all
// parameter values should be within [0, 100)
GetCursorPositionParam{ 50, 50 },
GetCursorPositionParam{ 25, 25 },
GetCursorPositionParam{ 0, 100 }
)
);
} // namespace AzFramework
@@ -17,5 +17,6 @@ set(FILES
XcbBaseTestFixture.cpp
XcbBaseTestFixture.h
XcbInputDeviceKeyboardTests.cpp
XcbInputDeviceMouseTests.cpp
XcbTestApplication.h
)
@@ -40,9 +40,9 @@ namespace AzToolsFramework
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
QModelIndex parent(const QModelIndex& child) const override;
QModelIndex sibling(int row, int column, const QModelIndex& idx) const override;
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
protected:
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
QVariant headerData(int section, Qt::Orientation orientation, int role /* = Qt::DisplayRole */) const override;
////////////////////////////////////////////////////////////////////
@@ -55,7 +55,7 @@ namespace AzToolsFramework
private slots:
void SourceDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight);
private:
AZ::u64 m_numberOfItemsDisplayed = 0;
AZ::u64 m_numberOfItemsDisplayed = 50;
int m_displayedItemsCounter = 0;
QPointer<AssetBrowserFilterModel> m_filterModel;
QMap<int, QModelIndex> m_indexMap;
@@ -0,0 +1,344 @@
/*
* 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 <AzTest/AzTest.h>
#include <AzToolsFramework/AssetBrowser/AssetBrowserComponent.h>
#include <AzToolsFramework/AssetBrowser/AssetBrowserFilterModel.h>
#include <AzToolsFramework/AssetBrowser/AssetBrowserModel.h>
#include <AzToolsFramework/AssetBrowser/AssetBrowserTableModel.h>
#include <AzToolsFramework/AssetBrowser/Entries/AssetBrowserEntry.h>
#include <AzToolsFramework/AssetBrowser/Entries/FolderAssetBrowserEntry.h>
#include <AzToolsFramework/AssetBrowser/Entries/ProductAssetBrowserEntry.h>
#include <AzToolsFramework/AssetBrowser/Entries/RootAssetBrowserEntry.h>
#include <AzToolsFramework/AssetBrowser/Entries/SourceAssetBrowserEntry.h>
#include <AzToolsFramework/AssetBrowser/Search/SearchWidget.h>
#include <AzToolsFramework/AssetDatabase/AssetDatabaseConnection.h>
#include <AzToolsFramework/Entity/EditorEntityContextComponent.h>
#include <AzToolsFramework/UnitTest/AzToolsFrameworkTestHelpers.h>
#include <QAbstractItemModelTester>
namespace UnitTest
{
// Test fixture for the AssetBrowser model that uses a QAbstractItemModelTester to validate the state of the model
// when QAbstractItemModel signals fire. Tests will exit with a fatal error if an invalid state is detected.
class AssetBrowserTest
: public ToolsApplicationFixture
, public testing::WithParamInterface<const char*>
{
protected:
enum class AssetEntryType
{
Root,
Folder,
Source,
Product
};
enum class FolderType
{
Root,
File
};
void SetUpEditorFixtureImpl() override;
void TearDownEditorFixtureImpl() override;
//! Creates a Mock Scan Folder
void AddScanFolder(AZ::s64 folderID, AZStd::string folderPath, AZStd::string displayName, FolderType folderType = FolderType::File);
//! Creates a Source entry from a mock file
AZ::Uuid CreateSourceEntry(
AZ::s64 fileID, AZ::s64 parentFolderID, AZStd::string filename, AssetEntryType sourceType = AssetEntryType::Source);
//! Creates a product from a given sourceEntry
void CreateProduct(AZ::s64 productID, AZ::Uuid sourceUuid, AZStd::string productName);
void SetupAssetBrowser();
void PrintModel(const QAbstractItemModel* model, AZStd::function<void(const QString&)> printer);
QModelIndex GetModelIndex(const QAbstractItemModel* model, int targetDepth, int row = 0);
AZStd::shared_ptr<AzToolsFramework::AssetBrowser::RootAssetBrowserEntry> GetRootEntry();
AZStd::vector<QString> GetVectorFromFormattedString(const QString& formattedString);
protected:
QString m_assetBrowserHierarchy = QString();
AZStd::unique_ptr<AzToolsFramework::AssetBrowser::SearchWidget> m_searchWidget;
AZStd::unique_ptr<AzToolsFramework::AssetBrowser::AssetBrowserComponent> m_assetBrowserComponent;
AZStd::unique_ptr<AzToolsFramework::AssetBrowser::AssetBrowserFilterModel> m_filterModel;
AZStd::unique_ptr<AzToolsFramework::AssetBrowser::AssetBrowserTableModel> m_tableModel;
AZStd::unique_ptr<QAbstractItemModelTester> m_modelTesterAssetBrowser;
AZStd::unique_ptr<QAbstractItemModelTester> m_modelTesterFilterModel;
AZStd::unique_ptr<QAbstractItemModelTester> m_modelTesterTableModel;
QVector<int> m_folderIds = { 13, 14, 15 };
QVector<int> m_sourceIDs = { 1, 2, 3, 4, 5 };
QVector<int> m_productIDs = { 1, 2, 3, 4, 5 };
};
void AssetBrowserTest::SetUpEditorFixtureImpl()
{
GetApplication()->RegisterComponentDescriptor(AzToolsFramework::EditorEntityContextComponent::CreateDescriptor());
m_assetBrowserComponent = AZStd::make_unique<AzToolsFramework::AssetBrowser::AssetBrowserComponent>();
m_assetBrowserComponent->Activate();
m_filterModel = AZStd::make_unique<AzToolsFramework::AssetBrowser::AssetBrowserFilterModel>();
m_tableModel = AZStd::make_unique<AzToolsFramework::AssetBrowser::AssetBrowserTableModel>();
m_filterModel->setSourceModel(m_assetBrowserComponent->GetAssetBrowserModel());
m_tableModel->setSourceModel(m_filterModel.get());
m_modelTesterAssetBrowser = AZStd::make_unique<QAbstractItemModelTester>(m_assetBrowserComponent->GetAssetBrowserModel());
m_modelTesterFilterModel = AZStd::make_unique<QAbstractItemModelTester>(m_filterModel.get());
m_modelTesterTableModel = AZStd::make_unique<QAbstractItemModelTester>(m_tableModel.get());
m_searchWidget = AZStd::make_unique<AzToolsFramework::AssetBrowser::SearchWidget>();
// Setup String filters
m_searchWidget->Setup(true, true);
m_filterModel->SetFilter(m_searchWidget->GetFilter());
SetupAssetBrowser();
}
void AssetBrowserTest::TearDownEditorFixtureImpl()
{
m_modelTesterAssetBrowser.reset();
m_modelTesterFilterModel.reset();
m_modelTesterTableModel.reset();
m_tableModel.reset();
m_filterModel.reset();
m_assetBrowserComponent->Deactivate();
m_assetBrowserComponent.reset();
m_searchWidget.reset();
}
void AssetBrowserTest::AddScanFolder(
AZ::s64 folderID, AZStd::string folderPath, AZStd::string displayName, FolderType folderType /*= FolderType::File*/)
{
AzToolsFramework::AssetDatabase::ScanFolderDatabaseEntry scanFolder = AzToolsFramework::AssetDatabase::ScanFolderDatabaseEntry();
scanFolder.m_scanFolderID = folderID;
scanFolder.m_scanFolder = folderPath;
scanFolder.m_displayName = displayName;
scanFolder.m_isRoot = folderType == FolderType::Root;
GetRootEntry()->AddScanFolder(scanFolder);
}
AZ::Uuid AssetBrowserTest::CreateSourceEntry(
AZ::s64 fileID, AZ::s64 parentFolderID, AZStd::string filename, AssetEntryType sourceType /*= AssetEntryType::Source*/)
{
AzToolsFramework::AssetDatabase::FileDatabaseEntry entry = AzToolsFramework::AssetDatabase::FileDatabaseEntry();
entry.m_scanFolderPK = parentFolderID;
entry.m_fileID = fileID;
entry.m_fileName = filename;
entry.m_isFolder = sourceType == AssetEntryType::Folder;
GetRootEntry()->AddFile(entry);
if (!entry.m_isFolder)
{
AzToolsFramework::AssetBrowser::SourceWithFileID entrySource = AzToolsFramework::AssetBrowser::SourceWithFileID();
entrySource.first = entry.m_fileID;
entrySource.second = AzToolsFramework::AssetDatabase::SourceDatabaseEntry();
entrySource.second.m_scanFolderPK = parentFolderID;
entrySource.second.m_sourceName = filename;
entrySource.second.m_sourceID = fileID;
entrySource.second.m_sourceGuid = AZ::Uuid::CreateRandom();
GetRootEntry()->AddSource(entrySource);
return entrySource.second.m_sourceGuid;
}
return AZ::Uuid::CreateNull();
}
void AssetBrowserTest::CreateProduct(AZ::s64 productID, AZ::Uuid sourceUuid, AZStd::string productName)
{
AzToolsFramework::AssetBrowser::ProductWithUuid product = AzToolsFramework::AssetBrowser::ProductWithUuid();
product.first = sourceUuid;
product.second = AzToolsFramework::AssetDatabase::ProductDatabaseEntry();
product.second.m_productID = productID;
product.second.m_subID = aznumeric_cast<AZ::u32>(productID);
product.second.m_productName = productName;
GetRootEntry()->AddProduct(product);
}
void AssetBrowserTest::SetupAssetBrowser()
{
// RootEntries : 1 | Folders : 4 | SourceEntries : 5 | ProductEntries : 9
m_assetBrowserHierarchy = R"(
D:
\
dev
o3de
GameProject
Assets
Source_1
Product_1_1
Product_1_0
Source_0
Product_0_3
Product_0_2
Product_0_1
Product_0_0
Scripts
Source_3
Source_2
Product_2_2
Product_2_1
Product_2_0
Misc
Source_4
Product_4_2
Product_4_1
Product_4_0 )";
namespace AzAssetBrowser = AzToolsFramework::AssetBrowser;
AddScanFolder(m_folderIds.at(2), "D:/dev/o3de/GameProject/Misc", "Misc");
AZ::Uuid sourceUuid_4 = CreateSourceEntry(m_sourceIDs.at(4), m_folderIds.at(2), "Source_4");
CreateProduct(m_productIDs.at(0), sourceUuid_4, "Product_4_0");
CreateProduct(m_productIDs.at(1), sourceUuid_4, "Product_4_1");
CreateProduct(m_productIDs.at(2), sourceUuid_4, "Product_4_2");
AddScanFolder(m_folderIds.at(1), "D:/dev/o3de/GameProject/Scripts", "Scripts");
AZ::Uuid sourceUuid_2 = CreateSourceEntry(m_sourceIDs.at(2), m_folderIds.at(1), "Source_2");
CreateProduct(m_productIDs.at(0), sourceUuid_2, "Product_2_0");
CreateProduct(m_productIDs.at(1), sourceUuid_2, "Product_2_1");
CreateProduct(m_productIDs.at(2), sourceUuid_2, "Product_2_2");
CreateSourceEntry(m_sourceIDs.at(3), m_folderIds.at(1), "Source_3");
AddScanFolder(m_folderIds.at(0), "D:/dev/o3de/GameProject/Assets", "Assets");
AZ::Uuid sourceUuid_0 = CreateSourceEntry(m_sourceIDs.at(0), m_folderIds.at(0), "Source_0");
CreateProduct(m_productIDs.at(0), sourceUuid_0, "Product_0_0");
CreateProduct(m_productIDs.at(1), sourceUuid_0, "Product_0_1");
CreateProduct(m_productIDs.at(2), sourceUuid_0, "Product_0_2");
CreateProduct(m_productIDs.at(3), sourceUuid_0, "Product_0_3");
AZ::Uuid sourceUuid_1 = CreateSourceEntry(m_sourceIDs.at(1), m_folderIds.at(0), "Source_1");
CreateProduct(m_productIDs.at(0), sourceUuid_1, "Product_1_0");
CreateProduct(m_productIDs.at(1), sourceUuid_1, "Product_1_1");
}
void AssetBrowserTest::PrintModel(const QAbstractItemModel* model, AZStd::function<void(const QString&)> printer)
{
AZStd::deque<AZStd::pair<QModelIndex, int>> indices;
indices.push_back({ model->index(0, 0), 0 });
while (!indices.empty())
{
auto [index, depth] = indices.front();
indices.pop_front();
QString indentString;
for (int i = 0; i < depth; ++i)
{
indentString += " ";
}
const QString message = indentString + index.data(Qt::DisplayRole).toString();
printer(message);
for (int i = 0; i < model->rowCount(index); ++i)
{
indices.emplace_front(model->index(i, 0, index), depth + 1);
}
}
}
QModelIndex AssetBrowserTest::GetModelIndex(const QAbstractItemModel* model, int targetDepth, int row)
{
AZStd::deque<AZStd::pair<QModelIndex, int>> indices;
indices.push_back({ model->index(0, 0), 0 });
while (!indices.empty())
{
auto [index, depth] = indices.front();
indices.pop_front();
for (int i = 0; i < model->rowCount(index); ++i)
{
if (depth + 1 == targetDepth && row == i)
{
return model->index(i, 0, index);
}
indices.emplace_front(model->index(i, 0, index), depth + 1);
}
}
return QModelIndex();
}
AZStd::shared_ptr<AzToolsFramework::AssetBrowser::RootAssetBrowserEntry> AssetBrowserTest::GetRootEntry()
{
return m_assetBrowserComponent->GetAssetBrowserModel()->GetRootEntry();
}
AZStd::vector<QString> AssetBrowserTest::GetVectorFromFormattedString(const QString& formattedString)
{
AZStd::vector<QString> hierarchySections;
QStringList splittedList = formattedString.split('\n', Qt::SkipEmptyParts);
for (auto& str : splittedList)
{
str.replace(" ", "");
hierarchySections.push_back(str);
}
return hierarchySections;
}
TEST_F(AssetBrowserTest, CheckCorrectNumberOfEntriesInTableView)
{
m_filterModel->FilterUpdatedSlotImmediate();
const int tableViewRowcount = m_tableModel->rowCount();
// RowCount should be 17 -> 5 SourceEntries + 12 ProductEntries)
EXPECT_EQ(tableViewRowcount, 17);
}
TEST_F(AssetBrowserTest, CheckCorrectNumberOfEntriesInTableViewAfterStringFilter)
{
/*
*-Source_1
* |
* |-product_1_0
* |-product_1_1
*
*
* Matching entries = 3
*/
// Apply string filter
m_searchWidget->SetTextFilter(QString("source_1"));
m_filterModel->FilterUpdatedSlotImmediate();
const int tableViewRowcount = m_tableModel->rowCount();
EXPECT_EQ(tableViewRowcount, 3);
}
TEST_F(AssetBrowserTest, CheckScanFolderAddition)
{
EXPECT_EQ(m_assetBrowserComponent->GetAssetBrowserModel()->rowCount(), 1);
const int newFolderId = 20;
AddScanFolder(newFolderId, "E:/TestFolder/TestFolder2", "TestFolder");
// Since the folder is empty it shouldn't be added to the model.
EXPECT_EQ(m_assetBrowserComponent->GetAssetBrowserModel()->rowCount(), 1);
CreateSourceEntry(123, newFolderId, "DummyFile");
// When we add a file to the folder it should be added to the model
EXPECT_EQ(m_assetBrowserComponent->GetAssetBrowserModel()->rowCount(), 2);
}
} // namespace UnitTest
@@ -123,6 +123,7 @@ set(FILES
UI/EntityIdQLineEditTests.cpp
UI/EntityOutlinerTests.cpp
UI/EntityPropertyEditorTests.cpp
UI/AssetBrowserTests.cpp
UndoStack.cpp
Viewport/ClusterTests.cpp
Viewport/ViewportEditorModeTests.cpp
@@ -76,6 +76,12 @@ foreach(project_name project_path IN ZIP_LISTS LY_PROJECTS_TARGET_NAME LY_PROJEC
Legacy::CrySystem
)
if(PAL_TRAIT_BUILD_SERVER_SUPPORTED)
set(server_runtime_dependencies
Legacy::CrySystem
)
endif()
endif()
################################################################################
+2 -2
View File
@@ -1121,8 +1121,8 @@ inline ISystem* GetISystem()
// Description:
// This function must be called once by each module at the beginning, to setup global pointers.
extern "C" AZ_DLL_EXPORT void ModuleInitISystem(ISystem* pSystem, const char* moduleName);
extern "C" AZ_DLL_EXPORT void ModuleShutdownISystem(ISystem* pSystem);
void ModuleInitISystem(ISystem* pSystem, const char* moduleName);
void ModuleShutdownISystem(ISystem* pSystem);
extern "C" AZ_DLL_EXPORT void InjectEnvironment(void* env);
extern "C" AZ_DLL_EXPORT void DetachEnvironment();
+2 -2
View File
@@ -74,7 +74,7 @@ void InitCRTHandlers() {}
//////////////////////////////////////////////////////////////////////////
// This is an entry to DLL initialization function that must be called for each loaded module
//////////////////////////////////////////////////////////////////////////
extern "C" AZ_DLL_EXPORT void ModuleInitISystem(ISystem* pSystem, [[maybe_unused]] const char* moduleName)
void ModuleInitISystem(ISystem* pSystem, [[maybe_unused]] const char* moduleName)
{
if (gEnv) // Already registered.
{
@@ -96,7 +96,7 @@ extern "C" AZ_DLL_EXPORT void ModuleInitISystem(ISystem* pSystem, [[maybe_unused
} // if pSystem
}
extern "C" AZ_DLL_EXPORT void ModuleShutdownISystem([[maybe_unused]] ISystem* pSystem)
void ModuleShutdownISystem([[maybe_unused]] ISystem* pSystem)
{
// Unregister with AZ environment.
AZ::Environment::Detach();
-23
View File
@@ -173,8 +173,6 @@ void CryEngineSignalHandler(int signal)
//////////////////////////////////////////////////////////////////////////
#if defined(WIN32) || defined(LINUX) || defined(APPLE)
# define DLL_MODULE_INIT_ISYSTEM "ModuleInitISystem"
# define DLL_MODULE_SHUTDOWN_ISYSTEM "ModuleShutdownISystem"
# define DLL_INITFUNC_RENDERER "PackageRenderConstructor"
# define DLL_INITFUNC_SOUND "CreateSoundSystem"
# define DLL_INITFUNC_FONT "CreateCryFontInterface"
@@ -188,8 +186,6 @@ void CryEngineSignalHandler(int signal)
#if defined(AZ_RESTRICTED_SECTION_IMPLEMENTED)
#undef AZ_RESTRICTED_SECTION_IMPLEMENTED
#else
# define DLL_MODULE_INIT_ISYSTEM (LPCSTR)2
# define DLL_MODULE_SHUTDOWN_ISYSTEM (LPCSTR)3
# define DLL_INITFUNC_RENDERER (LPCSTR)1
# define DLL_INITFUNC_RENDERER (LPCSTR)1
# define DLL_INITFUNC_SOUND (LPCSTR)1
@@ -445,18 +441,6 @@ AZStd::unique_ptr<AZ::DynamicModuleHandle> CSystem::LoadDLL(const char* dllName)
return handle;
}
//////////////////////////////////////////////////////////////////////////
// After loading DLL initialize it by calling ModuleInitISystem
//////////////////////////////////////////////////////////////////////////
AZStd::string moduleName = PathUtil::GetFileName(dllName);
typedef void*(*PtrFunc_ModuleInitISystem)(ISystem* pSystem, const char* moduleName);
PtrFunc_ModuleInitISystem pfnModuleInitISystem = handle->GetFunction<PtrFunc_ModuleInitISystem>(DLL_MODULE_INIT_ISYSTEM);
if (pfnModuleInitISystem)
{
pfnModuleInitISystem(this, moduleName.c_str());
}
return handle;
}
@@ -497,13 +481,6 @@ void CSystem::ShutdownModuleLibraries()
#if !defined(AZ_MONOLITHIC_BUILD)
for (auto iterator = m_moduleDLLHandles.begin(); iterator != m_moduleDLLHandles.end(); ++iterator)
{
typedef void*( * PtrFunc_ModuleShutdownISystem )(ISystem* pSystem);
PtrFunc_ModuleShutdownISystem pfnModuleShutdownISystem = iterator->second->GetFunction<PtrFunc_ModuleShutdownISystem>(DLL_MODULE_SHUTDOWN_ISYSTEM);
if (pfnModuleShutdownISystem)
{
pfnModuleShutdownISystem(this);
}
if (iterator->second->IsLoaded())
{
iterator->second->Unload();
-5
View File
@@ -333,11 +333,6 @@ void CXConsole::Init(ISystem* pSystem)
m_nLoadingBackTexID = -1;
if (gEnv->IsDedicated())
{
m_bConsoleActive = true;
}
REGISTER_COMMAND("ConsoleShow", &ConsoleShow, VF_NULL, "Opens the console");
REGISTER_COMMAND("ConsoleHide", &ConsoleHide, VF_NULL, "Closes the console");
@@ -25,7 +25,7 @@ namespace AssetProcessor
: public ::testing::Test
{
protected:
UnitTestUtils::AssertAbsorber* m_errorAbsorber;
AZStd::unique_ptr<UnitTestUtils::AssertAbsorber> m_errorAbsorber{};
FileStatePassthrough m_fileStateCache;
void SetUp() override
@@ -40,7 +40,7 @@ namespace AssetProcessor
m_ownsSysAllocator = true;
AZ::AllocatorInstance<AZ::SystemAllocator>::Create();
}
m_errorAbsorber = new UnitTestUtils::AssertAbsorber();
m_errorAbsorber = AZStd::make_unique<UnitTestUtils::AssertAbsorber>();
m_application = AZStd::make_unique<AzFramework::Application>();
@@ -62,8 +62,8 @@ namespace AssetProcessor
AssetUtilities::ResetAssetRoot();
m_application.reset();
delete m_errorAbsorber;
m_errorAbsorber = nullptr;
m_errorAbsorber.reset();
if (m_ownsSysAllocator)
{
AZ::AllocatorInstance<AZ::SystemAllocator>::Destroy();
@@ -4139,11 +4139,19 @@ struct LockedFileTest
switch (message.GetMessageType())
{
case SourceFileNotificationMessage::MessageType:
if (const auto sourceFileMessage = azrtti_cast<const SourceFileNotificationMessage*>(&message);
sourceFileMessage != nullptr && sourceFileMessage->m_type == SourceFileNotificationMessage::NotificationType::FileRemoved
&& m_callback)
if (const auto sourceFileMessage = azrtti_cast<const SourceFileNotificationMessage*>(&message); sourceFileMessage != nullptr &&
sourceFileMessage->m_type == SourceFileNotificationMessage::NotificationType::FileRemoved)
{
m_callback();
// The File Remove message will occur before an attempt to delete the file
// Wait for more than 1 File Remove message.
// This indicates the AP has attempted to delete the file once, failed to do so and is now retrying
++m_deleteCounter;
if(m_deleteCounter > 1 && m_callback)
{
m_callback();
m_callback = {}; // Unset it to be safe, we only intend to run the callback once
}
}
break;
default:
@@ -4167,6 +4175,7 @@ struct LockedFileTest
ModtimeScanningTest::TearDown();
}
AZStd::atomic_int m_deleteCounter{ 0 };
AZStd::function<void()> m_callback;
};
@@ -4206,6 +4215,10 @@ TEST_F(LockedFileTest, DeleteFile_LockedProduct_DeleteFails)
TEST_F(LockedFileTest, DeleteFile_LockedProduct_DeletesWhenReleased)
{
// This test is intended to verify the AP will successfully retry deleting a source asset
// when one of its product assets is locked temporarily
// We'll lock the file by holding it open
auto theFile = m_data->m_absolutePath[1].toUtf8();
const char* theFileString = theFile.constData();
auto [sourcePath, productPath] = *m_data->m_productPaths.find(theFileString);
@@ -4218,19 +4231,22 @@ TEST_F(LockedFileTest, DeleteFile_LockedProduct_DeletesWhenReleased)
ASSERT_GT(m_data->m_productPaths.size(), 0);
QFile product(productPath);
// Open the file and keep it open to lock it
// We'll start a thread later to unlock the file
// This will allow us to test how AP handles trying to delete a locked file
ASSERT_TRUE(product.open(QIODevice::ReadOnly));
// Check if we can delete the file now, if we can't, proceed with the test
// If we can, it means the OS running this test doesn't lock open files so there's nothing to test
if (!AZ::IO::SystemFile::Delete(productPath.toUtf8().constData()))
{
AZStd::thread workerThread;
m_deleteCounter = 0;
m_callback = [&product, &workerThread]() {
workerThread = AZStd::thread([&product]() {
AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(60));
product.close();
});
// Set up a callback which will fire after at least 1 retry
// Unlock the file at that point so AP can successfully delete it
m_callback = [&product]()
{
product.close();
};
QMetaObject::invokeMethod(
@@ -4240,8 +4256,9 @@ TEST_F(LockedFileTest, DeleteFile_LockedProduct_DeletesWhenReleased)
EXPECT_FALSE(QFile::exists(productPath));
EXPECT_EQ(m_data->m_deletedSources.size(), 1);
workerThread.join();
EXPECT_GT(m_deleteCounter, 1); // Make sure the AP tried more than once to delete the file
m_errorAbsorber->ExpectAsserts(0);
}
else
{
@@ -49,6 +49,12 @@ int main(int argc, char* argv[])
AZStd::unique_ptr<AzFramework::ProcessWatcher> shellProcess(AzFramework::ProcessWatcher::LaunchProcess(shellProcessLaunch, AzFramework::ProcessCommunicationType::COMMUNICATOR_TYPE_NONE));
shellProcess->WaitForProcessToExit(120);
shellProcess.reset();
parameters = AZStd::string::format("-c \"%s/scripts/o3de.sh register --this-engine\"", enginePath.c_str());
shellProcessLaunch.m_commandlineParameters = parameters;
shellProcess.reset(AzFramework::ProcessWatcher::LaunchProcess(shellProcessLaunch, AzFramework::ProcessCommunicationType::COMMUNICATOR_TYPE_NONE));
shellProcess->WaitForProcessToExit(120);
shellProcess.reset();
AZ::IO::FixedMaxPath projectManagerPath = installedBinariesFolder/"o3de.app"/"Contents"/"MacOS"/"o3de";
AzFramework::ProcessLauncher::ProcessLaunchInfo processLaunchInfo;
@@ -10,6 +10,8 @@
#include <QProcessEnvironment>
#include <QDir>
#include <AzCore/Utils/Utils.h>
namespace O3DE::ProjectManager
{
namespace ProjectUtils
@@ -94,5 +96,10 @@ namespace O3DE::ProjectManager
QProcessEnvironment::systemEnvironment(),
QObject::tr("Running get_python script..."));
}
AZ::IO::FixedMaxPath GetEditorDirectory()
{
return AZ::Utils::GetExecutableDirectory();
}
} // namespace ProjectUtils
} // namespace O3DE::ProjectManager
@@ -11,6 +11,9 @@
#include <QStandardPaths>
#include <QDir>
#include <AzCore/Utils/Utils.h>
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
namespace O3DE::ProjectManager
{
namespace ProjectUtils
@@ -104,5 +107,35 @@ namespace O3DE::ProjectManager
QProcessEnvironment::systemEnvironment(),
QObject::tr("Running get_python script..."));
}
AZ::IO::FixedMaxPath GetEditorDirectory()
{
AZ::IO::FixedMaxPath executableDirectory = AZ::Utils::GetExecutableDirectory();
AZ::IO::FixedMaxPath editorPath{ executableDirectory };
editorPath /= "../../../Editor.app/Contents/MacOS";
editorPath = editorPath.LexicallyNormal();
if (!AZ::IO::SystemFile::IsDirectory(editorPath.c_str()))
{
if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr)
{
if (AZ::IO::FixedMaxPath installedBinariesPath;
settingsRegistry->Get(installedBinariesPath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_InstalledBinaryFolder))
{
if (AZ::IO::FixedMaxPath engineRootFolder;
settingsRegistry->Get(engineRootFolder.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder))
{
editorPath = engineRootFolder / installedBinariesPath / "Editor.app/Contents/MacOS";
}
}
}
if (!AZ::IO::SystemFile::IsDirectory(editorPath.c_str()))
{
AZ_Error("ProjectManager", false, "Unable to find the Editor app bundle!");
}
}
return editorPath;
}
} // namespace ProjectUtils
} // namespace O3DE::ProjectManager
@@ -14,6 +14,8 @@
#include <QProcess>
#include <QProcessEnvironment>
#include <AzCore/Utils/Utils.h>
namespace O3DE::ProjectManager
{
namespace ProjectUtils
@@ -139,5 +141,10 @@ namespace O3DE::ProjectManager
QProcessEnvironment::systemEnvironment(),
QObject::tr("Running get_python script..."));
}
AZ::IO::FixedMaxPath GetEditorDirectory()
{
return AZ::Utils::GetExecutableDirectory();
}
} // namespace ProjectUtils
} // namespace O3DE::ProjectManager
@@ -81,6 +81,8 @@ namespace O3DE::ProjectManager
DownloadStatus m_downloadStatus = UnknownDownloadStatus;
QStringList m_features;
QString m_requirement;
QString m_licenseText;
QString m_licenseLink;
QString m_directoryLink;
QString m_documentationLink;
QString m_version = "Unknown Version";
@@ -52,6 +52,22 @@ namespace O3DE::ProjectManager
Update(selectedIndices[0]);
}
void SetLabelElidedText(QLabel* label, QString text)
{
QFontMetrics nameFontMetrics(label->font());
int labelWidth = label->width();
// Don't elide if the widgets are sized too small (sometimes occurs when loading gem catalog)
if (labelWidth > 100)
{
label->setText(nameFontMetrics.elidedText(text, Qt::ElideRight, labelWidth));
}
else
{
label->setText(text);
}
}
void GemInspector::Update(const QModelIndex& modelIndex)
{
if (!modelIndex.isValid())
@@ -59,38 +75,52 @@ namespace O3DE::ProjectManager
m_mainWidget->hide();
}
m_nameLabel->setText(m_model->GetDisplayName(modelIndex));
m_creatorLabel->setText(m_model->GetCreator(modelIndex));
SetLabelElidedText(m_nameLabel, m_model->GetDisplayName(modelIndex));
SetLabelElidedText(m_creatorLabel, m_model->GetCreator(modelIndex));
m_summaryLabel->setText(m_model->GetSummary(modelIndex));
m_summaryLabel->adjustSize();
m_licenseLinkLabel->setText(m_model->GetLicenseText(modelIndex));
m_licenseLinkLabel->SetUrl(m_model->GetLicenseLink(modelIndex));
m_directoryLinkLabel->SetUrl(m_model->GetDirectoryLink(modelIndex));
m_documentationLinkLabel->SetUrl(m_model->GetDocLink(modelIndex));
if (m_model->HasRequirement(modelIndex))
{
m_reqirementsIconLabel->show();
m_reqirementsTitleLabel->show();
m_reqirementsTextLabel->show();
m_requirementsIconLabel->show();
m_requirementsTitleLabel->show();
m_requirementsTextLabel->show();
m_requirementsMainSpacer->changeSize(0, 20, QSizePolicy::Fixed, QSizePolicy::Fixed);
m_reqirementsTitleLabel->setText("Requirement");
m_reqirementsTextLabel->setText(m_model->GetRequirement(modelIndex));
m_requirementsTitleLabel->setText(tr("Requirement"));
m_requirementsTextLabel->setText(m_model->GetRequirement(modelIndex));
}
else
{
m_reqirementsIconLabel->hide();
m_reqirementsTitleLabel->hide();
m_reqirementsTextLabel->hide();
m_requirementsIconLabel->hide();
m_requirementsTitleLabel->hide();
m_requirementsTextLabel->hide();
m_requirementsMainSpacer->changeSize(0, 0, QSizePolicy::Fixed, QSizePolicy::Fixed);
}
// Depending gems
m_dependingGems->Update("Depending Gems", "The following Gems will be automatically enabled with this Gem.", m_model->GetDependingGemNames(modelIndex));
QStringList dependingGems = m_model->GetDependingGemNames(modelIndex);
if (!dependingGems.isEmpty())
{
m_dependingGems->Update(tr("Depending Gems"), tr("The following Gems will be automatically enabled with this Gem."), dependingGems);
m_dependingGems->show();
}
else
{
m_dependingGems->hide();
}
// Additional information
m_versionLabel->setText(QString("Gem Version: %1").arg(m_model->GetVersion(modelIndex)));
m_lastUpdatedLabel->setText(QString("Last Updated: %1").arg(m_model->GetLastUpdated(modelIndex)));
m_binarySizeLabel->setText(QString("Binary Size: %1 KB").arg(m_model->GetBinarySizeInKB(modelIndex)));
m_versionLabel->setText(tr("Gem Version: %1").arg(m_model->GetVersion(modelIndex)));
m_lastUpdatedLabel->setText(tr("Last Updated: %1").arg(m_model->GetLastUpdated(modelIndex)));
m_binarySizeLabel->setText(tr("Binary Size: %1 KB").arg(m_model->GetBinarySizeInKB(modelIndex)));
m_mainWidget->adjustSize();
m_mainWidget->show();
@@ -108,35 +138,51 @@ namespace O3DE::ProjectManager
{
// Gem name, creator and summary
m_nameLabel = CreateStyledLabel(m_mainLayout, 18, s_headerColor);
m_creatorLabel = CreateStyledLabel(m_mainLayout, 12, s_headerColor);
m_creatorLabel = CreateStyledLabel(m_mainLayout, s_baseFontSize, s_headerColor);
m_mainLayout->addSpacing(5);
// TODO: QLabel seems to have issues determining the right sizeHint() for our font with the given font size.
// This results into squeezed elements in the layout in case the text is a little longer than a sentence.
m_summaryLabel = CreateStyledLabel(m_mainLayout, 12, s_textColor);
m_summaryLabel = CreateStyledLabel(m_mainLayout, s_baseFontSize, s_headerColor);
m_mainLayout->addWidget(m_summaryLabel);
m_summaryLabel->setWordWrap(true);
m_summaryLabel->setTextInteractionFlags(Qt::TextBrowserInteraction);
m_summaryLabel->setOpenExternalLinks(true);
m_mainLayout->addSpacing(5);
// License
{
QHBoxLayout* licenseHLayout = new QHBoxLayout();
licenseHLayout->setMargin(0);
licenseHLayout->setAlignment(Qt::AlignLeft);
m_mainLayout->addLayout(licenseHLayout);
QLabel* licenseLabel = CreateStyledLabel(licenseHLayout, s_baseFontSize, s_headerColor);
licenseLabel->setText(tr("License: "));
m_licenseLinkLabel = new LinkLabel("", QUrl(), s_baseFontSize);
licenseHLayout->addWidget(m_licenseLinkLabel);
licenseHLayout->addStretch();
m_mainLayout->addSpacing(5);
}
// Directory and documentation links
{
QHBoxLayout* linksHLayout = new QHBoxLayout();
linksHLayout->setMargin(0);
m_mainLayout->addLayout(linksHLayout);
QSpacerItem* spacerLeft = new QSpacerItem(0, 0, QSizePolicy::Expanding);
linksHLayout->addSpacerItem(spacerLeft);
linksHLayout->addStretch();
m_directoryLinkLabel = new LinkLabel("View in Directory");
m_directoryLinkLabel = new LinkLabel(tr("View in Directory"));
linksHLayout->addWidget(m_directoryLinkLabel);
linksHLayout->addWidget(new QLabel("|"));
m_documentationLinkLabel = new LinkLabel("Read Documentation");
m_documentationLinkLabel = new LinkLabel(tr("Read Documentation"));
linksHLayout->addWidget(m_documentationLinkLabel);
QSpacerItem* spacerRight = new QSpacerItem(0, 0, QSizePolicy::Expanding);
linksHLayout->addSpacerItem(spacerRight);
linksHLayout->addStretch();
m_mainLayout->addSpacing(8);
}
@@ -144,34 +190,35 @@ namespace O3DE::ProjectManager
// Separating line
QFrame* hLine = new QFrame();
hLine->setFrameShape(QFrame::HLine);
hLine->setStyleSheet("color: #666666;");
hLine->setObjectName("horizontalSeparatingLine");
m_mainLayout->addWidget(hLine);
m_mainLayout->addSpacing(10);
// Requirements
m_reqirementsTitleLabel = GemInspector::CreateStyledLabel(m_mainLayout, 16, s_headerColor);
m_requirementsTitleLabel = GemInspector::CreateStyledLabel(m_mainLayout, 16, s_headerColor);
QHBoxLayout* requrementsLayout = new QHBoxLayout();
requrementsLayout->setAlignment(Qt::AlignTop);
requrementsLayout->setMargin(0);
requrementsLayout->setSpacing(0);
QHBoxLayout* requirementsLayout = new QHBoxLayout();
requirementsLayout->setAlignment(Qt::AlignTop);
requirementsLayout->setMargin(0);
requirementsLayout->setSpacing(0);
m_reqirementsIconLabel = new QLabel();
m_reqirementsIconLabel->setPixmap(QIcon(":/Warning.svg").pixmap(24, 24));
requrementsLayout->addWidget(m_reqirementsIconLabel);
m_requirementsIconLabel = new QLabel();
m_requirementsIconLabel->setPixmap(QIcon(":/Warning.svg").pixmap(24, 24));
requirementsLayout->addWidget(m_requirementsIconLabel);
m_reqirementsTextLabel = GemInspector::CreateStyledLabel(requrementsLayout, 10, s_textColor);
m_reqirementsTextLabel->setWordWrap(true);
m_reqirementsTextLabel->setTextInteractionFlags(Qt::TextBrowserInteraction);
m_reqirementsTextLabel->setOpenExternalLinks(true);
m_requirementsTextLabel = GemInspector::CreateStyledLabel(requirementsLayout, 10, s_textColor);
m_requirementsTextLabel->setWordWrap(true);
m_requirementsTextLabel->setTextInteractionFlags(Qt::TextBrowserInteraction);
m_requirementsTextLabel->setOpenExternalLinks(true);
QSpacerItem* reqirementsSpacer = new QSpacerItem(0, 0, QSizePolicy::Expanding);
requrementsLayout->addSpacerItem(reqirementsSpacer);
QSpacerItem* requirementsSpacer = new QSpacerItem(0, 0, QSizePolicy::MinimumExpanding);
requirementsLayout->addSpacerItem(requirementsSpacer);
m_mainLayout->addLayout(requrementsLayout);
m_mainLayout->addLayout(requirementsLayout);
m_mainLayout->addSpacing(20);
m_requirementsMainSpacer = new QSpacerItem(0, 20, QSizePolicy::Fixed, QSizePolicy::Fixed);
m_mainLayout->addSpacerItem(m_requirementsMainSpacer);
// Depending gems
m_dependingGems = new GemsSubWidget();
@@ -181,10 +228,10 @@ namespace O3DE::ProjectManager
// Additional information
QLabel* additionalInfoLabel = CreateStyledLabel(m_mainLayout, 14, s_headerColor);
additionalInfoLabel->setText("Additional Information");
additionalInfoLabel->setText(tr("Additional Information"));
m_versionLabel = CreateStyledLabel(m_mainLayout, 12, s_textColor);
m_lastUpdatedLabel = CreateStyledLabel(m_mainLayout, 12, s_textColor);
m_binarySizeLabel = CreateStyledLabel(m_mainLayout, 12, s_textColor);
m_versionLabel = CreateStyledLabel(m_mainLayout, s_baseFontSize, s_textColor);
m_lastUpdatedLabel = CreateStyledLabel(m_mainLayout, s_baseFontSize, s_textColor);
m_binarySizeLabel = CreateStyledLabel(m_mainLayout, s_baseFontSize, s_textColor);
}
} // namespace O3DE::ProjectManager
@@ -16,7 +16,7 @@
#include <QItemSelection>
#include <QScrollArea>
#include <QWidget>
#include <QSpacerItem>
#endif
QT_FORWARD_DECLARE_CLASS(QVBoxLayout)
@@ -36,6 +36,9 @@ namespace O3DE::ProjectManager
void Update(const QModelIndex& modelIndex);
static QLabel* CreateStyledLabel(QLayout* layout, int fontSize, const QString& colorCodeString);
// Fonts
inline constexpr static int s_baseFontSize = 12;
// Colors
inline constexpr static const char* s_headerColor = "#FFFFFF";
inline constexpr static const char* s_textColor = "#DDDDDD";
@@ -57,13 +60,15 @@ namespace O3DE::ProjectManager
QLabel* m_nameLabel = nullptr;
QLabel* m_creatorLabel = nullptr;
QLabel* m_summaryLabel = nullptr;
LinkLabel* m_licenseLinkLabel = nullptr;
LinkLabel* m_directoryLinkLabel = nullptr;
LinkLabel* m_documentationLinkLabel = nullptr;
// Requirements
QLabel* m_reqirementsTitleLabel = nullptr;
QLabel* m_reqirementsIconLabel = nullptr;
QLabel* m_reqirementsTextLabel = nullptr;
QLabel* m_requirementsTitleLabel = nullptr;
QLabel* m_requirementsIconLabel = nullptr;
QLabel* m_requirementsTextLabel = nullptr;
QSpacerItem* m_requirementsMainSpacer = nullptr;
// Depending and conflicting gems
GemsSubWidget* m_dependingGems = nullptr;
@@ -58,6 +58,8 @@ namespace O3DE::ProjectManager
item->setData(gemInfo.m_path, RolePath);
item->setData(gemInfo.m_requirement, RoleRequirement);
item->setData(gemInfo.m_downloadStatus, RoleDownloadStatus);
item->setData(gemInfo.m_licenseText, RoleLicenseText);
item->setData(gemInfo.m_licenseLink, RoleLicenseLink);
appendRow(item);
@@ -248,6 +250,16 @@ namespace O3DE::ProjectManager
return modelIndex.data(RoleRequirement).toString();
}
QString GemModel::GetLicenseText(const QModelIndex& modelIndex)
{
return modelIndex.data(RoleLicenseText).toString();
}
QString GemModel::GetLicenseLink(const QModelIndex& modelIndex)
{
return modelIndex.data(RoleLicenseLink).toString();
}
GemModel* GemModel::GetSourceModel(QAbstractItemModel* model)
{
GemSortFilterProxyModel* proxyModel = qobject_cast<GemSortFilterProxyModel*>(model);
@@ -50,6 +50,8 @@ namespace O3DE::ProjectManager
static QStringList GetFeatures(const QModelIndex& modelIndex);
static QString GetPath(const QModelIndex& modelIndex);
static QString GetRequirement(const QModelIndex& modelIndex);
static QString GetLicenseText(const QModelIndex& modelIndex);
static QString GetLicenseLink(const QModelIndex& modelIndex);
static GemModel* GetSourceModel(QAbstractItemModel* model);
static const GemModel* GetSourceModel(const QAbstractItemModel* model);
@@ -107,7 +109,9 @@ namespace O3DE::ProjectManager
RoleTypes,
RolePath,
RoleRequirement,
RoleDownloadStatus
RoleDownloadStatus,
RoleLicenseText,
RoleLicenseLink
};
QHash<QString, QModelIndex> m_nameToIndexMap;
@@ -99,7 +99,7 @@ namespace O3DE::ProjectManager
m_nameLabel->setObjectName("gemRepoInspectorNameLabel");
m_mainLayout->addWidget(m_nameLabel);
m_repoLinkLabel = new LinkLabel(tr("Repo Url"), QUrl(""), 12, this);
m_repoLinkLabel = new LinkLabel(tr("Repo Url"), QUrl(), 12, this);
m_mainLayout->addWidget(m_repoLinkLabel);
m_mainLayout->addSpacing(5);
@@ -14,6 +14,7 @@
#include <QWidget>
#include <QProcessEnvironment>
#include <AzCore/IO/Path/Path_fwd.h>
#include <AzCore/Outcome/Outcome.h>
namespace O3DE::ProjectManager
@@ -67,7 +68,8 @@ namespace O3DE::ProjectManager
AZ::Outcome<QString, QString> GetProjectBuildPath(const QString& projectPath);
AZ::Outcome<void, QString> OpenCMakeGUI(const QString& projectPath);
AZ::Outcome<QString, QString> RunGetPythonScript(const QString& enginePath);
AZ::IO::FixedMaxPath GetEditorDirectory();
} // namespace ProjectUtils
} // namespace O3DE::ProjectManager
@@ -392,11 +392,11 @@ namespace O3DE::ProjectManager
{
if (!WarnIfInBuildQueue(projectPath))
{
AZ::IO::FixedMaxPath executableDirectory = AZ::Utils::GetExecutableDirectory();
AZ::IO::FixedMaxPath executableDirectory = ProjectUtils::GetEditorDirectory();
AZStd::string executableFilename = "Editor";
AZ::IO::FixedMaxPath editorExecutablePath = executableDirectory / (executableFilename + AZ_TRAIT_OS_EXECUTABLE_EXTENSION);
auto cmdPath = AZ::IO::FixedMaxPathString::format(
"%s -regset=\"/Amazon/AzCore/Bootstrap/project_path=%s\"", editorExecutablePath.c_str(),
"%s --regset=\"/Amazon/AzCore/Bootstrap/project_path=%s\"", editorExecutablePath.c_str(),
projectPath.toStdString().c_str());
AzFramework::ProcessLauncher::ProcessLaunchInfo processLaunchInfo;
@@ -709,6 +709,8 @@ namespace O3DE::ProjectManager
gemInfo.m_requirement = Py_To_String_Optional(data, "requirements", "");
gemInfo.m_creator = Py_To_String_Optional(data, "origin", "");
gemInfo.m_documentationLink = Py_To_String_Optional(data, "documentation_url", "");
gemInfo.m_licenseText = Py_To_String_Optional(data, "license", "Unspecified License");
gemInfo.m_licenseLink = Py_To_String_Optional(data, "license_url", "");
if (gemInfo.m_creator.contains("Open 3D Engine"))
{
@@ -63,7 +63,7 @@ namespace AZ
}
ProcessingOverlayWidget::ProcessingOverlayWidget(UI::OverlayWidget* overlay, Layout layout, Uuid traceTag)
: QWidget()
: QWidget(nullptr, Qt::Tool | Qt::WindowStaysOnTopHint)
, m_traceTag(traceTag)
, ui(new Ui::ProcessingOverlayWidget())
, m_overlay(overlay)
+1
View File
@@ -2,6 +2,7 @@
"gem_name": "AWSClientAuth",
"display_name": "AWS Client Authorization",
"license": "Apache-2.0 Or MIT",
"license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt",
"origin": "Amazon Web Services, Inc.",
"type": "Code",
"summary": "AWS Client Auth provides client authentication and AWS authorization solution.",
+1
View File
@@ -2,6 +2,7 @@
"gem_name": "AWSCore",
"display_name": "AWS Core",
"license": "Apache-2.0 Or MIT",
"license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt",
"origin": "Amazon Web Services, Inc.",
"type": "Code",
"summary": "The AWS Core Gem provides basic shared AWS functionality such as AWS SDK initialization and client configuration, and is automatically added when selecting any AWS feature Gem.",
@@ -44,7 +44,7 @@ FLEET_CONFIGURATIONS = [
'build_path': '<build path>',
# (Conditional) The operating system that the game server binaries are built to run on.
# This parameter is required if the parameter build_path is defined.
# Choose from AMAZON_LINUX, AMAZON_LINUX or WINDOWS_2012.
# Choose from AMAZON_LINUX or WINDOWS_2012.
'operating_system': 'WINDOWS_2012'
},
# (Optional) Information about the use of a TLS/SSL certificate for a fleet.
+1
View File
@@ -2,6 +2,7 @@
"gem_name": "AWSGameLift",
"display_name": "AWS GameLift",
"license": "Apache-2.0 Or MIT",
"license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt",
"origin": "Amazon Web Services, Inc.",
"type": "Code",
"summary": "The AWS GameLift Gem provides a framework to extend O3DE networking layer to work with GameLift resources via GameLift server and client SDK.",
+1
View File
@@ -2,6 +2,7 @@
"gem_name": "AWSMetrics",
"display_name": "AWS Metrics",
"license": "Apache-2.0 Or MIT",
"license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt",
"origin": "Amazon Web Services, Inc.",
"type": "Code",
"summary": "The AWS Metrics Gem provides a solution for AWS metrics submission and analytics.",
+1
View File
@@ -2,6 +2,7 @@
"gem_name": "Achievements",
"display_name": "Achievements",
"license": "Apache-2.0 Or MIT",
"license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt",
"origin": "Open 3D Engine - o3de.org",
"type": "Code",
"summary": "The Achievements Gem provides a target platform agnostic interface for retrieving achievement details and unlocking achievements.",
+1
View File
@@ -2,6 +2,7 @@
"gem_name": "AssetMemoryAnalyzer",
"display_name": "Asset Memory Analyzer",
"license": "Apache-2.0 Or MIT",
"license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt",
"origin": "Open 3D Engine - o3de.org",
"type": "Code",
"summary": "The Asset Memory Analyzer Gem provides tools to profile asset memory usage in Open 3D Engine through ImGUI (Immediate Mode Graphical User Interface).",
+1
View File
@@ -2,6 +2,7 @@
"gem_name": "AssetValidation",
"display_name": "Asset Validation",
"license": "Apache-2.0 Or MIT",
"license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt",
"origin": "Open 3D Engine - o3de.org",
"type": "Code",
"summary": "The Asset Validation Gem provides seed-related commands to ensure assets have valid seeds for asset bundling.",
@@ -100,13 +100,6 @@ namespace ImageProcessingAtom
//compare whether two images are same. return true if they are same.
virtual bool CompareImage(const IImageObjectPtr otherImage) const = 0;
// Writes this image to file used for runtime, overwrites any existing file.
// It may write alpha image as attached image into the same file
// outFilePaths will save filenames finally saved to since the image might be split and saved to multiple files
virtual bool SaveImage(const char* filename, IImageObjectPtr alphaImage, AZStd::vector<AZStd::string>& outFilePaths) const = 0;
virtual bool SaveImage(AZ::IO::SystemFileStream& out) const = 0;
virtual bool SaveMipToFile(AZ::u32 mip, const AZStd::string& filename) const = 0;
//get total image data size in memory of all mipmaps. Not includs header and flags.
virtual AZ::u32 GetTextureMemory() const = 0;
@@ -135,9 +128,6 @@ namespace ImageProcessingAtom
// The algorithm is based on the Frequency Domain Normal Mapping implementation presented by Neubelt and Pettineo at Siggraph 2013.
virtual void GlossFromNormals(bool hasAuthoredGloss) = 0;
//convert gloss map from legacy distribution to new one. New World is still using legacy gloss map.
virtual void ConvertLegacyGloss() = 0;
//clear image with color
virtual void ClearColor(float r, float g, float b, float a) = 0;
};
@@ -45,10 +45,7 @@ namespace ImageProcessingAtom
->Field("MinTextureSize", &PresetSettings::m_minTextureSize)
->Field("IsPowerOf2", &PresetSettings::m_isPowerOf2)
->Field("SizeReduceLevel", &PresetSettings::m_sizeReduceLevel)
->Field("IsColorChart", &PresetSettings::m_isColorChart)
->Field("HighPassMip", &PresetSettings::m_highPassMip)
->Field("GlossFromNormal", &PresetSettings::m_glossFromNormals)
->Field("UseLegacyGloss", &PresetSettings::m_isLegacyGloss)
->Field("MipRenormalize", &PresetSettings::m_isMipRenormalize)
->Field("NumberResidentMips", &PresetSettings::m_numResidentMips)
->Field("Swizzle", &PresetSettings::m_swizzle)
@@ -200,10 +197,7 @@ namespace ImageProcessingAtom
m_maxTextureSize == other.m_maxTextureSize &&
m_isPowerOf2 == other.m_isPowerOf2 &&
m_sizeReduceLevel == other.m_sizeReduceLevel &&
m_isColorChart == other.m_isColorChart &&
m_highPassMip == other.m_highPassMip &&
m_glossFromNormals == other.m_glossFromNormals &&
m_isLegacyGloss == other.m_isLegacyGloss &&
m_swizzle == other.m_swizzle &&
m_isMipRenormalize == other.m_isMipRenormalize &&
m_numResidentMips == other.m_numResidentMips;
@@ -239,10 +233,7 @@ namespace ImageProcessingAtom
m_maxTextureSize = other.m_maxTextureSize;
m_isPowerOf2 = other.m_isPowerOf2;
m_sizeReduceLevel = other.m_sizeReduceLevel;
m_isColorChart = other.m_isColorChart;
m_highPassMip = other.m_highPassMip;
m_glossFromNormals = other.m_glossFromNormals;
m_isLegacyGloss = other.m_isLegacyGloss;
m_swizzle = other.m_swizzle;
m_isMipRenormalize = other.m_isMipRenormalize;
m_numResidentMips = other.m_numResidentMips;
@@ -84,16 +84,7 @@ namespace ImageProcessingAtom
//settings for mipmap generation. it's null if this preset disable mipmap.
AZStd::unique_ptr<MipmapSettings> m_mipmapSetting;
//some specific settings
// "colorchart". This is to indicate if need to extract color chart from the image and output the color chart data.
// This is very specific usage for cryEngine. Check ColorChart.cpp for better explanation.
bool m_isColorChart = false;
//"highpass". Defines which mip level is subtracted when applying the high pass filter
//this is only used for terrain asset. we might remove it later since it can be done with source image directly
AZ::u32 m_highPassMip = 0;
//"glossfromnormals". Bake normal variance into smoothness stored in alpha channel
AZ::u32 m_glossFromNormals = 0;
@@ -109,10 +100,6 @@ namespace ImageProcessingAtom
//that add up to 64K or lower
AZ::u8 m_numResidentMips = 0;
//legacy options might be removed later
//"glosslegacydist". If the gloss map use legacy distribution. NW is still using legacy dist
bool m_isLegacyGloss = false;
//"swizzle". need to be 4 character and each character need to be one of "rgba01"
AZStd::string m_swizzle;
@@ -1,307 +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 <Processing/ImageObjectImpl.h>
#include <Processing/ImageToProcess.h>
namespace ImageProcessingAtom
{
const int COLORCHART_IMAGE_WIDTH = 78;
const int COLORCHART_IMAGE_HEIGHT = 66;
// color chart in cry engine is a special image data, with size 78x66, you may see in game screenshot which is defined by a rectangle
// area with a yellow-black dash line boarder
// Create color chart function is to read that block of image data and convert it to a color table then save it to another image
// with size 256x16.
class C3dLutColorChart
{
public:
C3dLutColorChart() {}
~C3dLutColorChart() {};
//generate default color chart data
void GenerateDefault();
//generate color chart data from input image
bool GenerateFromInput(IImageObjectPtr image);
//ouput the color chart data to an image object
IImageObjectPtr GenerateChartImage();
protected:
//extract color chart data from specified location in an image
void ExtractFromImageAt(IImageObjectPtr pImg, AZ::u32 x, AZ::u32 y);
//find color chart location in an image
static bool FindColorChart(const IImageObjectPtr pImg, AZ::u32& outLocX, AZ::u32& outLocY);
//if there is a color chart at specified location
static bool IsColorChartAt(AZ::u32 x, AZ::u32 y, void* pData, AZ::u32 pitch);
private:
enum EPrimaryShades
{
ePS_Red = 16,
ePS_Green = 16,
ePS_Blue = 16,
ePS_NumColors = ePS_Red * ePS_Green * ePS_Blue
};
struct SColor
{
unsigned char r, g, b, _padding;
};
typedef AZStd::vector<SColor> ColorMapping;
ColorMapping m_mapping;
};
void C3dLutColorChart::GenerateDefault()
{
m_mapping.reserve(ePS_NumColors);
for (int b = 0; b < ePS_Blue; ++b)
{
for (int g = 0; g < ePS_Green; ++g)
{
for (int r = 0; r < ePS_Red; ++r)
{
SColor col;
col.r = static_cast<unsigned char>(255 * r / (ePS_Red));
col.g = static_cast<unsigned char>(255 * g / (ePS_Green));
col.b = static_cast<unsigned char>(255 * b / (ePS_Blue));
int l = 255 - (col.r * 3 + col.g * 6 + col.b) / 10;
col.r = col.g = col.b = (unsigned char)l;
m_mapping.push_back(col);
}
}
}
}
//find color chart location in a image
bool C3dLutColorChart::FindColorChart(const IImageObjectPtr pImg, AZ::u32& outLocX, AZ::u32& outLocY)
{
const AZ::u32 width = pImg->GetWidth(0);
const AZ::u32 height = pImg->GetHeight(0);
//the origin image is too small to have a color chart
if (width < COLORCHART_IMAGE_WIDTH || height < COLORCHART_IMAGE_HEIGHT)
{
return false;
}
AZ::u8* pData;
AZ::u32 pitch;
pImg->GetImagePointer(0, pData, pitch);
//check all the posible start location on whether there might be a color chart
for (AZ::u32 y = 0; y <= height - COLORCHART_IMAGE_HEIGHT; ++y)
{
for (AZ::u32 x = 0; x <= width - COLORCHART_IMAGE_WIDTH; ++x)
{
if (IsColorChartAt(x, y, pData, pitch))
{
outLocX = x;
outLocY = y;
return true;
}
}
}
return false;
}
bool C3dLutColorChart::GenerateFromInput(IImageObjectPtr image)
{
AZ::u32 outLocX, outLocY;
if (FindColorChart(image, outLocX, outLocY))
{
ExtractFromImageAt(image, outLocX, outLocY);
return true;
}
return false;
}
IImageObjectPtr C3dLutColorChart::GenerateChartImage()
{
IImageObjectPtr image(IImageObject::CreateImage(ePS_Red* ePS_Blue, ePS_Green, 1, ePixelFormat_R8G8B8A8));
{
AZ::u8* pData;
AZ::u32 pitch;
image->GetImagePointer(0, pData, pitch);
size_t nSlicePitch = (pitch / ePS_Blue);
AZ::u32 src = 0;
for (int b = 0; b < ePS_Blue; ++b)
{
for (int g = 0; g < ePS_Green; ++g)
{
AZ::u8* p = pData + g * pitch + b * nSlicePitch;
for (int r = 0; r < ePS_Red; ++r)
{
const SColor& c = m_mapping[src];
p[0] = c.r;
p[1] = c.g;
p[2] = c.b;
p[3] = 255;
++src;
p += 4;
}
}
}
}
return image;
}
void C3dLutColorChart::ExtractFromImageAt(IImageObjectPtr image, AZ::u32 x, AZ::u32 y)
{
int ox = x + 1;
int oy = y + 1;
AZ::u8* pData;
AZ::u32 pitch;
image->GetImagePointer(0, pData, pitch);
m_mapping.reserve(ePS_NumColors);
for (int b = 0; b < ePS_Blue; ++b)
{
int px = ox + ePS_Red * (b % 4);
int py = oy + ePS_Green * (b / 4);
for (int g = 0; g < ePS_Green; ++g)
{
for (int r = 0; r < ePS_Red; ++r)
{
AZ::u8* p = pData + pitch * (py + g) + (px + r) * 4;
SColor col;
col.r = p[0];
col.g = p[1];
col.b = p[2];
m_mapping.push_back(col);
}
}
}
}
//check if image data at location x and y could be a color chart
//based on if the boarder is dash lines with two pixel each segement
//the idea and implementation are both coming from CryEngine.
bool C3dLutColorChart::IsColorChartAt(AZ::u32 x, AZ::u32 y, void* pData, AZ::u32 pitch)
{
struct Color
{
private:
int c[3];
public:
Color(AZ::u32 x, AZ::u32 y, void* pPixels, AZ::u32 pitch)
{
const uint8* p = (const uint8*)pPixels + pitch * y + x * 4;
c[0] = p[0];
c[1] = p[1];
c[2] = p[2];
}
bool isSimilar(const Color& a, int maxDiff) const
{
return
abs(a.c[0] - c[0]) <= maxDiff &&
abs(a.c[1] - c[1]) <= maxDiff &&
abs(a.c[2] - c[2]) <= maxDiff;
}
};
const Color colorRef[2] =
{
Color(x, y, pData, pitch),
Color(x + 2, y, pData, pitch)
};
// We require two colors of the border to be at least a bit different
if (colorRef[0].isSimilar(colorRef[1], 15))
{
return false;
}
static const int kMaxDiff = 3;
int refIdx = 0;
//rectangle's top
for (int i = 0; i < COLORCHART_IMAGE_WIDTH; i += 2)
{
if (!colorRef[refIdx].isSimilar(Color(x + i, y, pData, pitch), kMaxDiff) ||
!colorRef[refIdx].isSimilar(Color(x + i + 1, y, pData, pitch), kMaxDiff))
{
return false;
}
refIdx ^= 1;
}
refIdx = 0;
//left
for (int i = 0; i < COLORCHART_IMAGE_HEIGHT; i += 2)
{
if (!colorRef[refIdx].isSimilar(Color(x, y + i, pData, pitch), kMaxDiff) ||
!colorRef[refIdx].isSimilar(Color(x, y + i + 1, pData, pitch), kMaxDiff))
{
return false;
}
refIdx ^= 1;
}
refIdx = 0;
//right
for (int i = 0; i < COLORCHART_IMAGE_HEIGHT; i += 2)
{
if (!colorRef[refIdx].isSimilar(Color(x + COLORCHART_IMAGE_WIDTH - 1, y + i, pData, pitch), kMaxDiff) ||
!colorRef[refIdx].isSimilar(Color(x + COLORCHART_IMAGE_WIDTH - 1, y + i + 1, pData, pitch), kMaxDiff))
{
return false;
}
refIdx ^= 1;
}
refIdx = 0;
//bottom
for (int i = 0; i < COLORCHART_IMAGE_WIDTH; i += 2)
{
if (!colorRef[refIdx].isSimilar(Color(x + i, y + COLORCHART_IMAGE_HEIGHT - 1, pData, pitch), kMaxDiff) ||
!colorRef[refIdx].isSimilar(Color(x + i + 1, y + COLORCHART_IMAGE_HEIGHT - 1, pData, pitch), kMaxDiff))
{
return false;
}
refIdx ^= 1;
}
return true;
}
void ImageToProcess::CreateColorChart()
{
C3dLutColorChart colorChart;
//get color chart data from source image.
if (!colorChart.GenerateFromInput(m_img))
{
//if load from image failed then generate default color data
colorChart.GenerateDefault();
}
//save color chart data to an image and save as current
m_img = colorChart.GenerateChartImage();
}
}
@@ -547,7 +547,7 @@ namespace ImageProcessingAtom
}
//generate box filtered source image mip chain
IImageObjectPtr mippedSourceImage(IImageObject::CreateImage(outWidth, outHeight, maxMipCount, ePixelFormat_R32G32B32A32F));
IImageObjectPtr mippedSourceImage(IImageObject::CreateImage(outWidth, outHeight, maxMipCount, srcPixelFormat));
mippedSourceImage->CopyPropertiesFrom(m_image->Get());
for (int iSide = 0; iSide < 6; ++iSide)
@@ -1,100 +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 <Processing/ImageObjectImpl.h>
#include <Processing/ImageToProcess.h>
#include <Processing/ImageConvert.h>
#include <Processing/PixelFormatInfo.h>
#include <Converters/FIR-Windows.h>
#include <Converters/PixelOperation.h>
namespace ImageProcessingAtom
{
// higher mip level is subtracted by lower mip level when applying the [cheap] high pass filter
void ImageToProcess::CreateHighPass(AZ::u32 dwMipDown)
{
//no need to convert if mip go down 0
if (dwMipDown == 0)
{
return;
}
const EPixelFormat ePixelFormat = m_img->GetPixelFormat();
if (ePixelFormat != ePixelFormat_R32G32B32A32F)
{
AZ_Assert(false, "You need convert the orginal image to ePixelFormat_R32G32B32A32F before call this function");
return;
}
AZ::u32 dwWidth, dwHeight, dwMips;
dwWidth = m_img->GetWidth(0);
dwHeight = m_img->GetHeight(0);
dwMips = m_img->GetMipCount();
if (dwMipDown >= dwMips)
{
AZ_Warning("Image Processing", false, "CreateHighPass can't go down %i MIP levels for high pass as there are not\
enough MIP levels available, going down by %i instead", dwMipDown, dwMips - 1);
dwMipDown = dwMips - 1;
}
IImageObjectPtr newImage(IImageObject::CreateImage(dwWidth, dwHeight, dwMips, ePixelFormat));
newImage->CopyPropertiesFrom(m_img);
IPixelOperationPtr pixelOp = CreatePixelOperation(ePixelFormat);
AZ::u32 pixelBytes = CPixelFormats::GetInstance().GetPixelFormatInfo(ePixelFormat)->bitsPerBlock / 8;
AZ::u32 dstMips = newImage->GetMipCount();
for (AZ::u32 dstMip = 0; dstMip < dwMipDown; ++dstMip)
{
// linear interpolation
FilterImage(MipGenType::triangle, MipGenEvalType::sum, 0.0f, 0.0f, m_img, dwMipDown, newImage, dstMip, NULL, NULL);
//substraction
AZ::u8* srcPixelBuf;
AZ::u32 srcPitch;
m_img->GetImagePointer(dstMip, srcPixelBuf, srcPitch);
AZ::u8* dstPixelBuf;
AZ::u32 dstPitch;
newImage->GetImagePointer(dstMip, dstPixelBuf, dstPitch);
const AZ::u32 pixelCount = newImage->GetPixelCount(dstMip);
for (AZ::u32 i = 0; i < pixelCount; ++i, srcPixelBuf += pixelBytes, dstPixelBuf += pixelBytes)
{
float r1, g1, b1, a1, r2, g2, b2, a2;
pixelOp->GetRGBA(srcPixelBuf, r1, g1, b1, a1);
pixelOp->GetRGBA(dstPixelBuf, r2, g2, b2, a2);
r2 = AZ::GetClamp<float>(r1 - r2 + 0.5f, 0.0f, 1.0f);
g2 = AZ::GetClamp<float>(g1 - g2 + 0.5f, 0.0f, 1.0f);
b2 = AZ::GetClamp<float>(b1 - b2 + 0.5f, 0.0f, 1.0f);
a2 = AZ::GetClamp<float>(a1 - a2 + 0.5f, 0.0f, 1.0f);
pixelOp->SetRGBA(dstPixelBuf, r2, g2, b2, a2);
}
}
// mips below the chosen highpass mip are grey
for (AZ::u32 dstMip = dwMipDown; dstMip < dstMips; ++dstMip)
{
AZ::u8* dstPixelBuf;
AZ::u32 dstPitch;
newImage->GetImagePointer(dstMip, dstPixelBuf, dstPitch);
const AZ::u32 pixelCount = newImage->GetPixelCount(dstMip);
for (AZ::u32 i = 0; i < pixelCount; ++i, dstPixelBuf += pixelBytes)
{
pixelOp->SetRGBA(dstPixelBuf, 0.5f, 0.5f, 0.5f, 1.0f);
}
}
m_img = newImage;
}
} // namespace ImageProcessingAtom
@@ -82,10 +82,7 @@ namespace ImageProcessingAtomEditor
presetInfoText += "\n";
presetInfoText += QString("Suppress Engine Reduce: %1\n").arg(presetSettings->m_suppressEngineReduce ? "True" : "False");
presetInfoText += QString("Discard Alpha: %1\n").arg(presetSettings->m_discardAlpha ? "True" : "False");
presetInfoText += QString("Is Color Chart: %1\n").arg(presetSettings->m_isColorChart ? "True" : "False");
presetInfoText += QString("High Pass Mip: %1\n").arg(presetSettings->m_highPassMip);
presetInfoText += QString("Gloss From Normal: %1\n").arg(presetSettings->m_glossFromNormals);
presetInfoText += QString("Use Legacy Gloss: %1\n").arg(presetSettings->m_isLegacyGloss ? "True" : "False");
presetInfoText += QString("Mip Re-normalize: %1\n").arg(presetSettings->m_isMipRenormalize ? "True" : "False");
presetInfoText += QString("Resident Mips Number: %1\n").arg(presetSettings->m_numResidentMips);
presetInfoText += QString("Swizzle: %1\n").arg(presetSettings->m_swizzle.c_str());
@@ -74,7 +74,7 @@ namespace ImageProcessingAtom
builderDescriptor.m_busId = azrtti_typeid<ImageBuilderWorker>();
builderDescriptor.m_createJobFunction = AZStd::bind(&ImageBuilderWorker::CreateJobs, &m_imageBuilder, AZStd::placeholders::_1, AZStd::placeholders::_2);
builderDescriptor.m_processJobFunction = AZStd::bind(&ImageBuilderWorker::ProcessJob, &m_imageBuilder, AZStd::placeholders::_1, AZStd::placeholders::_2);
builderDescriptor.m_version = 25; // [ATOM-16575]
builderDescriptor.m_version = 26; // [ATOM-15086]
builderDescriptor.m_analysisFingerprint = ImageProcessingAtom::BuilderSettingManager::Instance()->GetAnalysisFingerprint();
m_imageBuilder.BusConnect(builderDescriptor.m_busId);
AssetBuilderSDK::AssetBuilderBus::Broadcast(&AssetBuilderSDK::AssetBuilderBusTraits::RegisterBuilderInformation, builderDescriptor);
@@ -46,7 +46,6 @@ namespace ImageProcessingAtom
enum ConvertStep
{
StepValidateInput = 0,
StepGenerateColorChart,
StepConvertToLinear,
StepSwizzle,
StepCubemapLayout,
@@ -55,9 +54,7 @@ namespace ImageProcessingAtom
StepMipmap,
StepGlossFromNormal,
StepPostNormalize,
StepCreateHighPass,
StepConvertOutputColorSpace,
StepAlphaImage,
StepConvertPixelFormat,
StepSaveToFile,
StepAll
@@ -66,7 +63,6 @@ namespace ImageProcessingAtom
[[maybe_unused]] const char ProcessStepNames[StepAll][64] =
{
"ValidateInput",
"GenerateColorChart",
"ConvertToLinear",
"Swizzle",
"CubemapLayout",
@@ -75,9 +71,7 @@ namespace ImageProcessingAtom
"Mipmap",
"GlossFromNormal",
"PostNormalize",
"CreateHighPass",
"ConvertOutputColorSpace",
"AlphaImage",
"ConvertPixelFormat",
"SaveToFile",
};
@@ -94,11 +88,6 @@ namespace ImageProcessingAtom
return nullptr;
}
IImageObjectPtr ImageConvertProcess::GetOutputAlphaImage()
{
return m_alphaImage;
}
IImageObjectPtr ImageConvertProcess::GetOutputIBLSpecularCubemap()
{
return m_iblSpecularCubemapImage;
@@ -180,6 +169,58 @@ namespace ImageProcessingAtom
m_image = new ImageToProcess(IImageObjectPtr(m_input->m_inputImage->Clone(mipsToClone)));
}
break;
case StepConvertToLinear:
// convert to linear space and the output image pixel format should be rgba32f
ConvertToLinear();
break;
case StepSwizzle:
{
// swizzle if swizzle was set or decard alpha
bool swizzleWasSet = m_input->m_presetSetting.m_swizzle.size() >= 4;
if (swizzleWasSet || m_input->m_presetSetting.m_discardAlpha)
{
AZStd::string swizzle = "rgba";
if (swizzleWasSet)
{
swizzle = m_input->m_presetSetting.m_swizzle.substr(0, 4);
}
if (m_input->m_presetSetting.m_discardAlpha)
{
swizzle[3] = '1';
}
m_image->Get()->Swizzle(swizzle.c_str());
if (!m_input->m_presetSetting.m_discardAlpha)
{
m_alphaContent = EAlphaContent::eAlphaContent_Absent;
}
else
{
m_alphaContent = m_image->Get()->GetAlphaContent();
}
}
}
break;
case StepCubemapLayout:
// convert cubemap image's layout to vertical strip used in game.
if (IsConvertToCubemap())
{
if (!m_image->ConvertCubemapLayout(CubemapLayoutVertical))
{
m_image->Set(nullptr);
}
}
break;
case StepPreNormalize:
// normalize base image before mipmap generation if glossfromnormals is enabled and require normalize
if (m_input->m_presetSetting.m_isMipRenormalize && m_input->m_presetSetting.m_glossFromNormals)
{
// Normalize the base mip map. This has to be done explicitly because we need to disable mip renormalization to
// preserve the normal length when deriving the normal variance
m_image->Get()->NormalizeVectors(0, 1);
}
break;
case StepGenerateIBL:
if (IsConvertToCubemap())
@@ -204,56 +245,6 @@ namespace ImageProcessingAtom
m_isFinished = true;
}
break;
case StepGenerateColorChart:
// GenerateColorChart.
if (m_input->m_presetSetting.m_isColorChart)
{
// Convert to uncompressed format if it's compressed format. For example, loaded from DDS file.
if (!CPixelFormats::GetInstance().IsPixelFormatUncompressed(m_image->Get()->GetPixelFormat()))
{
m_image->ConvertFormat(ePixelFormat_R32G32B32A32F);
}
m_image->CreateColorChart();
}
break;
case StepConvertToLinear:
// convert to linear space and the output image pixel format should be rgba32f
ConvertToLinear();
break;
case StepSwizzle:
// convert texture format.
if (m_input->m_presetSetting.m_swizzle.size() >= 4)
{
m_image->Get()->Swizzle(m_input->m_presetSetting.m_swizzle.substr(0, 4).c_str());
m_alphaContent = m_image->Get()->GetAlphaContent();
}
// convert gloss map (alhpa channel) from legacy distribution to new one
if (m_input->m_presetSetting.m_isLegacyGloss)
{
m_image->Get()->ConvertLegacyGloss();
}
break;
case StepCubemapLayout:
// convert cubemap image's layout to vertical strip used in game.
if (IsConvertToCubemap())
{
if (!m_image->ConvertCubemapLayout(CubemapLayoutVertical))
{
m_image->Set(nullptr);
}
}
break;
case StepPreNormalize:
// normalize base image before mipmap generation if glossfromnormals is enabled and require normalize
if (m_input->m_presetSetting.m_isMipRenormalize && m_input->m_presetSetting.m_glossFromNormals)
{
// Normalize the base mip map. This has to be done explicitly because we need to disable mip renormalization to
// preserve the normal length when deriving the normal variance
m_image->Get()->NormalizeVectors(0, 1);
}
break;
case StepMipmap:
// generate mipmaps
if (IsConvertToCubemap())
@@ -304,20 +295,10 @@ namespace ImageProcessingAtom
m_image->Get()->AddImageFlags(EIF_RenormalizedTexture);
}
break;
case StepCreateHighPass:
if (m_input->m_presetSetting.m_highPassMip > 0)
{
m_image->CreateHighPass(m_input->m_presetSetting.m_highPassMip);
}
break;
case StepConvertOutputColorSpace:
// convert image from linear space to desired output color space
ConvertToOuputColorSpace();
break;
case StepAlphaImage:
// save alpha channel to separate image if it's needed
CreateAlphaImage();
break;
case StepConvertPixelFormat:
// convert pixel format
ConvertPixelformat();
@@ -411,12 +392,6 @@ namespace ImageProcessingAtom
return;
}
// don't do any reduce for color chart
if (presetSettings->m_isColorChart)
{
return;
}
// get suitable size for dest pixel format
CPixelFormats::GetInstance().GetSuitableImageSize(presetSettings->m_pixelFormat, inputWidth, inputHeight,
outWidth, outHeight);
@@ -510,52 +485,6 @@ namespace ImageProcessingAtom
return true;
}
void ImageConvertProcess::CreateAlphaImage()
{
// if alpha content doesn't have alpha or we need to discard alpha, skip
// we won't create alpha image for cubemap too
if (m_alphaContent == EAlphaContent::eAlphaContent_Absent
|| m_alphaContent == EAlphaContent::eAlphaContent_OnlyWhite
|| m_input->m_presetSetting.m_discardAlpha || IsConvertToCubemap())
{
return;
}
// if dest format could save alpha, skip too
if (!CPixelFormats::GetInstance().IsPixelFormatWithoutAlpha(m_input->m_presetSetting.m_pixelFormat))
{
return;
}
// now create alpha image
ImageToProcess alphaImage(m_image->Get());
alphaImage.ConvertFormat(ePixelFormat_A8);
// validate pixelformatalpha
if (CPixelFormats::GetInstance().IsFormatSingleChannel(m_input->m_presetSetting.m_pixelFormatAlpha))
{
alphaImage.ConvertFormat(m_input->m_presetSetting.m_pixelFormatAlpha);
}
else
{
//For ASTC compression we need to clear out the alpha to get accurate rgb compression.
if (IsASTCFormat(m_input->m_presetSetting.m_pixelFormat))
{
alphaImage.ConvertFormat(ePixelFormat_R8G8B8X8);
alphaImage.ConvertFormat(m_input->m_presetSetting.m_pixelFormatAlpha);
}
else
{
AZ_Assert(false, "PixelFormatAlpha only supports single channel pixel formats or ASTC formats");
}
}
// get final result and save it to member variable for later use
m_alphaImage = alphaImage.Get();
m_image->Get()->AddImageFlags(EIF_AttachedAlpha);
}
// pixel format conversion
bool ImageConvertProcess::ConvertPixelformat()
{
@@ -575,12 +504,6 @@ namespace ImageProcessingAtom
m_image->GetCompressOption().rgbWeight = m_input->m_presetSetting.GetColorWeight();
m_image->GetCompressOption().discardAlpha = m_input->m_presetSetting.m_discardAlpha;
//For ASTC compression we need to clear out the alpha to get accurate rgb compression.
if(m_alphaImage && IsASTCFormat(m_input->m_presetSetting.m_pixelFormat))
{
m_image->GetCompressOption().discardAlpha = true;
}
m_image->ConvertFormat(m_input->m_presetSetting.m_pixelFormat);
return true;
@@ -762,7 +685,6 @@ namespace ImageProcessingAtom
if (ImageProcess##PrivateName::DoesSupport(m_input->m_platform)) \
{ \
ImageProcess##PrivateName::PrepareImageForExport(m_image->Get()); \
ImageProcess##PrivateName::PrepareImageForExport(m_alphaImage); \
}
AZ_TOOLS_EXPAND_FOR_RESTRICTED_PLATFORMS
#undef AZ_RESTRICTED_PLATFORM_EXPANSION
@@ -115,7 +115,6 @@ namespace ImageProcessingAtom
//get output images
IImageObjectPtr GetOutputImage();
IImageObjectPtr GetOutputAlphaImage();
IImageObjectPtr GetOutputIBLSpecularCubemap();
IImageObjectPtr GetOutputIBLDiffuseCubemap();
@@ -131,8 +130,6 @@ namespace ImageProcessingAtom
//for alpha
//to indicate the current alpha channel content
EAlphaContent m_alphaContent;
//An image object to hold alpha channel in a separate image
IImageObjectPtr m_alphaImage;
//output results of IBL cubemap generation, used in unit tests
IImageObjectPtr m_iblSpecularCubemapImage;
@@ -171,9 +168,6 @@ namespace ImageProcessingAtom
//convert to output color space before compression
bool ConvertToOuputColorSpace();
//create alpha image if it's needed
void CreateAlphaImage();
//pixel format convertion/compression
bool ConvertPixelformat();
@@ -108,17 +108,14 @@ namespace ImageProcessingAtom
}
IImageObjectPtr outputImage = m_process->GetOutputImage();
IImageObjectPtr outputImageAlpha = m_process->GetOutputAlphaImage();
m_output->SetOutputImage(outputImage, ImageConvertOutput::Base);
m_output->SetOutputImage(outputImageAlpha, ImageConvertOutput::Alpha);
if (!IsJobCancelled())
{
// For preview, combine image output with alpha if any
m_output->SetProgress(1.0f / static_cast<float>(m_previewProcessStep));
IImageObjectPtr combinedImage = MergeOutputImageForPreview(outputImage, outputImageAlpha);
m_output->SetOutputImage(combinedImage, ImageConvertOutput::Preview);
m_output->SetOutputImage(outputImage, ImageConvertOutput::Preview);
}
m_output->SetReady(true);
@@ -20,7 +20,7 @@ namespace ImageProcessingAtom
const static AZ::u32 EIF_Greyscale = 0x8; // hint for the engine (e.g. greyscale light beams can be applied to shadow mask), can be for DXT1 because compression artfacts don't count as color
const static AZ::u32 EIF_SupressEngineReduce = 0x10; // info for the engine: don't reduce texture resolution on this texture
const static AZ::u32 EIF_UNUSED_BIT = 0x40; // Free to use
const static AZ::u32 EIF_AttachedAlpha = 0x400; // info for the engine: it's a texture with attached alpha channel
const static AZ::u32 EIF_AttachedAlpha = 0x400; // deprecated: info for the engine: it's a texture with attached alpha channel
const static AZ::u32 EIF_SRGBRead = 0x800; // info for the engine: if gamma corrected rendering is on, this texture requires SRGBRead (it's not stored in linear)
const static AZ::u32 EIF_DontResize = 0x8000; // info for the engine: for dds textures that shouldn't be resized
const static AZ::u32 EIF_RenormalizedTexture = 0x10000; // info for the engine: for dds textures that have renormalized color range
@@ -316,130 +316,6 @@ namespace ImageProcessingAtom
m_mips.clear();
}
//note: there are some unreasonable parts of the save files formats for cry textures. We might need to rethink about
// it for new renderer
bool CImageObject::SaveImage(const char* filename, IImageObjectPtr alphaImage, AZStd::vector<AZStd::string>& outFilePaths) const
{
AZ::IO::SystemFile file;
file.Open(filename, AZ::IO::SystemFile::SF_OPEN_CREATE | AZ::IO::SystemFile::SF_OPEN_CREATE_PATH | AZ::IO::SystemFile::SF_OPEN_WRITE_ONLY);
AZ::IO::SystemFileStream fileSaveStream(&file, true);
if (!fileSaveStream.IsOpen())
{
AZ_Warning("Image Processing", false, "%s: failed to create file %s", __FUNCTION__, filename);
return false;
}
if (alphaImage)
{
AZ_Assert(HasImageFlags(EIF_AttachedAlpha), "attached alpha image flag wasn't set");
AZ_Assert(!alphaImage->HasImageFlags(EIF_AttachedAlpha), "alpha image shouldn't have attached alpha image flag");
// inherit cubemap and decal image flags to attached alpha image
alphaImage->AddImageFlags(GetImageFlags() & (EIF_Cubemap
| EIF_Decal | EIF_Splitted));
alphaImage->SetNumPersistentMips(m_numPersistentMips);
}
bool bOk = SaveImage(fileSaveStream);
bool hasSplitFlag = HasImageFlags(EIF_Splitted);
//append alpha image data in the end if there is no split
if (bOk && alphaImage && !hasSplitFlag)
{
//4 bytes extension tag, 4 bytes attached alpha tag, then 4 bytes of chunk size
fileSaveStream.Write(sizeof(FOURCC_CExt), &FOURCC_CExt); // marker for the start of O3DE Extended data
fileSaveStream.Write(sizeof(FOURCC_AttC), &FOURCC_AttC); // Attached Channel chunk
uint32_t size = 0;
uint32_t sizeBytes = sizeof(size);
fileSaveStream.Write(sizeBytes, &size); //size of attached chunk
//save alpha image and get the size
AZ::IO::SizeType startPos = fileSaveStream.GetCurPos();
bOk = alphaImage->SaveImage(fileSaveStream);
AZ::IO::SizeType endPos = fileSaveStream.GetCurPos();
size = static_cast<uint32_t>(endPos - startPos);
//move back to beginning of chunk and write chunk size then move back to end
fileSaveStream.Seek(startPos - sizeBytes, AZ::IO::GenericStream::ST_SEEK_BEGIN);
fileSaveStream.Write(sizeBytes, &size);
fileSaveStream.Seek(endPos, AZ::IO::GenericStream::ST_SEEK_BEGIN);
// marker for the end of O3DE Extended data
fileSaveStream.Write(sizeof(FOURCC_CEnd), &FOURCC_CEnd);
}
if (!bOk)
{
AZ::IO::SystemFile::Delete(filename);
return false;
}
// It's important to maintain the product output sequence. Asset Database/Browser will use the first product to determine the source type!
outFilePaths.push_back(filename);
// save stand alone products
if (hasSplitFlag)
{
// alpha
if (alphaImage)
{
AZStd::string alphaFile = AZStd::string::format("%s.a", filename);
AZ::IO::SystemFile outAlphaFile;
outAlphaFile.Open(alphaFile.c_str(), AZ::IO::SystemFile::SF_OPEN_CREATE | AZ::IO::SystemFile::SF_OPEN_CREATE_PATH | AZ::IO::SystemFile::SF_OPEN_WRITE_ONLY);
AZ::IO::SystemFileStream alphaFileSaveStream(&outAlphaFile, true);
if (alphaFileSaveStream.IsOpen())
{
alphaImage->SaveImage(alphaFileSaveStream);
outFilePaths.push_back(alphaFile);
}
else
{
AZ_Warning("Image Processing", false, "%s: failed to create file %s", __FUNCTION__, alphaFile.c_str());
}
}
// mips
AZ::u32 numStreamable = GetMipCount() - m_numPersistentMips;
for (AZ::u32 mip = 0; mip < numStreamable; mip++)
{
AZ::u32 nameIdx = numStreamable - mip;
AZStd::string mipFileName = AZStd::string::format("%s.%d", filename, nameIdx);
SaveMipToFile(mip, mipFileName);
outFilePaths.push_back(mipFileName);
if (alphaImage)
{
AZStd::string mipAlphaFileName = mipFileName + "a";
alphaImage->SaveMipToFile(mip, mipAlphaFileName);
outFilePaths.push_back(mipAlphaFileName);
}
}
}
return bOk;
}
bool CImageObject::SaveMipToFile(AZ::u32 mip, const AZStd::string& filename) const
{
AZ::IO::SystemFile saveFile;
saveFile.Open(filename.c_str(), AZ::IO::SystemFile::SF_OPEN_CREATE | AZ::IO::SystemFile::SF_OPEN_CREATE_PATH | AZ::IO::SystemFile::SF_OPEN_WRITE_ONLY);
AZ::IO::SystemFileStream saveFileStream(&saveFile, true);
if (!saveFileStream.IsOpen())
{
AZ_Warning("Image Processing", false, "%s: failed to create file %s", __FUNCTION__, filename.c_str());
return false;
}
saveFileStream.Write(GetMipBufSize(mip), m_mips[mip]->m_pData);
return true;
}
float CImageObject::CalculateAverageBrightness() const
{
//if it's compressed format, return a default value
@@ -642,63 +518,6 @@ namespace ImageProcessingAtom
return true;
}
bool CImageObject::SaveImage(AZ::IO::SystemFileStream& saveFileStream) const
{
DDS_FILE_DESC_LEGACY desc;
DDS_HEADER_DXT10 exthead;
desc.dwMagic = FOURCC_DDS;
if (!BuildSurfaceHeader(desc.header))
{
return false;
}
if (desc.header.IsDX10Ext() && !BuildSurfaceExtendedHeader(exthead))
{
return false;
}
saveFileStream.Write(sizeof(desc), &desc);
if (desc.header.IsDX10Ext())
{
saveFileStream.Write(sizeof(exthead), &exthead);
}
AZ::u32 faces = 1;
//for cubemap. export each face and its mipmap
if (HasImageFlags(EIF_Cubemap))
{
faces = 6;
}
AZ::u32 mipStart = 0;
if (HasImageFlags(EIF_Splitted))
{
if (m_numPersistentMips < m_mips.size())
{
mipStart = (AZ::u32)m_mips.size() - m_numPersistentMips;
}
else
{
AZ_Assert(false, "numPersistentMips wasn't setup correctly");
}
}
for (AZ::u32 face = 0; face < faces; face++)
{
for (AZ::u32 mip = mipStart; mip < m_mips.size(); ++mip)
{
const MipLevel& level = *m_mips[mip];
AZ::u32 faceBufSize = level.m_pitch * level.m_rowCount / faces;
saveFileStream.Write(faceBufSize, level.m_pData + faceBufSize * face);
}
}
return true;
}
void CImageObject::GetExtent(AZ::u32& width, AZ::u32& height, AZ::u32& mipCount) const
{
mipCount = (AZ::u32)m_mips.size();
@@ -953,35 +772,4 @@ namespace ImageProcessingAtom
}
}
}
void CImageObject::ConvertLegacyGloss()
{
if (!(CPixelFormats::GetInstance().IsPixelFormatUncompressed(m_pixelFormat)))
{
AZ_Assert(false, "%s function only works with uncompressed pixel format", __FUNCTION__);
return;
}
//create pixel operation function
IPixelOperationPtr pixelOp = CreatePixelOperation(m_pixelFormat);
//get count of bytes per pixel
AZ::u32 pixelBytes = CPixelFormats::GetInstance().GetPixelFormatInfo(m_pixelFormat)->bitsPerBlock / 8;
const AZ::u32 mips = (AZ::u32)m_mips.size();
float color[4];
for (AZ::u32 mip = 0; mip < mips; ++mip)
{
AZ::u8* pixelBuf = m_mips[mip]->m_pData;
const AZ::u32 pixelCount = GetPixelCount(mip);
for (AZ::u32 i = 0; i < pixelCount; ++i, pixelBuf += pixelBytes)
{
pixelOp->GetRGBA(pixelBuf, color[0], color[1], color[2], color[3]);
// Convert from (1 - s * 0.7)^6 to (1 - s)^2
color[3] = 1 - pow(1.0f - color[3] * 0.7f, 3.0f);
pixelOp->SetRGBA(pixelBuf, color[0], color[1], color[2], color[3]);
}
}
}
} // namespace ImageProcessingAtom
@@ -57,10 +57,6 @@ namespace ImageProcessingAtom
bool CompareImage(const IImageObjectPtr otherImage) const override;
bool SaveImage(const char* filename, IImageObjectPtr alphaImage, AZStd::vector<AZStd::string>& outFilePaths) const override;
bool SaveImage(AZ::IO::SystemFileStream& out) const override;
bool SaveMipToFile(AZ::u32 mip, const AZStd::string& filename) const override;
uint32_t GetTextureMemory() const override;
EAlphaContent GetAlphaContent() const override;
@@ -79,7 +75,6 @@ namespace ImageProcessingAtom
void SetNumPersistentMips(AZ::u32 nMips) override;
void GlossFromNormals(bool hasAuthoredGloss) override;
void ConvertLegacyGloss() override;
void ClearColor(float r, float g, float b, float a) override;
//end virtual functions from IImageObject
@@ -66,13 +66,6 @@ namespace ImageProcessingAtom
bool GammaToLinearRGBA32F(bool bDeGamma);
void LinearToGamma();
// ---------------------------------------------------------------------------------
// Tools for A32B32G32R32F
void CreateHighPass(uint32 dwMipDown);
void CreateColorChart();
//convert various original cubemap layouts to new layout
bool ConvertCubemapLayout(CubemapLayoutType newLayout);
};
@@ -988,7 +988,6 @@ namespace UnitTest
ASSERT_TRUE(process->IsSucceed());
SaveImageToFile(process->GetOutputImage(), "rgb", 10);
SaveImageToFile(process->GetOutputAlphaImage(), "alpha", 10);
process->GetAppendOutputProducts(outProducts);
@@ -103,8 +103,6 @@ set(FILES
Source/Converters/ConvertPixelFormat.cpp
Source/Converters/Cubemap.h
Source/Converters/Cubemap.cpp
Source/Converters/ColorChart.cpp
Source/Converters/HighPass.cpp
Source/Converters/Histogram.cpp
Source/Converters/Histogram.h
../External/CubeMapGen/CBBoxInt32.cpp
@@ -2,6 +2,7 @@
"gem_name": "ImageProcessingAtom",
"display_name": "Atom Image Processing",
"license": "Apache-2.0 Or MIT",
"license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt",
"origin": "Open 3D Engine - o3de.org",
"type": "Code",
"summary": "",
+1
View File
@@ -2,6 +2,7 @@
"gem_name": "AtomShader",
"display_name": "Atom Shader Builder",
"license": "Apache-2.0 Or MIT",
"license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt",
"origin": "Open 3D Engine - o3de.org",
"type": "Code",
"summary": "",
@@ -272,6 +272,7 @@ namespace AZ
// Create and register a scene with all available feature processors
RPI::SceneDescriptor sceneDesc;
sceneDesc.m_nameId = AZ::Name("Main");
AZ::RPI::ScenePtr atomScene = RPI::Scene::CreateScene(sceneDesc);
atomScene->EnableAllFeatureProcessors();
atomScene->Activate();
+1
View File
@@ -2,6 +2,7 @@
"gem_name": "Atom_Bootstrap",
"display_name": "Atom Bootstrap",
"license": "Apache-2.0 Or MIT",
"license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt",
"origin": "Open 3D Engine - o3de.org",
"type": "Code",
"summary": "",
+1
View File
@@ -2,6 +2,7 @@
"gem_name": "Atom_Component_DebugCamera",
"display_name": "Atom Debug Camera Component",
"license": "Apache-2.0 Or MIT",
"license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt",
"origin": "Open 3D Engine - o3de.org",
"type": "Code",
"summary": "",
@@ -153,7 +153,7 @@ struct StandardMaterialInputs
float2 m_vertexUv[UvSetCount];
float3x3 m_uvMatrix;
float m_normal;
float3 m_normal;
float3 m_tangents[UvSetCount];
float3 m_bitangents[UvSetCount];
@@ -32,7 +32,7 @@ namespace AZ
{
if (m_rtPipeline)
{
AZ::RPI::RPISystemInterface::Get()->GetDefaultScene()->RemoveRenderPipeline(m_rtPipeline->GetId());
m_rtPipeline->RemoveFromScene();
m_rtPipeline = nullptr;
}
@@ -111,8 +111,12 @@ namespace AZ
parentPass->SetSourceTexture(m_texture, RHI::Format::R8G8B8A8_UNORM);
break;
}
AZ::RPI::RPISystemInterface::Get()->GetDefaultScene()->AddRenderPipeline(m_rtPipeline);
const auto mainScene = AZ::RPI::RPISystemInterface::Get()->GetSceneByName(AZ::Name("RPI"));
if (mainScene)
{
mainScene->AddRenderPipeline(m_rtPipeline);
}
}
bool LuxCoreTexture::IsIBLTexture()
+1
View File
@@ -2,6 +2,7 @@
"gem_name": "Atom_Feature_Common",
"display_name": "Atom Feature Common",
"license": "Apache-2.0 Or MIT",
"license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt",
"origin": "Open 3D Engine - o3de.org",
"type": "Code",
"summary": "",
+1
View File
@@ -2,6 +2,7 @@
"gem_name": "Atom_RHI_DX12",
"display_name": "Atom RHI DX12",
"license": "Apache-2.0 Or MIT",
"license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt",
"origin": "Open 3D Engine - o3de.org",
"type": "Code",
"summary": "",
+1
View File
@@ -2,6 +2,7 @@
"gem_name": "Atom_RHI_Metal",
"display_name": "Atom RHI Metal",
"license": "Apache-2.0 Or MIT",
"license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt",
"origin": "Open 3D Engine - o3de.org",
"type": "Code",
"summary": "",
+1
View File
@@ -2,6 +2,7 @@
"gem_name": "Atom_RHI_Null",
"display_name": "Atom RHI Null",
"license": "Apache-2.0 Or MIT",
"license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt",
"origin": "Open 3D Engine - o3de.org",
"type": "Code",
"summary": "",
+1
View File
@@ -2,6 +2,7 @@
"gem_name": "Atom_RHI_Vulkan",
"display_name": "Atom RHI Vulkan",
"license": "Apache-2.0 Or MIT",
"license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt",
"origin": "Open 3D Engine - o3de.org",
"type": "Code",
"summary": "",
+1
View File
@@ -2,6 +2,7 @@
"gem_name": "Atom_RHI",
"display_name": "Atom RHI",
"license": "Apache-2.0 Or MIT",
"license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt",
"origin": "Open 3D Engine - o3de.org",
"type": "Code",
"summary": "",
@@ -70,7 +70,8 @@ namespace AZ
void InitializeSystemAssets() override;
void RegisterScene(ScenePtr scene) override;
void UnregisterScene(ScenePtr scene) override;
ScenePtr GetScene(const SceneId& sceneId) const override;
Scene* GetScene(const SceneId& sceneId) const override;
Scene* GetSceneByName(const AZ::Name& name) const override;
ScenePtr GetDefaultScene() const override;
RenderPipelinePtr GetRenderPipelineForWindow(AzFramework::NativeWindowHandle windowHandle) override;
Data::Asset<ShaderAsset> GetCommonShaderAssetForSrgs() const override;
@@ -13,6 +13,7 @@
#include <Atom/RPI.Public/Base.h>
#include <AzCore/Name/Name.h>
#include <AzFramework/Windowing/WindowBus.h>
namespace AZ
@@ -46,11 +47,14 @@ namespace AZ
//! Unregister a scene from RPISystem. The scene won't be simulated or rendered.
virtual void UnregisterScene(ScenePtr scene) = 0;
// [GFX TODO] to be removed when we have scene setup in AZ Core
virtual ScenePtr GetDefaultScene() const = 0;
//! Deprecated. Use GetSceneByName(name), GetSceneForEntityContextId(entityContextId) or Scene::GetSceneForEntityId(AZ::EntityId entityId) instead
AZ_DEPRECATED(virtual ScenePtr GetDefaultScene() const = 0;, "This method has been deprecated. Please use GetSceneByName(name), GetSceneForEntityContextId(entityContextId) or Scene::GetSceneForEntityId(AZ::EntityId entityId) instead.");
//! Get scene by using scene id.
virtual ScenePtr GetScene(const SceneId& sceneId) const = 0;
virtual Scene* GetScene(const SceneId& sceneId) const = 0;
//! Get scene by using scene name.
virtual Scene* GetSceneByName(const AZ::Name& name) const = 0;
//! Get the render pipeline created for a window
virtual RenderPipelinePtr GetRenderPipelineForWindow(AzFramework::NativeWindowHandle windowHandle) = 0;
@@ -80,6 +80,9 @@ namespace AZ
//! Gets the RPI::Scene for a given entityContextId.
//! May return nullptr if there is no RPI::Scene created for that entityContext.
static Scene* GetSceneForEntityContextId(AzFramework::EntityContextId entityContextId);
//! Gets the RPI::Scene for a given entityId.
static Scene* GetSceneForEntityId(AZ::EntityId entityId);
~Scene();
@@ -135,6 +138,8 @@ namespace AZ
const SceneId& GetId() const;
AZ::Name GetName() const;
//! Set default pipeline by render pipeline ID.
//! It returns true if the default render pipeline was set from the input ID.
//! If the specified render pipeline doesn't exist in this scene then it won't do anything and returns false.
@@ -245,6 +250,9 @@ namespace AZ
// The uuid to identify this scene.
SceneId m_id;
// Scene's name which is set at initialization. Can be empty
AZ::Name m_name;
bool m_activated = false;
bool m_taskGraphActive = false; // update during tick, to ensure it only changes on frame boundaries
@@ -286,13 +294,10 @@ namespace AZ
template<typename FeatureProcessorType>
FeatureProcessorType* Scene::GetFeatureProcessorForEntity(AZ::EntityId entityId)
{
// Find the entity context for the entity ID.
AzFramework::EntityContextId entityContextId = AzFramework::EntityContextId::CreateNull();
AzFramework::EntityIdContextQueryBus::EventResult(entityContextId, entityId, &AzFramework::EntityIdContextQueryBus::Events::GetOwningContextId);
if (!entityContextId.IsNull())
RPI::Scene* renderScene = GetSceneForEntityId(entityId);
if (renderScene)
{
return GetFeatureProcessorForEntityContextId<FeatureProcessorType>(entityContextId);
return renderScene->GetFeatureProcessor<FeatureProcessorType>();
}
return nullptr;
};
@@ -9,6 +9,7 @@
#pragma once
#include <AzCore/Asset/AssetCommon.h>
#include <AzCore/Name/Name.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/std/string/string.h>
@@ -25,6 +26,9 @@ namespace AZ
//! List of feature processors which the scene will initially enable.
AZStd::vector<AZStd::string> m_featureProcessorNames;
//! A name used as scene id. It can be used to search a registered scene via RPISystemInterface::GetScene()
AZ::Name m_nameId;
};
} // namespace RPI
} // namespace AZ
@@ -699,9 +699,7 @@ namespace AZ
m_parentScene = parentScene;
AZ_Assert(m_visScene == nullptr, "IVisibilityScene already created for this RPI::Scene");
char sceneIdBuf[40] = "";
m_parentScene->GetId().ToString(sceneIdBuf);
AZ::Name visSceneName(AZStd::string::format("RenderCullScene[%s]", sceneIdBuf));
AZ::Name visSceneName(AZStd::string::format("RenderCullScene[%s]", m_parentScene->GetName().GetCStr()));
m_visScene = AZ::Interface<AzFramework::IVisibilitySystem>::Get()->CreateVisibilityScene(visSceneName);
#ifdef AZ_CULL_DEBUG_ENABLED
@@ -159,6 +159,11 @@ namespace AZ
AZ_Assert(false, "Scene was already registered");
return;
}
else if (!scene->GetName().IsEmpty() && scene->GetName() == sceneItem->GetName())
{
// only report a warning if there is a scene with duplicated name
AZ_Warning("RPISystem", false, "There is a registered scene with same name [%s]", scene->GetName().GetCStr());
}
}
m_scenes.push_back(scene);
@@ -177,11 +182,35 @@ namespace AZ
AZ_Assert(false, "Can't unregister scene which wasn't registered");
}
ScenePtr RPISystem::GetScene(const SceneId& sceneId) const
Scene* RPISystem::GetScene(const SceneId& sceneId) const
{
for (const auto& scene : m_scenes)
{
if (scene->GetId() == sceneId)
{
return scene.get();
}
}
return nullptr;
}
Scene* RPISystem::GetSceneByName(const AZ::Name& name) const
{
for (const auto& scene : m_scenes)
{
if (scene->GetName() == name)
{
return scene.get();
}
}
return nullptr;
}
ScenePtr RPISystem::GetDefaultScene() const
{
for (const auto& scene : m_scenes)
{
if (scene->GetName() == AZ::Name("Main"))
{
return scene;
}
@@ -189,16 +218,6 @@ namespace AZ
return nullptr;
}
ScenePtr RPISystem::GetDefaultScene() const
{
if (m_scenes.size() > 0)
{
return m_scenes[0];
}
return nullptr;
}
RenderPipelinePtr RPISystem::GetRenderPipelineForWindow(AzFramework::NativeWindowHandle windowHandle)
{
RenderPipelinePtr renderPipeline;
+22 -3
View File
@@ -45,7 +45,9 @@ namespace AZ
auto shaderAsset = RPISystemInterface::Get()->GetCommonShaderAssetForSrgs();
scene->m_srg = ShaderResourceGroup::Create(shaderAsset, sceneSrgLayout->GetName());
}
scene->m_name = sceneDescriptor.m_nameId;
return ScenePtr(scene);
}
@@ -83,10 +85,23 @@ namespace AZ
return nullptr;
}
Scene* Scene::GetSceneForEntityId(AZ::EntityId entityId)
{
// Find the entity context for the entity ID.
AzFramework::EntityContextId entityContextId = AzFramework::EntityContextId::CreateNull();
AzFramework::EntityIdContextQueryBus::EventResult(entityContextId, entityId, &AzFramework::EntityIdContextQueryBus::Events::GetOwningContextId);
if (!entityContextId.IsNull())
{
return GetSceneForEntityContextId(entityContextId);
}
return nullptr;
}
Scene::Scene()
{
m_id = Uuid::CreateRandom();
m_id = AZ::Uuid::CreateRandom();
m_cullingScene = aznew CullingScene();
SceneRequestBus::Handler::BusConnect(m_id);
m_drawFilterTagRegistry = RHI::DrawFilterTagRegistry::Create();
@@ -299,7 +314,6 @@ namespace AZ
// Force to update the lookup table since adding render pipeline would effect any pipeline states created before pass system tick
RebuildPipelineStatesLookup();
AZ_Assert(!m_id.IsNull(), "RPI::Scene needs to have a valid uuid.");
SceneNotificationBus::Event(m_id, &SceneNotification::OnRenderPipelineAdded, pipeline);
}
@@ -785,6 +799,11 @@ namespace AZ
{
return m_id;
}
AZ::Name Scene::GetName() const
{
return m_name;
}
bool Scene::SetDefaultRenderPipeline(const RenderPipelineId& pipelineId)
{
+1
View File
@@ -3,6 +3,7 @@
"display_name": "Atom API",
"summary": "",
"license": "Apache-2.0 Or MIT",
"license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt",
"origin": "Open 3D Engine - o3de.org",
"type": "Code",
"canonical_tags": [
@@ -43,6 +43,7 @@ namespace AtomToolsFramework
&PreviewerFeatureProcessorProviderBus::Handler::GetRequiredFeatureProcessors, featureProcessors);
AZ::RPI::SceneDescriptor sceneDesc;
sceneDesc.m_nameId = AZ::Name("PreviewRenderer");
sceneDesc.m_featureProcessorNames.assign(featureProcessors.begin(), featureProcessors.end());
m_scene = AZ::RPI::Scene::CreateScene(sceneDesc);
@@ -2,6 +2,7 @@
"gem_name": "AtomToolsFramework",
"display_name": "Atom Tools Framework",
"license": "Apache-2.0 Or MIT",
"license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt",
"origin": "Open 3D Engine - o3de.org",
"type": "Code",
"summary": "",

Some files were not shown because too many files have changed in this diff Show More