Merge branch 'development' into cmake/SPEC-7179

This commit is contained in:
Esteban Papp
2021-07-19 15:48:29 -07:00
66 changed files with 572 additions and 1755 deletions
+1 -3
View File
@@ -243,7 +243,7 @@ namespace Editor
}
EditorQtApplication::EditorQtApplication(int& argc, char** argv)
: QApplication(argc, argv)
: AzQtApplication(argc, argv)
, m_inWinEventFilter(false)
, m_stylesheet(new AzQtComponents::O3DEStylesheet(this))
, m_idleTimer(new QTimer(this))
@@ -253,8 +253,6 @@ namespace Editor
setWindowIcon(QIcon(":/Application/res/o3de_editor.ico"));
// set the default key store for our preferences:
setOrganizationName("O3DE");
setOrganizationDomain("o3de.org");
setApplicationName("O3DE Editor");
connect(m_idleTimer, &QTimer::timeout, this, &EditorQtApplication::maybeProcessIdle);
+2 -2
View File
@@ -8,7 +8,6 @@
#pragma once
#if !defined(Q_MOC_RUN)
#include <QApplication>
#include <QAbstractNativeEventFilter>
#include <QColor>
#include <QMap>
@@ -20,6 +19,7 @@
#include <AzCore/PlatformDef.h>
#include <AzCore/UserSettings/UserSettingsProvider.h>
#include <IEditor.h>
#include <AzQtComponents/Application/AzQtApplication.h>
#endif
class QFileInfo;
@@ -48,7 +48,7 @@ namespace Editor
void ScanDirectories(QFileInfoList& directoryList, const QStringList& filters, QFileInfoList& files, ScanDirectoriesUpdateCallBack updateCallback = nullptr);
class EditorQtApplication
: public QApplication
: public AzQtComponents::AzQtApplication
, public QAbstractNativeEventFilter
, public IEditorNotifyListener
, public AZ::UserSettingsOwnerRequestBus::Handler
+1 -1
View File
@@ -103,7 +103,7 @@
AZ_CVAR(
bool, ed_visibility_logTiming, false, nullptr, AZ::ConsoleFunctorFlags::Null, "Output the timing of the new IVisibilitySystem query");
AZ_CVAR(bool, ed_useNewCameraSystem, true, nullptr, AZ::ConsoleFunctorFlags::Null, "Use the new Editor camera system");
AZ_CVAR(bool, ed_showCursorCameraLook, false, nullptr, AZ::ConsoleFunctorFlags::Null, "Show the cursor when using free look with the new camera system");
AZ_CVAR(bool, ed_showCursorCameraLook, true, nullptr, AZ::ConsoleFunctorFlags::Null, "Show the cursor when using free look with the new camera system");
namespace SandboxEditor
{
@@ -130,7 +130,11 @@ namespace AZ
//So for each plane, we can test compare the center-to-plane distance to this interval to see which side of the plane the AABB is on.
//The AABB is not overlapping if it is fully behind any of the planes, otherwise it is overlapping.
const Vector3 center = aabb.GetCenter();
const Vector3 extents = 0.5f * aabb.GetExtents();
//If the AABB contains FLT_MAX at either (or both) extremes, it would be easy to overflow here by using "0.5f * GetExtents()"
//or "0.5f * (GetMax() - GetMin())". By separating into two separate multiplies before the subtraction, we can ensure
//that we don't overflow.
const Vector3 extents = (0.5f * aabb.GetMax()) - (0.5f * aabb.GetMin());
for (Frustum::PlaneId planeId = Frustum::PlaneId::Near; planeId < Frustum::PlaneId::MAX; ++planeId)
{
@@ -42,6 +42,7 @@ namespace UnitTest
AZ::Aabb unitBox = AZ::Aabb::CreateCenterHalfExtents(AZ::Vector3::CreateZero(), AZ::Vector3(1.f, 1.f, 1.f));
AZ::Aabb aabb = AZ::Aabb::CreateCenterHalfExtents(AZ::Vector3(10.f, 10.f, 10.f), AZ::Vector3(1.f, 1.f, 1.f));
AZ::Aabb aabb1 = AZ::Aabb::CreateCenterHalfExtents(AZ::Vector3(10.f, 10.f, 10.f), AZ::Vector3(100.f, 100.f, 100.f));
AZ::Aabb maxSizeAabb = AZ::Aabb::CreateFromMinMax(AZ::Vector3(-AZ::Constants::FloatMax), AZ::Vector3(AZ::Constants::FloatMax));
AZ::Vector3 point(0.f, 0.f, 0.f);
AZ::Vector3 point1(10.f, 10.f, 10.f);
@@ -73,6 +74,10 @@ namespace UnitTest
EXPECT_TRUE(AZ::ShapeIntersection::Overlaps(frustum, aabb1));
EXPECT_TRUE(AZ::ShapeIntersection::Overlaps(sphere1, far_value));
// Verify that an AABB that covers the max floating point range successfully overlaps with a frustum and doesn't hit any
// floating-point math overflows.
EXPECT_TRUE(AZ::ShapeIntersection::Overlaps(frustum, maxSizeAabb));
EXPECT_FALSE(AZ::ShapeIntersection::Overlaps(frustum, aabb));
EXPECT_FALSE(AZ::ShapeIntersection::Overlaps(unitSphere, aabb));
EXPECT_FALSE(AZ::ShapeIntersection::Overlaps(unitSphere, sphere2));
@@ -0,0 +1,38 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzQtComponents/Application/AzQtApplication.h>
#include <AzCore/PlatformIncl.h> // This should be the first include to make sure Windows.h is defined with NOMINMAX
#include <AzQtComponents/Utilities/QtPluginPaths.h>
namespace AzQtComponents
{
AzQtApplication::AzQtApplication(int& argc, char** argv)
: QApplication(argc, argv)
{
// Use a common Qt settings path for applications that don't register their own application name
QApplication::setOrganizationName("O3DE");
QApplication::setOrganizationDomain("o3de.org");
QApplication::setApplicationName("O3DE Tools Application");
AzQtComponents::PrepareQtPaths();
QLocale::setDefault(QLocale(QLocale::English, QLocale::UnitedStates));
}
void AzQtApplication::InitializeDpiScaling()
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
QCoreApplication::setAttribute(Qt::AA_DontCreateNativeWidgetSiblings);
QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough);
AzQtComponents::Utilities::HandleDpiAwareness(AzQtComponents::Utilities::SystemDpiAware);
}
} // namespace AzQtComponents
@@ -0,0 +1,33 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <QApplication>
#include <AzQtComponents/Utilities/HandleDpiAwareness.h>
namespace AzQtComponents
{
//! Base class for O3DE Tools Applications
class AZ_QT_COMPONENTS_API AzQtApplication
: public QApplication
{
public:
AzQtApplication(int& argc, char** argv);
//! Initializes Qt DPI scaling to handle displays with high display densities, such as Retina displays.
//! Currently, this uses Qt's system DPI awareness, in which a common device scaling factor will be
//! calculated across all attached screens.
//! \warning This must be called before this AzQtApplication instance is initialized.
static void InitializeDpiScaling();
};
} // namespace AzQtComponents
@@ -8,6 +8,8 @@
set(FILES
AzQtComponentsAPI.h
Application/AzQtApplication.cpp
Application/AzQtApplication.h
Buses/DragAndDrop.h
Buses/ShortcutDispatch.h
DragAndDrop/MainWindowDragAndDrop.h
@@ -0,0 +1,74 @@
/*
* 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 <AzToolsFramework/Logger/TraceLogger.h>
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
#include <AzFramework/StringFunc/StringFunc.h>
namespace AzToolsFramework
{
TraceLogger::TraceLogger()
{
AZ::Debug::TraceMessageBus::Handler::BusConnect();
}
TraceLogger::~TraceLogger()
{
AZ::Debug::TraceMessageBus::Handler::BusDisconnect();
}
bool TraceLogger::OnOutput(const char* window, const char* message)
{
if (m_logFile)
{
m_logFile->AppendLog(AzFramework::LogFile::SEV_NORMAL, window, message);
}
else
{
m_startupLogSink.push_back({ window, message });
}
return false;
}
void TraceLogger::WriteStartupLog(const AZStd::string& logFileName)
{
using namespace AzFramework;
AZ::IO::FileIOBase* fileIO = AZ::IO::FileIOBase::GetInstance();
AZ_Assert(fileIO != nullptr, "FileIO should be running at this point");
// There is no log system online so we have to create your own log file.
char resolveBuffer[AZ_MAX_PATH_LEN] = { 0 };
fileIO->ResolvePath("@user@", resolveBuffer, AZ_MAX_PATH_LEN);
// Note: @log@ hasn't been set at this point
AZStd::string logDirectory;
StringFunc::Path::Join(resolveBuffer, "log", logDirectory);
fileIO->SetAlias("@log@", logDirectory.c_str());
fileIO->CreatePath("@root@");
fileIO->CreatePath("@user@");
fileIO->CreatePath("@log@");
AZStd::string logPath;
StringFunc::Path::Join(logDirectory.c_str(), logFileName.c_str(), logPath);
m_logFile.reset(aznew LogFile(logPath.c_str()));
if (m_logFile)
{
m_logFile->SetMachineReadable(false);
for (const LogMessage& message : m_startupLogSink)
{
m_logFile->AppendLog(LogFile::SEV_NORMAL, message.window.c_str(), message.message.c_str());
}
m_startupLogSink = {};
m_logFile->FlushLog();
}
}
} // namespace AzToolsFramework
@@ -0,0 +1,43 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <AzCore/Debug/TraceMessageBus.h>
#include <AzFramework/Logging/LogFile.h>
#include <AzToolsFramework/API/EditorPythonConsoleBus.h>
#include <AzFramework/Asset/AssetSystemBus.h>
namespace AzToolsFramework
{
//! Connects and disconnects TraceMessageBus and allows for logging for O3DE Tools Applications
class TraceLogger
: public AZ::Debug::TraceMessageBus::Handler
{
public:
TraceLogger();
~TraceLogger();
//! Intalize logging for O3DEToolsApplications
void WriteStartupLog(const AZStd::string& logFileName);
protected:
//////////////////////////////////////////////////////////////////////////
// AZ::Debug::TraceMessageBus::Handler overrides...
bool OnOutput(const char* window, const char* message) override;
//////////////////////////////////////////////////////////////////////////
struct LogMessage
{
public:
AZStd::string window;
AZStd::string message;
};
AZStd::vector<LogMessage> m_startupLogSink;
AZStd::unique_ptr<AzFramework::LogFile> m_logFile;
};
} // namespace AzToolsFramework
@@ -237,7 +237,6 @@ namespace AzToolsFramework
if (m_containerEntity)
{
m_instanceEntityMapper->UnregisterEntity(m_containerEntity->GetId());
m_containerEntity.reset(aznew AZ::Entity());
RegisterEntity(m_containerEntity->GetId(), GenerateEntityAlias());
}
@@ -265,6 +264,11 @@ namespace AzToolsFramework
void Instance::ClearEntities()
{
if (m_containerEntity)
{
m_instanceEntityMapper->UnregisterEntity(m_containerEntity->GetId());
}
for (const auto&[entityAlias, entity] : m_entities)
{
if (entity)
@@ -168,7 +168,6 @@ namespace AzToolsFramework
if (instance->m_containerEntity)
{
instance->m_instanceEntityMapper->UnregisterEntity(instance->m_containerEntity->GetId());
instance->m_containerEntity.reset();
}
@@ -147,6 +147,8 @@ set(FILES
Entity/SliceEditorEntityOwnershipServiceBus.h
Fingerprinting/TypeFingerprinter.h
Fingerprinting/TypeFingerprinter.cpp
Logger/TraceLogger.cpp
Logger/TraceLogger.h
Manipulators/AngularManipulator.cpp
Manipulators/AngularManipulator.h
Manipulators/BaseManipulator.cpp
@@ -6,6 +6,7 @@
*
*/
#include <Atom/RHI/RHISystemInterface.h>
#include <Atom/RPI.Public/Image/AttachmentImagePool.h>
#include <Atom/RPI.Public/Image/ImageSystemInterface.h>
#include <Atom/RPI.Public/RenderPipeline.h>
@@ -44,6 +45,7 @@ namespace AZ
void DiffuseProbeGridRenderPass::FrameBeginInternal(FramePrepareParams params)
{
RHI::Ptr<RHI::Device> device = RHI::RHISystemInterface::Get()->GetDevice();
RPI::Scene* scene = m_pipeline->GetScene();
DiffuseProbeGridFeatureProcessor* diffuseProbeGridFeatureProcessor = scene->GetFeatureProcessor<DiffuseProbeGridFeatureProcessor>();
@@ -67,10 +69,13 @@ namespace AZ
Base::FrameBeginInternal(params);
for (auto& diffuseProbeGrid : diffuseProbeGridFeatureProcessor->GetRealTimeProbeGrids())
// process attachment readback for RealTime grids, if raytracing is supported on this device
if (device->GetFeatures().m_rayTracing)
{
// process attachment readback
diffuseProbeGrid->GetTextureReadback().FrameBegin(params);
for (auto& diffuseProbeGrid : diffuseProbeGridFeatureProcessor->GetRealTimeProbeGrids())
{
diffuseProbeGrid->GetTextureReadback().FrameBegin(params);
}
}
}
@@ -81,13 +86,7 @@ namespace AZ
for (auto& diffuseProbeGrid : diffuseProbeGridFeatureProcessor->GetProbeGrids())
{
if (diffuseProbeGrid->GetMode() == DiffuseProbeGridMode::Baked &&
!diffuseProbeGrid->HasValidBakedTextures())
{
continue;
}
if (!diffuseProbeGrid->GetIsVisible())
if (!ShouldRender(diffuseProbeGrid))
{
continue;
}
@@ -173,13 +172,7 @@ namespace AZ
for (auto& diffuseProbeGrid : diffuseProbeGridFeatureProcessor->GetProbeGrids())
{
if (diffuseProbeGrid->GetMode() == DiffuseProbeGridMode::Baked &&
!diffuseProbeGrid->HasValidBakedTextures())
{
continue;
}
if (!diffuseProbeGrid->GetIsVisible())
if (!ShouldRender(diffuseProbeGrid))
{
continue;
}
@@ -193,5 +186,33 @@ namespace AZ
Base::CompileResources(context);
}
bool DiffuseProbeGridRenderPass::ShouldRender(const AZStd::shared_ptr<DiffuseProbeGrid>& diffuseProbeGrid)
{
RHI::Ptr<RHI::Device> device = RHI::RHISystemInterface::Get()->GetDevice();
// check for baked mode with no valid textures
if (diffuseProbeGrid->GetMode() == DiffuseProbeGridMode::Baked &&
!diffuseProbeGrid->HasValidBakedTextures())
{
return false;
}
// check for RealTime mode without ray tracing
if (diffuseProbeGrid->GetMode() == DiffuseProbeGridMode::RealTime &&
!device->GetFeatures().m_rayTracing)
{
return false;
}
// check if culled out
if (!diffuseProbeGrid->GetIsVisible())
{
return false;
}
// DiffuseProbeGrid should be rendered
return true;
}
} // namespace Render
} // namespace AZ
@@ -14,6 +14,8 @@ namespace AZ
{
namespace Render
{
class DiffuseProbeGrid;
//! This pass renders the diffuse global illumination in the area covered by
//! each DiffuseProbeGrid.
class DiffuseProbeGridRenderPass final
@@ -40,6 +42,9 @@ namespace AZ
void SetupFrameGraphDependencies(RHI::FrameGraphInterface frameGraph) override;
void CompileResources(const RHI::FrameGraphCompileContext& context) override;
// helper function to determine if a DiffuseProbeGrid should be rendered based on its state
bool ShouldRender(const AZStd::shared_ptr<DiffuseProbeGrid>& diffuseProbeGrid);
Data::Instance<RPI::Shader> m_shader;
RHI::Ptr<RHI::ShaderResourceGroupLayout> m_srgLayout;
};
@@ -18,6 +18,10 @@ namespace AZ
{
namespace RPI
{
class Scene;
class RenderPipeline;
class RasterPass;
//! This class helps setup dynamic draw data as well as provide draw functions to draw dynamic items.
//! The draw calls added to the context are only valid for one frame.
//! DynamicDrawContext is only associated with
@@ -80,17 +84,20 @@ namespace AZ
//! This function can only be called before EndInit() is called
void AddDrawStateOptions(DrawStateOptions options);
//! Call any one of these functions to decide which scope this DynamicDrawContext may draw to.
//! One of the function has to be called once before EndInit() is called.
//! After DynamicDrawContext is initialized, the output scope can be changed.
//! But it has to be called after existing draw calls are submitted.
//! @Param scene Draw calls made with this DynamicDrawContext will be submit to this scene
//! @Param pipeline Draw calls made with this DynamicDrawContext will be submit to this render pipeline
//! @Param pass Draw calls made with this DynamicDrawContext will only be submit to this pass
void SetOutputScope(Scene* scene);
void SetOutputScope(RenderPipeline* pipeline);
void SetOutputScope(RasterPass* pass);
//! Finalize and validate initialization. Any initialization functions should be called before EndInit is called.
void EndInit();
//! Set up the DynamicDrawContext for the input Scene.
//! This should be called after the last frame is done and before any draw calls.
void SetScene(Scene* scene);
//! Set up the DynamicDrawContext for the input RenderPipeline.
//! This should be called after the last frame is done and before any draw calls.
void SetRenderPipeline(RenderPipeline* pipeline);
//! Return if this DynamicDrawContext is ready to add draw calls
bool IsReady();
@@ -177,14 +184,21 @@ namespace AZ
DynamicDrawContext() = default;
// Submit draw items to a view
void SubmitDrawData(ViewPtr view);
void SubmitDrawList(ViewPtr view);
// Finalize the draw list for all submiited draws.
void FinalizeDrawList();
RHI::DrawListView GetDrawList();
// Reset cached draw data when frame is end (draw data was submitted)
void FrameEnd();
void ReInit();
// Get rhi pipeline state which matches current states
const RHI::PipelineState* GetCurrentPipelineState();
struct MultiStates
{
// states available for change
@@ -230,10 +244,22 @@ namespace AZ
// Draw variations allowed in this DynamicDrawContext
DrawStateOptions m_drawStateOptions;
// For generate output attachment layout and filter draw items
Scene* m_scene = nullptr;
// DrawListTag used to help setup PipelineState's output
// and also for submitting draw items to views
RHI::DrawListTag m_drawListTag;
// Output scope related
enum class OutputScopeType
{
Unset,
Scene,
RenderPipeline,
RasterPass
};
Scene* m_scene = nullptr;
RasterPass* m_pass = nullptr;
OutputScopeType m_outputScope = OutputScopeType::Unset;
// All draw items use this filter when submit them to views
// It's set to RenderPipeline's draw filter mask if the DynamicDrawContext was created for a render pipeline.
RHI::DrawFilterMask m_drawFilter = RHI::DrawFilterMaskDefaultValue;
@@ -242,19 +268,22 @@ namespace AZ
AZStd::vector<RHI::StreamBufferView> m_cachedStreamBufferViews;
AZStd::vector<RHI::IndexBufferView> m_cachedIndexBufferViews;
AZStd::vector<Data::Instance<ShaderResourceGroup>> m_cachedDrawSrg;
// structure includes DrawItem and stream and index buffer index
static const uint32_t InvalidIndex = static_cast<uint32_t>(-1);
using BufferViewIndexType = uint32_t;
static const BufferViewIndexType InvalidIndex = static_cast<BufferViewIndexType>(-1);
struct DrawItemInfo
{
RHI::DrawItem m_drawItem;
RHI::DrawItemSortKey m_sortKey;
uint32_t m_vertexBufferViewIndex = InvalidIndex;
uint32_t m_indexBufferViewIndex = InvalidIndex;
BufferViewIndexType m_vertexBufferViewIndex = InvalidIndex;
BufferViewIndexType m_indexBufferViewIndex = InvalidIndex;
};
AZStd::vector<DrawItemInfo> m_cachedDrawItems;
// Cached draw list for render to rasterpass
RHI::DrawList m_cachedDrawList;
// Flags if this DynamicDrawContext can change shader variants
bool m_supportShaderVariants = false;
ShaderVariantId m_currentShaderVariantId;
@@ -266,6 +295,8 @@ namespace AZ
bool m_initialized = false;
RHI::DrawItemSortKey m_sortKey = 0;
bool m_drawFinalized = false;
};
AZ_DEFINE_ENUM_BITWISE_OPERATORS(AZ::RPI::DynamicDrawContext::DrawStateOptions);
@@ -48,15 +48,9 @@ namespace AZ
AZ_DISABLE_COPY_MOVE(DynamicDrawInterface);
//! Create a DynamicDrawContext for specified scene (and render pipeline).
//! Draw calls which are made to this DynamicDrawContext will only be submitted for this scene.
//! Create a DynamicDrawContext
//! The created DynamicDrawContext is managed by dynamic draw system.
virtual RHI::Ptr<DynamicDrawContext> CreateDynamicDrawContext(Scene* scene) = 0;
//! Create a DynamicDrawContext for specified render pipeline
//! Draw calls submitted through the context created by this function are only submitted
//! to the supplied render pipeline (viewport)
virtual RHI::Ptr<DynamicDrawContext> CreateDynamicDrawContext(RenderPipeline* pipeline) = 0;
virtual RHI::Ptr<DynamicDrawContext> CreateDynamicDrawContext() = 0;
//! Get a DynamicBuffer from DynamicDrawSystem.
//! The returned buffer will be invalidated every time the RPISystem's RenderTick is called
@@ -69,6 +63,9 @@ namespace AZ
//! Note that ownership of the DrawPacket pointer is passed to the dynamic draw system.
//! (it will be cleaned up correctly since the DrawPacket keeps track of the allocator that was used when it was built)
virtual void AddDrawPacket(Scene* scene, AZStd::unique_ptr<const RHI::DrawPacket> drawPacket) = 0;
//! Get DrawLists from any DynamicDrawContext which output to the specified RasterPass.
virtual AZStd::vector<RHI::DrawListView> GetDrawListsForPass(const RasterPass* pass) = 0;
};
//! Global function to query the DynamicDrawInterface.
@@ -31,14 +31,15 @@ namespace AZ
void Shutdown();
// DynamicDrawInterface overrides...
RHI::Ptr<DynamicDrawContext> CreateDynamicDrawContext(Scene* scene) override;
RHI::Ptr<DynamicDrawContext> CreateDynamicDrawContext(RenderPipeline* pipeline) override;
RHI::Ptr<DynamicDrawContext> CreateDynamicDrawContext() override;
RHI::Ptr<DynamicBuffer> GetDynamicBuffer(uint32_t size, uint32_t alignment = 1) override;
void DrawGeometry(Data::Instance<Material> material, const GeometryData& geometry, ScenePtr scene) override;
void AddDrawPacket(Scene* scene, AZStd::unique_ptr<const RHI::DrawPacket> drawPacket) override;
AZStd::vector<RHI::DrawListView> GetDrawListsForPass(const RasterPass* pass) override;
// Submit draw data for selected scene and pipeline
void SubmitDrawData(Scene* scene, AZStd::vector<ViewPtr> views);
protected:
void FrameEnd();
@@ -55,6 +55,9 @@ namespace AZ
void CompileResources(const RHI::FrameGraphCompileContext& context) override;
void BuildCommandListInternal(const RHI::FrameGraphExecuteContext& context) override;
// Retrieve draw lists from view and dynamic draw system and generate final draw list
void UpdateDrawList();
// The draw list tag used to fetch the draw list from the views
RHI::DrawListTag m_drawListTag;
@@ -62,8 +65,12 @@ namespace AZ
// This is the index of the pipeline state data that corresponds to this pass in the array of pipeline state data
RHI::Handle<> m_pipelineStateDataIndex;
// The draw list returned from the view
// The reference of the draw list to be drawn
RHI::DrawListView m_drawListView;
// If there are more than one draw lists from different source: View, DynamicDrawSystem,
// we need to creates a combined draw list which combines all the draw lists to one and cache it until they are submitted.
RHI::DrawList m_combinedDrawList;
RHI::Scissor m_scissorState;
RHI::Viewport m_viewportState;
@@ -13,6 +13,7 @@
#include <Atom/RPI.Public/DynamicDraw/DynamicBuffer.h>
#include <Atom/RPI.Public/DynamicDraw/DynamicDrawContext.h>
#include <Atom/RPI.Public/DynamicDraw/DynamicDrawInterface.h>
#include <Atom/RPI.Public/Pass/RasterPass.h>
#include <Atom/RPI.Public/RenderPipeline.h>
#include <Atom/RPI.Public/View.h>
@@ -173,9 +174,7 @@ namespace AZ
void DynamicDrawContext::EndInit()
{
AZ_Assert(m_scene != nullptr, "DynamicDrawContext should always belong to a scene");
AZ_Warning("RPI", m_pipelineState, "Failed to initialized shader for DynamicDrawContext");
AZ_Warning("RPI", m_pipelineState, "Failed to initialize shader for DynamicDrawContext");
AZ_Warning("RPI", m_drawListTag.IsValid(), "DynamicDrawContext doesn't have a valid DrawListTag");
if (!m_drawListTag.IsValid() || m_pipelineState == nullptr)
@@ -183,8 +182,27 @@ namespace AZ
return;
}
m_pipelineState->SetOutputFromScene(m_scene, m_drawListTag);
m_pipelineState->Finalize();
if (m_outputScope == OutputScopeType::RenderPipeline || m_outputScope == OutputScopeType::Scene)
{
m_pipelineState->SetOutputFromScene(m_scene, m_drawListTag);
}
else if (m_outputScope == OutputScopeType::RasterPass)
{
m_pipelineState->SetOutputFromPass(m_pass);
}
else
{
AZ_Assert(false, "DynamicDrawContext need to set output scope before end initialization");
return;
}
m_rhiPipelineState = m_pipelineState->Finalize();
if (!m_rhiPipelineState)
{
AZ_Warning("RPI", false, "Failed to initialize PipelineState for DynamicDrawContext");
return;
}
m_initialized = true;
// Acquire MultiStates from m_pipelineState
@@ -199,16 +217,58 @@ namespace AZ
m_rhiPipelineState = m_pipelineState->GetRHIPipelineState();
}
void DynamicDrawContext::SetScene(Scene* scene)
void DynamicDrawContext::SetOutputScope(Scene* scene)
{
AZ_Assert(scene, "SetScene called with an invalid scene");
if (!scene || m_scene == scene)
AZ_Assert(scene, "SetOutputScope was called with an invalid Scene");
if (!scene)
{
return;
}
m_outputScope = OutputScopeType::Scene;
m_scene = scene;
m_pass = nullptr;
m_drawFilter = RHI::DrawFilterMaskDefaultValue;
// Reinitialize if it was initialized
ReInit();
}
void DynamicDrawContext::SetOutputScope(RenderPipeline* pipeline)
{
AZ_Assert(pipeline, "SetOutputScope was called with an invalid RenderPipeline");
AZ_Assert(pipeline->GetScene(), "SetOutputScope called with a RenderPipeline without adding to a scene");
if (!pipeline || !pipeline->GetScene())
{
return;
}
m_outputScope = OutputScopeType::RenderPipeline;
m_scene = pipeline->GetScene();
m_pass = nullptr;
m_drawFilter = pipeline->GetDrawFilterMask();
ReInit();
}
void DynamicDrawContext::SetOutputScope(RasterPass* pass)
{
AZ_Assert(pass, "SetOutputScope was called with an invalid RasterPass");
if (!pass)
{
return;
}
m_outputScope = OutputScopeType::RasterPass;
m_scene = nullptr;
m_pass = pass;
m_drawFilter = RHI::DrawFilterMaskDefaultValue;
ReInit();
}
void DynamicDrawContext::ReInit()
{
// Reinitialize if it was initialized
if (m_initialized)
{
// Report warning if there were some draw data
@@ -224,17 +284,6 @@ namespace AZ
}
}
void DynamicDrawContext::SetRenderPipeline(RenderPipeline* pipeline)
{
AZ_Assert(pipeline, "SetRenderPipeline called with an invalid pipeline");
if (!pipeline)
{
return;
}
SetScene(pipeline->GetScene());
m_drawFilter = pipeline->GetDrawFilterMask();
}
bool DynamicDrawContext::IsReady()
{
return m_initialized;
@@ -402,10 +451,16 @@ namespace AZ
AZ_Assert(false, "DynamicDrawContext isn't initialized");
return;
}
if (!m_drawSrgLayout)
if (m_drawFinalized)
{
AZ_Assert(false, "PerDrawSrg need to be provided since the shader uses it");
AZ_Assert(false, "Can't add draw calls after draw data was finalized");
return;
}
if (m_drawSrgLayout && !drawSrg)
{
AZ_Assert(false, "drawSrg need to be provided since the shader requires it");
return;
}
@@ -446,12 +501,12 @@ namespace AZ
vertexBuffer->Write(vertexData, vertexDataSize);
m_cachedStreamBufferViews.push_back(vertexBuffer->GetStreamBufferView(m_perVertexDataSize));
drawItem.m_streamBufferViewCount = 1;
drawItemInfo.m_vertexBufferViewIndex = uint32_t(m_cachedStreamBufferViews.size() - 1);
drawItemInfo.m_vertexBufferViewIndex = static_cast<BufferViewIndexType>(m_cachedStreamBufferViews.size() - 1);
// Write data to index buffer and set up index buffer view for DrawItem
indexBuffer->Write(indexData, indexDataSize);
m_cachedIndexBufferViews.push_back(indexBuffer->GetIndexBufferView(indexFormat));
drawItemInfo.m_indexBufferViewIndex = uint32_t(m_cachedIndexBufferViews.size() - 1);
drawItemInfo.m_indexBufferViewIndex = static_cast<BufferViewIndexType>(m_cachedIndexBufferViews.size() - 1);
// Setup per context srg if it exists
if (m_srgPerContext)
@@ -487,7 +542,7 @@ namespace AZ
drawItemInfo.m_sortKey = m_sortKey++;
m_cachedDrawItems.emplace_back(drawItemInfo);
}
void DynamicDrawContext::DrawLinear(const void* vertexData, uint32_t vertexCount, Data::Instance<ShaderResourceGroup> drawSrg)
{
if (!m_initialized)
@@ -496,9 +551,15 @@ namespace AZ
return;
}
if (!m_drawSrgLayout)
if (m_drawFinalized)
{
AZ_Assert(false, "PerDrawSrg need to be provided since the shader uses it");
AZ_Assert(false, "Can't add draw calls after draw data was finalized");
return;
}
if (m_drawSrgLayout && !drawSrg)
{
AZ_Assert(false, "drawSrg need to be provided since the shader requires it");
return;
}
@@ -569,7 +630,6 @@ namespace AZ
m_cachedDrawItems.emplace_back(drawItemInfo);
}
Data::Instance<ShaderResourceGroup> DynamicDrawContext::NewDrawSrg()
{
if (!m_drawSrgLayout)
@@ -626,18 +686,14 @@ namespace AZ
return m_sortKey;
}
void DynamicDrawContext::SubmitDrawData(ViewPtr view)
void DynamicDrawContext::FinalizeDrawList()
{
if (!m_initialized)
if (m_drawFinalized)
{
return;
}
AZ_Assert(m_cachedDrawList.size() == 0, "m_cachedDrawList should be cleared ine the end of last frame ");
if (!view->HasDrawListTag(m_drawListTag))
{
return;
}
for (auto& drawItemInfo : m_cachedDrawItems)
{
if (drawItemInfo.m_indexBufferViewIndex != InvalidIndex)
@@ -654,10 +710,34 @@ namespace AZ
drawItemProperties.m_sortKey = drawItemInfo.m_sortKey;
drawItemProperties.m_item = &drawItemInfo.m_drawItem;
drawItemProperties.m_drawFilterMask = m_drawFilter;
m_cachedDrawList.emplace_back(drawItemProperties);
}
m_drawFinalized = true;
}
void DynamicDrawContext::SubmitDrawList(ViewPtr view)
{
if (!m_initialized || m_outputScope == OutputScopeType::RasterPass)
{
return;
}
if (!view->HasDrawListTag(m_drawListTag))
{
return;
}
for (auto& drawItemProperties : m_cachedDrawList)
{
view->AddDrawItem(m_drawListTag, drawItemProperties);
}
}
RHI::DrawListView DynamicDrawContext::GetDrawList()
{
return m_cachedDrawList;
}
void DynamicDrawContext::FrameEnd()
{
m_sortKey = 0;
@@ -665,6 +745,8 @@ namespace AZ
m_cachedStreamBufferViews.clear();
m_cachedIndexBufferViews.clear();
m_cachedDrawSrg.clear();
m_cachedDrawList.clear();
m_drawFinalized = false;
}
const RHI::PipelineState* DynamicDrawContext::GetCurrentPipelineState()
@@ -53,34 +53,14 @@ namespace AZ
return m_bufferAlloc->Allocate(size, alignment);
}
RHI::Ptr<DynamicDrawContext> DynamicDrawSystem::CreateDynamicDrawContext(Scene* scene)
RHI::Ptr<DynamicDrawContext> DynamicDrawSystem::CreateDynamicDrawContext()
{
if (!scene)
{
AZ_Error("RPI", false, "Failed to create a DynamicDrawContext: the input scene is invalid");
return nullptr;
}
RHI::Ptr<DynamicDrawContext> drawContext = aznew DynamicDrawContext();
drawContext->m_scene = scene;
AZStd::lock_guard<AZStd::mutex> lock(m_mutexDrawContext);
m_dynamicDrawContexts.push_back(drawContext);
return drawContext;
}
RHI::Ptr<DynamicDrawContext> DynamicDrawSystem::CreateDynamicDrawContext(RenderPipeline* pipeline)
{
if (!pipeline || !pipeline->GetScene())
{
AZ_Error("RPI", false, "Failed to create a DynamicDrawContext: the input RenderPipeline is invalid or wasn't added to a Scene");
return nullptr;
}
auto context = CreateDynamicDrawContext(pipeline->GetScene());
context->m_drawFilter = pipeline->GetDrawFilterMask();
return context;
}
// [GFX TODO][ATOM-13184] Add support of draw geometry with material for DynamicDrawSystemInterface
void DynamicDrawSystem::DrawGeometry([[maybe_unused]] Data::Instance<Material> material, [[maybe_unused]] const GeometryData& geometry, [[maybe_unused]] ScenePtr scene)
{
@@ -101,9 +81,10 @@ namespace AZ
{
if (drawContext->m_scene == scene)
{
drawContext->FinalizeDrawList();
for (auto& view : views)
{
drawContext->SubmitDrawData(view);
drawContext->SubmitDrawList(view);
}
}
}
@@ -121,6 +102,25 @@ namespace AZ
}
}
AZStd::vector<RHI::DrawListView> DynamicDrawSystem::GetDrawListsForPass(const RasterPass* pass)
{
AZStd::vector<RHI::DrawListView> result;
AZStd::lock_guard<AZStd::mutex> lock(m_mutexDrawContext);
for (RHI::Ptr<DynamicDrawContext> drawContext : m_dynamicDrawContexts)
{
if (drawContext->m_pass == pass)
{
drawContext->FinalizeDrawList();
auto drawListView = drawContext->GetDrawList();
if (drawListView.size() > 0)
{
result.push_back(drawListView);
}
}
}
return result;
}
void DynamicDrawSystem::FrameEnd()
{
{
@@ -7,17 +7,18 @@
*/
#include <Atom/RHI/CommandList.h>
#include <Atom/RHI/ShaderResourceGroup.h>
#include <Atom/RHI/DrawListTagRegistry.h>
#include <Atom/RPI.Reflect/Asset/AssetUtils.h>
#include <Atom/RPI.Public/RenderPipeline.h>
#include <Atom/RHI/RHISystemInterface.h>
#include <Atom/RHI/ShaderResourceGroup.h>
#include <Atom/RPI.Public/DynamicDraw/DynamicDrawInterface.h>
#include <Atom/RPI.Public/Pass/RasterPass.h>
#include <Atom/RPI.Public/RenderPipeline.h>
#include <Atom/RPI.Public/RPISystemInterface.h>
#include <Atom/RPI.Public/Scene.h>
#include <Atom/RPI.Public/View.h>
#include <Atom/RPI.Public/Pass/RasterPass.h>
#include <Atom/RPI.Reflect/Asset/AssetUtils.h>
#include <Atom/RPI.Reflect/Pass/RasterPassData.h>
namespace AZ
@@ -133,10 +134,19 @@ namespace AZ
m_viewportState = params.m_viewportState;
}
// -- View & DrawList --
const AZStd::vector<ViewPtr>& views = m_pipeline->GetViews(GetPipelineViewTag());
m_drawListView = {};
UpdateDrawList();
RenderPass::FrameBeginInternal(params);
}
void RasterPass::UpdateDrawList()
{
// DrawLists from dynamic draw
AZStd::vector<RHI::DrawListView> drawLists = DynamicDrawInterface::Get()->GetDrawListsForPass(this);
// Get DrawList from view
const AZStd::vector<ViewPtr>& views = m_pipeline->GetViews(GetPipelineViewTag());
RHI::DrawListView viewDrawList;
if (!views.empty())
{
const ViewPtr& view = views.front();
@@ -144,11 +154,40 @@ namespace AZ
// Assert the view has our draw list (the view's DrawlistTags are collected from passes using its viewTag)
AZ_Assert(view->HasDrawListTag(m_drawListTag), "View's DrawListTags out of sync with pass'. ");
// Draw List
m_drawListView = view->GetDrawList(m_drawListTag);
viewDrawList = view->GetDrawList(m_drawListTag);
}
RenderPass::FrameBeginInternal(params);
// clean up data
m_drawListView = {};
m_combinedDrawList.clear();
// draw list from view was sorted and if it's the only draw list then we can use it directly
if (viewDrawList.size() > 0 && drawLists.size() == 0)
{
m_drawListView = viewDrawList;
return;
}
// add view's draw list to drawLists too
drawLists.push_back(viewDrawList);
// combine draw items from mutiple draw lists to one draw list and sort it.
size_t itemCount = 0;
for (auto drawList : drawLists)
{
itemCount += drawList.size();
}
m_combinedDrawList.resize(itemCount);
RHI::DrawItemProperties* currentBuffer = m_combinedDrawList.data();
for (auto drawList : drawLists)
{
memcpy(currentBuffer, drawList.data(), drawList.size()*sizeof(RHI::DrawItemProperties));
currentBuffer += drawList.size();
}
SortDrawList(m_combinedDrawList);
// have the final draw list point to the combined draw list.
m_drawListView = m_combinedDrawList;
}
// --- DrawList and PipelineView Tags ---
@@ -73,9 +73,9 @@ namespace MaterialEditor
MaterialEditorApplication::MaterialEditorApplication(int* argc, char*** argv)
: Application(argc, argv)
, QApplication(*argc, *argv)
, AzQtApplication(*argc, *argv)
{
AZ::Debug::TraceMessageBus::Handler::BusConnect();
QApplication::setApplicationName("O3DE Material Editor");
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddBuildSystemTargetSpecialization(
*AZ::SettingsRegistry::Get(), GetBuildTargetName());
@@ -89,10 +89,9 @@ namespace MaterialEditor
MaterialEditorApplication::~MaterialEditorApplication()
{
AzToolsFramework::EditorPythonConsoleNotificationBus::Handler::BusDisconnect();
AzToolsFramework::AssetDatabase::AssetDatabaseRequestsBus::Handler::BusDisconnect();
MaterialEditorWindowNotificationBus::Handler::BusDisconnect();
AZ::Debug::TraceMessageBus::Handler::BusDisconnect();
AzToolsFramework::EditorPythonConsoleNotificationBus::Handler::BusDisconnect();
}
void MaterialEditorApplication::CreateReflectionManager()
@@ -357,42 +356,6 @@ namespace MaterialEditor
}
}
void MaterialEditorApplication::WriteStartupLog()
{
using namespace AzFramework;
AZ::IO::FileIOBase* fileIO = AZ::IO::FileIOBase::GetInstance();
AZ_Assert(fileIO != nullptr, "FileIO should be running at this point");
// There is no log system online so we have to create your own log file.
char resolveBuffer[AZ_MAX_PATH_LEN] = { 0 };
fileIO->ResolvePath("@user@", resolveBuffer, AZ_MAX_PATH_LEN);
// Note: @log@ hasn't been set at this point
AZStd::string logDirectory;
StringFunc::Path::Join(resolveBuffer, "log", logDirectory);
fileIO->SetAlias("@log@", logDirectory.c_str());
fileIO->CreatePath("@root@");
fileIO->CreatePath("@user@");
fileIO->CreatePath("@log@");
AZStd::string logPath;
StringFunc::Path::Join(logDirectory.c_str(), "MaterialEditor.log", logPath);
m_logFile.reset(aznew LogFile(logPath.c_str()));
if (m_logFile)
{
m_logFile->SetMachineReadable(false);
for (const LogMessage& message : m_startupLogSink)
{
m_logFile->AppendLog(LogFile::SEV_NORMAL, message.window.c_str(), message.message.c_str());
}
m_startupLogSink = {};
m_logFile->FlushLog();
}
}
void MaterialEditorApplication::LoadSettings()
{
AZ::SerializeContext* context = nullptr;
@@ -480,7 +443,7 @@ namespace MaterialEditor
return;
}
WriteStartupLog();
m_traceLogger.WriteStartupLog("MaterialEditor.log");
if (!LaunchDiscoveryService())
{
@@ -21,8 +21,9 @@
#include <AzFramework/Logging/LogFile.h>
#include <AzToolsFramework/API/AssetDatabaseBus.h>
#include <AzToolsFramework/API/EditorPythonConsoleBus.h>
#include <AzToolsFramework/Logger/TraceLogger.h>
#include <AzQtComponents/Application/AzQtApplication.h>
#include <QApplication>
#include <QTimer>
namespace MaterialEditor
@@ -31,7 +32,7 @@ namespace MaterialEditor
class MaterialEditorApplication
: public AzFramework::Application
, public QApplication
, public AzQtComponents::AzQtApplication
, private AzToolsFramework::AssetDatabase::AssetDatabaseRequestsBus::Handler
, private MaterialEditorWindowNotificationBus::Handler
, private AzFramework::AssetSystemStatusBus::Handler
@@ -100,12 +101,11 @@ namespace MaterialEditor
//////////////////////////////////////////////////////////////////////////
// AZ::Debug::TraceMessageBus::Handler overrides...
bool OnOutput(const char* window, const char* message) override;
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
void CompileCriticalAssets();
void ProcessCommandLine(const AZ::CommandLine& commandLine);
void WriteStartupLog();
void LoadSettings();
void UnloadSettings();
@@ -125,6 +125,8 @@ namespace MaterialEditor
AZStd::vector<LogMessage> m_startupLogSink;
AZStd::unique_ptr<AzFramework::LogFile> m_logFile;
AzToolsFramework::TraceLogger m_traceLogger;
//! Local user settings are used to store material browser tree expansion state
AZ::UserSettingsProvider m_localUserSettings;
@@ -25,21 +25,7 @@
int main(int argc, char** argv)
{
QApplication::setOrganizationName("O3DE");
QApplication::setOrganizationDomain("o3de.org");
QApplication::setApplicationName("O3DE Material Editor");
AzQtComponents::PrepareQtPaths();
QLocale::setDefault(QLocale(QLocale::English, QLocale::UnitedStates));
// Must be set before QApplication is initialized, so that we support HighDpi monitors, like the Retina displays
// on Windows 10
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
QCoreApplication::setAttribute(Qt::AA_DontCreateNativeWidgetSiblings);
QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough);
AzQtComponents::Utilities::HandleDpiAwareness(AzQtComponents::Utilities::SystemDpiAware);
AzQtComponents::AzQtApplication::InitializeDpiScaling();
MaterialEditor::MaterialEditorApplication app(&argc, &argv);
@@ -69,8 +69,10 @@ namespace ShaderManagementConsole
ShaderManagementConsoleApplication::ShaderManagementConsoleApplication(int* argc, char*** argv)
: Application(argc, argv)
, QApplication(*argc, *argv)
, AzQtApplication(*argc, *argv)
{
QApplication::setApplicationName("O3DE Shader Management Console");
// The settings registry has been created at this point, so add the CMake target
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddBuildSystemTargetSpecialization(
*AZ::SettingsRegistry::Get(), GetBuildTargetName());
@@ -151,7 +153,6 @@ namespace ShaderManagementConsole
{
AzFramework::AssetSystemStatusBus::Handler::BusConnect();
AzToolsFramework::EditorPythonConsoleNotificationBus::Handler::BusConnect();
AZ::Debug::TraceMessageBus::Handler::BusConnect();
AzFramework::Application::StartCommon(systemEntity);
@@ -163,7 +164,6 @@ namespace ShaderManagementConsole
void ShaderManagementConsoleApplication::OnShaderManagementConsoleWindowClosing()
{
ExitMainLoop();
AZ::Debug::TraceMessageBus::Handler::BusDisconnect();
ShaderManagementConsoleWindowNotificationBus::Handler::BusDisconnect();
AzToolsFramework::EditorPythonConsoleNotificationBus::Handler::BusDisconnect();
}
@@ -344,6 +344,8 @@ namespace ShaderManagementConsole
return;
}
m_traceLogger.WriteStartupLog("ShaderManagementConsole.log");
//[GFX TODO][ATOM-415] Try to factor out some of this stuff with AtomSampleViewerApplication
AzToolsFramework::AssetDatabase::AssetDatabaseRequestsBus::Handler::BusConnect();
AzToolsFramework::AssetBrowser::AssetDatabaseLocationNotificationBus::Broadcast(&AzToolsFramework::AssetBrowser::AssetDatabaseLocationNotifications::OnDatabaseInitialized);
@@ -8,7 +8,6 @@
#pragma once
#include <AzCore/Component/Entity.h>
#include <AzCore/Component/TickBus.h>
#include <AzCore/UserSettings/UserSettingsProvider.h>
@@ -19,18 +18,20 @@
#include <AzToolsFramework/API/AssetDatabaseBus.h>
#include <AzToolsFramework/API/EditorPythonConsoleBus.h>
#include <AzToolsFramework/Logger/TraceLogger.h>
#include <Atom/Document/ShaderManagementConsoleDocumentSystemRequestBus.h>
#include <Atom/Window/ShaderManagementConsoleWindowNotificationBus.h>
#include <QApplication>
#include <AzQtComponents/Application/AzQtApplication.h>
#include <QTimer>
namespace ShaderManagementConsole
{
class ShaderManagementConsoleApplication
: public AzFramework::Application
, public QApplication
, public AzQtComponents::AzQtApplication
, private AzToolsFramework::AssetDatabase::AssetDatabaseRequestsBus::Handler
, private ShaderManagementConsoleWindowNotificationBus::Handler
, private AzFramework::AssetSystemStatusBus::Handler
@@ -114,6 +115,8 @@ namespace ShaderManagementConsole
static void PyIdleWaitFrames(uint32_t frames);
AzToolsFramework::TraceLogger m_traceLogger;
//! Local user settings are used to store asset browser tree expansion state
AZ::UserSettingsProvider m_localUserSettings;
@@ -16,6 +16,7 @@
#include <AzQtComponents/Utilities/QtPluginPaths.h>
#include <AzQtComponents/Utilities/HandleDpiAwareness.h>
#include <AzQtComponents/Components/WindowDecorationWrapper.h>
#include <AzQtComponents/Application/AzQtApplication.h>
#include <QtWidgets/QApplication>
#include <QtGui/private/qhighdpiscaling_p.h>
@@ -24,23 +25,10 @@
int main(int argc, char** argv)
{
QApplication::setOrganizationName("O3DE");
QApplication::setOrganizationDomain("o3de.com");
QApplication::setApplicationName("O3DE Shader Management Console");
AzQtComponents::PrepareQtPaths();
QLocale::setDefault(QLocale(QLocale::English, QLocale::UnitedStates));
// Must be set before QApplication is initialized, so that we support HighDpi monitors, like the Retina displays
// on Windows 10
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
QCoreApplication::setAttribute(Qt::AA_DontCreateNativeWidgetSiblings);
QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough);
AzQtComponents::Utilities::HandleDpiAwareness(AzQtComponents::Utilities::PerScreenDpiAware);
AzQtComponents::AzQtApplication::InitializeDpiScaling();
ShaderManagementConsole::ShaderManagementConsoleApplication app(&argc, &argv);
AZ::IO::FixedMaxPath engineRootPath;
if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr)
{
@@ -83,7 +83,7 @@ namespace AZ::AtomBridge
ViewportData& viewportData = m_viewportData[viewportId];
for (auto& context : viewportData.m_dynamicDrawContexts)
{
context.second->SetRenderPipeline(pipeline.get());
context.second->SetOutputScope(pipeline.get());
}
});
viewportData.m_viewportDestroyedHandler = AZ::Event<AzFramework::ViewportId>::Handler([this, viewportId](AzFramework::ViewportId id)
@@ -106,7 +106,8 @@ namespace AZ::AtomBridge
{
return nullptr;
}
context = RPI::DynamicDrawInterface::Get()->CreateDynamicDrawContext(pipeline);
context = RPI::DynamicDrawInterface::Get()->CreateDynamicDrawContext();
context->SetOutputScope(pipeline);
contextFactoryIt->second(context);
}
@@ -1731,6 +1731,14 @@ void AZ::FFont::DrawScreenAlignedText3d(
currentView->GetWorldToViewMatrix(),
currentView->GetViewToClipMatrix()
);
// Text behind the camera shouldn't get rendered. WorldToScreenNDC returns values in the range 0 - 1, so Z < 0.5 is behind the screen
// and >= 0.5 is in front of the screen.
if (positionNDC.GetZ() < 0.5f)
{
return;
}
internalParams.m_ctx.m_sizeIn800x600 = false;
DrawStringUInternal(
@@ -1,9 +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
#
#
add_subdirectory(Code)
@@ -1,45 +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
#
#
ly_add_target(
NAME AutomatedLauncherTesting.Static STATIC
NAMESPACE Gem
FILES_CMAKE
automatedlaunchertesting_files.cmake
INCLUDE_DIRECTORIES
PRIVATE
Source
PUBLIC
Include
BUILD_DEPENDENCIES
PUBLIC
AZ::AzCore
Legacy::CryCommon
Gem::LmbrCentral
)
ly_add_target(
NAME AutomatedLauncherTesting ${PAL_TRAIT_MONOLITHIC_DRIVEN_MODULE_TYPE}
NAMESPACE Gem
FILES_CMAKE
automatedlaunchertesting_shared_files.cmake
INCLUDE_DIRECTORIES
PRIVATE
Source
PUBLIC
Include
BUILD_DEPENDENCIES
PRIVATE
Gem::AutomatedLauncherTesting.Static
RUNTIME_DEPENDENCIES
Gem::LmbrCentral
)
# servers and clients use the above module.
ly_create_alias(NAME AutomatedLauncherTesting.Servers NAMESPACE Gem TARGETS Gem::AutomatedLauncherTesting)
ly_create_alias(NAME AutomatedLauncherTesting.Clients NAMESPACE Gem TARGETS Gem::AutomatedLauncherTesting)
@@ -1,28 +0,0 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <AzCore/EBus/EBus.h>
namespace AutomatedLauncherTesting
{
class AutomatedLauncherTestingRequests
: public AZ::EBusTraits
{
public:
//////////////////////////////////////////////////////////////////////////
// EBusTraits overrides
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
//////////////////////////////////////////////////////////////////////////
// Call this method from your test logic when a test is complete.
virtual void CompleteTest(bool success, const AZStd::string& message) = 0;
};
using AutomatedLauncherTestingRequestBus = AZ::EBus<AutomatedLauncherTestingRequests>;
} // namespace AutomatedLauncherTesting
@@ -1,48 +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 <AzCore/Memory/SystemAllocator.h>
#include <AzCore/Module/Module.h>
#include <AutomatedLauncherTestingSystemComponent.h>
namespace AutomatedLauncherTesting
{
class AutomatedLauncherTestingModule
: public AZ::Module
{
public:
AZ_RTTI(AutomatedLauncherTestingModule, "{3FC3E44A-0AC0-47C5-BD02-ADB2BA4338CA}", AZ::Module);
AZ_CLASS_ALLOCATOR(AutomatedLauncherTestingModule, AZ::SystemAllocator, 0);
AutomatedLauncherTestingModule()
: AZ::Module()
{
// Push results of [MyComponent]::CreateDescriptor() into m_descriptors here.
m_descriptors.insert(m_descriptors.end(), {
AutomatedLauncherTestingSystemComponent::CreateDescriptor(),
});
}
/**
* Add required SystemComponents to the SystemEntity.
*/
AZ::ComponentTypeList GetRequiredSystemComponents() const override
{
return AZ::ComponentTypeList{
azrtti_typeid<AutomatedLauncherTestingSystemComponent>(),
};
}
};
}
// DO NOT MODIFY THIS LINE UNLESS YOU RENAME THE GEM
// The first parameter should be GemName_GemIdLower
// The second should be the fully qualified name of the class above
AZ_DECLARE_MODULE_CLASS(Gem_AutomatedLauncherTesting, AutomatedLauncherTesting::AutomatedLauncherTestingModule)
@@ -1,231 +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 <AutomatedLauncherTestingSystemComponent.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/Serialization/EditContextConstants.inl>
#include <AzCore/RTTI/BehaviorContext.h>
#include <AzFramework/StringFunc/StringFunc.h>
#include <CryCommon/platform.h>
#include <CryCommon/ICmdLine.h>
#include <CryCommon/IConsole.h>
#include <CryCommon/ISystem.h>
#include "SpawnDynamicSlice.h"
#include <AzCore/Component/Entity.h>
namespace AutomatedLauncherTesting
{
void AutomatedLauncherTestingSystemComponent::Reflect(AZ::ReflectContext* context)
{
if (AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context))
{
serialize->Class<AutomatedLauncherTestingSystemComponent, AZ::Component>()
->Version(0);
if (AZ::EditContext* ec = serialize->GetEditContext())
{
ec->Class<AutomatedLauncherTestingSystemComponent>("AutomatedLauncherTesting", "[Description of functionality provided by this System Component]")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("System"))
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
;
}
}
if (AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
{
behaviorContext->EBus<AutomatedLauncherTestingRequestBus>("AutomatedLauncherTestingRequestBus")
->Attribute(AZ::Script::Attributes::Category, "Testing")
->Event("CompleteTest", &AutomatedLauncherTestingRequestBus::Events::CompleteTest)
;
}
}
void AutomatedLauncherTestingSystemComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
{
provided.push_back(AZ_CRC("AutomatedLauncherTestingService"));
}
void AutomatedLauncherTestingSystemComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
{
incompatible.push_back(AZ_CRC("AutomatedLauncherTestingService"));
}
void AutomatedLauncherTestingSystemComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required)
{
AZ_UNUSED(required);
}
void AutomatedLauncherTestingSystemComponent::GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent)
{
AZ_UNUSED(dependent);
}
void AutomatedLauncherTestingSystemComponent::Init()
{
}
void AutomatedLauncherTestingSystemComponent::Activate()
{
AutomatedLauncherTestingRequestBus::Handler::BusConnect();
CrySystemEventBus::Handler::BusConnect();
AZ::TickBus::Handler::BusConnect();
}
void AutomatedLauncherTestingSystemComponent::Deactivate()
{
AZ::TickBus::Handler::BusDisconnect();
CrySystemEventBus::Handler::BusDisconnect();
AutomatedLauncherTestingRequestBus::Handler::BusDisconnect();
}
void AutomatedLauncherTestingSystemComponent::CompleteTest(bool success, const AZStd::string& message)
{
AZ_Assert(
m_phase == Phase::RunningTest,
"Expected current phase to be RunningTest (%d), got %d, will skip printing CompleteTest message.",
Phase::RunningTest, m_phase);
if (m_phase == Phase::RunningTest)
{
if (!message.empty())
{
LogAlways("AutomatedLauncher: %s", message.c_str());
}
// Make sure this is always printed, in case log severity is turned down.
LogAlways("AutomatedLauncher: %s", success ? "AUTO_LAUNCHER_TEST_COMPLETE" : "AUTO_LAUNCHER_TEST_FAIL");
m_phase = Phase::Complete;
}
}
void AutomatedLauncherTestingSystemComponent::OnCrySystemInitialized(ISystem& system, [[maybe_unused]] const SSystemInitParams& systemInitParams)
{
m_system = &system;
// Only allow any testing to actually happen in non-release builds.
#if !defined(_RELEASE)
ICmdLine* cmdLine = m_system->GetICmdLine();
if (cmdLine)
{
AZ_Printf("AutomatedLauncher", "Checking for automated launcher testing command line arguments.");
const ICmdLineArg* mapArg = cmdLine->FindArg(eCLAT_Pre, "ltest_map");
if (mapArg)
{
AZStd::string map = mapArg->GetValue();
AZ_Printf("AutomatedLauncher", "Found ltest_map arg %s.", map.c_str());
if(map.compare("default") != 0)
{
AZStd::lock_guard<MutexType> lock(m_testOperationsMutex);
m_testOperations.push_back(TestOperation(TestOperationType::LoadMap, map.c_str()));
}
else
{
// Allow the default menu to load, watch for the next level to load
m_phase = Phase::LoadingMap;
m_nextLevelLoad = NextLevelLoad::WatchForNextLevelLoad;
}
}
const ICmdLineArg* sliceArg = cmdLine->FindArg(eCLAT_Pre, "ltest_slice");
if (sliceArg)
{
AZStd::string slice = sliceArg->GetValue();
AZ_Printf("AutomatedLauncher", "Found ltest_slice arg %s.", slice.c_str());
AzFramework::StringFunc::Tokenize(slice.c_str(), m_slices, ",");
if (!m_slices.empty())
{
AZStd::lock_guard<MutexType> lock(m_testOperationsMutex);
m_testOperations.push_back(TestOperation(TestOperationType::SpawnDynamicSlice, m_slices[0].c_str()));
m_slices.erase(m_slices.begin());
}
}
}
#endif
}
void AutomatedLauncherTestingSystemComponent::OnCrySystemShutdown([[maybe_unused]] ISystem& system)
{
m_system = nullptr;
}
void AutomatedLauncherTestingSystemComponent::OnTick([[maybe_unused]] float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint time)
{
// Check to see if there is a load map operation in flight
if (m_currentTestOperation.m_type == TestOperationType::LoadMap && !m_currentTestOperation.m_complete)
{
if (m_system->GetSystemGlobalState() == ESYSTEM_GLOBAL_STATE_LEVEL_LOAD_COMPLETE)
{
m_currentTestOperation.m_complete = true;
}
}
// Only start a new operation if there isn't already one in flight right now.
else if (!m_testOperations.empty())
{
// Grab the first operation from the list
{
AZStd::lock_guard<MutexType> lock(m_testOperationsMutex);
m_currentTestOperation = m_testOperations.at(0);
m_testOperations.erase(m_testOperations.begin());
}
// If it was a map command, go ahead and launch it.
if (m_currentTestOperation.m_type == TestOperationType::LoadMap)
{
AZ_Assert(m_phase == Phase::None, "Expected current phase to be None (%d), got %d", Phase::None, m_phase);
AZStd::string command = AZStd::string::format("map %s", m_currentTestOperation.m_value.c_str());
m_system->GetIConsole()->ExecuteString(command.c_str());
m_phase = Phase::LoadingMap;
}
// If it was a spawn dynamic slice command, go ahead and spawn it.
else if (m_currentTestOperation.m_type == TestOperationType::SpawnDynamicSlice)
{
AZ_Assert((m_phase == Phase::LoadingMap) || (m_phase == Phase::RunningTest), "Expected current phase to be LoadMap or RunningTest (%d), got %d", Phase::LoadingMap, m_phase);
AZ::Entity* spawnedEntity = SpawnDynamicSlice::CreateSpawner(m_currentTestOperation.m_value, "Automated Testing Dynamic Slice Spawner");
if (spawnedEntity)
{
m_spawnedEntities.emplace_back(std::move(spawnedEntity));
}
m_phase = Phase::RunningTest;
}
}
else if ((m_nextLevelLoad == NextLevelLoad::None) && (m_phase == Phase::RunningTest) && (m_system->GetSystemGlobalState() == ESYSTEM_GLOBAL_STATE_RUNNING))
{
AZ_Printf("AutomatedLauncher", "Running Test - Watching for a next level load");
m_nextLevelLoad = NextLevelLoad::WatchForNextLevelLoad;
}
else if ((m_nextLevelLoad == NextLevelLoad::WatchForNextLevelLoad) && (m_system->GetSystemGlobalState() == ESYSTEM_GLOBAL_STATE_LEVEL_LOAD_COMPLETE))
{
AZ_Printf("AutomatedLauncher", "Next level loaded, adding operations");
if (!m_slices.empty())
{
m_testOperations.push_back(TestOperation(TestOperationType::SpawnDynamicSlice, m_slices[0].c_str()));
m_slices.erase(m_slices.begin());
m_nextLevelLoad = NextLevelLoad::None;
}
else
{
m_nextLevelLoad = NextLevelLoad::LevelLoadsComplete;
}
}
}
void AutomatedLauncherTestingSystemComponent::LogAlways(const char* format, ...)
{
va_list args;
va_start(args, format);
m_system->GetILog()->LogV(ILog::eAlways, format, args);
va_end(args);
}
}
@@ -1,115 +0,0 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <AzCore/Component/Component.h>
#include <AutomatedLauncherTesting/AutomatedLauncherTestingBus.h>
#include <CrySystemBus.h>
#include <AzCore/Component/TickBus.h>
#include <AzCore/std/smart_ptr/shared_ptr.h>
#include <AzCore/std/containers/vector.h>
namespace AZ
{
class Entity;
}
namespace AutomatedLauncherTesting
{
class AutomatedLauncherTestingSystemComponent
: public AZ::Component
, protected AutomatedLauncherTestingRequestBus::Handler
, private CrySystemEventBus::Handler
, private AZ::TickBus::Handler
{
private:
enum class Phase
{
None,
LoadingMap,
RunningTest,
Complete
};
enum class NextLevelLoad
{
None,
WatchForNextLevelLoad,
LevelLoadsComplete
};
enum class TestOperationType
{
None,
LoadMap,
SpawnDynamicSlice
};
struct TestOperation
{
TestOperation()
{
}
TestOperation(TestOperationType type, const AZStd::string& value)
: m_type(type)
, m_value(value)
{
}
TestOperationType m_type = TestOperationType::None;
AZStd::string m_value;
bool m_complete = false;
};
public:
AZ_COMPONENT(AutomatedLauncherTestingSystemComponent, "{87A405E2-390B-43A9-9A96-94BDC0DF680B}");
static void Reflect(AZ::ReflectContext* context);
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided);
static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible);
static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required);
static void GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent);
protected:
////////////////////////////////////////////////////////////////////////
// AutomatedLauncherTestingRequestBus interface implementation
void CompleteTest(bool success, const AZStd::string& message) override;
////////////////////////////////////////////////////////////////////////
protected:
////////////////////////////////////////////////////////////////////////////
// CrySystemEvents
void OnCrySystemInitialized(ISystem& system, const SSystemInitParams& systemInitParams) override;
void OnCrySystemShutdown(ISystem& system) override;
// AZ::Component interface implementation
void Init() override;
void Activate() override;
void Deactivate() override;
// TickBus
void OnTick(float deltaTime, AZ::ScriptTimePoint time) override;
void LogAlways(const char* format, ...);
private:
ISystem* m_system = nullptr;
AZStd::vector<TestOperation> m_testOperations;
AZStd::vector<AZStd::string> m_slices;
MutexType m_testOperationsMutex;
TestOperation m_currentTestOperation;
AZStd::vector<AZStd::shared_ptr<AZ::Entity>> m_spawnedEntities;
Phase m_phase = Phase::None;
NextLevelLoad m_nextLevelLoad = NextLevelLoad::None;
};
}
@@ -1,54 +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 "SpawnDynamicSlice.h"
#include <AzCore/Asset/AssetManager.h>
#include <AzCore/Asset/AssetManagerBus.h>
#include <AzCore/Component/Entity.h>
#include <LmbrCentral/Scripting/SpawnerComponentBus.h>
namespace AutomatedLauncherTesting
{
AZ::Entity* SpawnDynamicSlice::CreateSpawner(const AZStd::string& path, const AZStd::string& entityName)
{
AZ::Entity* spawnerEntity = nullptr;
AZ::Data::AssetId sliceAssetId;
AZ::Data::AssetCatalogRequestBus::BroadcastResult(sliceAssetId, &AZ::Data::AssetCatalogRequestBus::Events::GetAssetIdByPath, path.c_str(), AZ::Data::s_invalidAssetType, false);
if (sliceAssetId.IsValid())
{
AZ_Printf("System", "Spawning dynamic slide %s", path.c_str());
spawnerEntity = aznew AZ::Entity(entityName.c_str());
spawnerEntity->Init();
LmbrCentral::SpawnerConfig spawnerConfig;
AZ::Data::Asset<AZ::DynamicSliceAsset> sliceAssetData = AZ::Data::AssetManager::Instance().GetAsset<AZ::DynamicSliceAsset>(sliceAssetId, spawnerConfig.m_sliceAsset.GetAutoLoadBehavior());
sliceAssetData.BlockUntilLoadComplete();
AZ::Component* spawnerComponent = nullptr;
AZ::ComponentDescriptorBus::EventResult(spawnerComponent, LmbrCentral::SpawnerComponentTypeId, &AZ::ComponentDescriptorBus::Events::CreateComponent);
spawnerConfig.m_sliceAsset = sliceAssetData;
spawnerConfig.m_spawnOnActivate = true;
spawnerComponent->SetConfiguration(spawnerConfig);
spawnerEntity->AddComponent(spawnerComponent);
spawnerEntity->Activate();
}
else
{
AZ_Warning("System", false, "Could not create asset for dynamic slide %s", path.c_str());
}
return spawnerEntity;
}
} // namespace AutomatedLauncherTesting
@@ -1,25 +0,0 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <AzCore/std/string/string.h>
namespace AZ
{
class Entity;
}
namespace AutomatedLauncherTesting
{
class SpawnDynamicSlice
{
public:
static AZ::Entity* CreateSpawner(const AZStd::string& path, const AZStd::string& entityName);
};
} // namespace AutomatedLauncherTesting
@@ -1,15 +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
#
#
set(FILES
Include/AutomatedLauncherTesting/AutomatedLauncherTestingBus.h
Source/AutomatedLauncherTestingSystemComponent.cpp
Source/AutomatedLauncherTestingSystemComponent.h
Source/SpawnDynamicSlice.cpp
Source/SpawnDynamicSlice.h
)
@@ -1,11 +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
#
#
set(FILES
Source/AutomatedLauncherTestingModule.cpp
)
-12
View File
@@ -1,12 +0,0 @@
{
"gem_name": "AutomatedLauncherTesting",
"display_name": "Automated Launcher Testing",
"license": "Apache-2.0 Or MIT",
"origin": "Open 3D Engine - o3de.org",
"type": "Code",
"summary": "The Automated Launcher Testing Gem manages automated Open 3D Engine (O3DE) launcher tests.",
"canonical_tags": ["Gem"],
"user_tags": ["Debug", "Tools"],
"icon_path": "preview.png",
"requirements": ""
}
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6d6204c6730e5675791765ca194e9b1cbec282208e280507de830afc2805e5fa
size 41127
+2 -1
View File
@@ -95,7 +95,7 @@ void CDraw2d::OnBootstrapSceneReady([[maybe_unused]] AZ::RPI::Scene* bootstrapSc
AZ_Assert(scene != nullptr, "Attempting to create a DynamicDrawContext for a viewport context that has not been associated with a scene yet.");
// Create and initialize a DynamicDrawContext for 2d drawing
m_dynamicDraw = AZ::RPI::DynamicDrawInterface::Get()->CreateDynamicDrawContext(scene.get());
m_dynamicDraw = AZ::RPI::DynamicDrawInterface::Get()->CreateDynamicDrawContext();
AZ::RPI::ShaderOptionList shaderOptions;
shaderOptions.push_back(AZ::RPI::ShaderOption(AZ::Name("o_useColorChannels"), AZ::Name("true")));
shaderOptions.push_back(AZ::RPI::ShaderOption(AZ::Name("o_clamp"), AZ::Name("false")));
@@ -106,6 +106,7 @@ void CDraw2d::OnBootstrapSceneReady([[maybe_unused]] AZ::RPI::Scene* bootstrapSc
{"TEXCOORD0", AZ::RHI::Format::R32G32_FLOAT} });
m_dynamicDraw->AddDrawStateOptions(AZ::RPI::DynamicDrawContext::DrawStateOptions::PrimitiveType
| AZ::RPI::DynamicDrawContext::DrawStateOptions::BlendMode);
m_dynamicDraw->SetOutputScope(scene.get());
m_dynamicDraw->EndInit();
AZ::RHI::TargetBlendState targetBlendState;
+2 -1
View File
@@ -109,7 +109,7 @@ AZ::RPI::ScenePtr UiRenderer::CreateScene(AZStd::shared_ptr<AZ::RPI::ViewportCon
void UiRenderer::CreateDynamicDrawContext(AZ::RPI::ScenePtr scene, AZ::Data::Instance<AZ::RPI::Shader> uiShader)
{
m_dynamicDraw = AZ::RPI::DynamicDrawInterface::Get()->CreateDynamicDrawContext(scene.get());
m_dynamicDraw = AZ::RPI::DynamicDrawInterface::Get()->CreateDynamicDrawContext();
// Initialize the dynamic draw context
m_dynamicDraw->InitShader(uiShader);
@@ -121,6 +121,7 @@ void UiRenderer::CreateDynamicDrawContext(AZ::RPI::ScenePtr scene, AZ::Data::Ins
);
m_dynamicDraw->AddDrawStateOptions(AZ::RPI::DynamicDrawContext::DrawStateOptions::StencilState
| AZ::RPI::DynamicDrawContext::DrawStateOptions::BlendMode);
m_dynamicDraw->SetOutputScope(scene.get());
m_dynamicDraw->EndInit();
}
@@ -19,8 +19,9 @@ namespace PhysX
setTextInteractionFlags(Qt::TextBrowserInteraction);
setOpenExternalLinks(true);
setAlignment(Qt::AlignCenter);
setContentsMargins(60, 15, 60, 15);
setContentsMargins(60, 7, 60, 7);
setWordWrap(true);
setStyleSheet(QString::fromUtf8("background-color: rgb(51, 51, 51);"));
}
}
}
@@ -34,7 +34,7 @@ namespace SurfaceData
{
// Transform everything back to world space
outPosition = meshTransform.TransformPoint((rayStartLocal + (rayDirectionLocal * distance)) * clampedScale);
outNormal = meshTransform.TransformVector(normalLocal * clampedScale);
outNormal = meshTransform.TransformVector(normalLocal).GetNormalized();
return true;
}
@@ -112,6 +112,7 @@ void DebugComponent::Activate()
DebugNotificationBus::Handler::BusConnect();
DebugNotificationBus::AllowFunctionQueuing(true);
AzFramework::EntityDebugDisplayEventBus::Handler::BusConnect(GetEntityId());
AzFramework::BoundsRequestBus::Handler::BusConnect(GetEntityId());
SystemConfigurationRequestBus::Handler::BusConnect();
VEG_PROFILE_METHOD(DebugSystemDataBus::BroadcastResult(m_debugData, &DebugSystemDataBus::Events::GetDebugData));
@@ -120,6 +121,7 @@ void DebugComponent::Activate()
void DebugComponent::Deactivate()
{
SystemConfigurationRequestBus::Handler::BusDisconnect();
AzFramework::BoundsRequestBus::Handler::BusDisconnect();
AzFramework::EntityDebugDisplayEventBus::Handler::BusDisconnect();
DebugRequestBus::Handler::BusDisconnect();
DebugNotificationBus::Handler::BusDisconnect();
@@ -13,6 +13,7 @@
#include <AzCore/Component/Component.h>
#include <AzCore/Math/Vector3.h>
#include <AzFramework/Entity/EntityDebugDisplayBus.h>
#include <AzFramework/Visibility/BoundsBus.h>
#include <AzCore/PlatformDef.h>
#include <Vegetation/Ebuses/InstanceSystemRequestBus.h>
#include <Vegetation/Ebuses/SystemConfigurationBus.h>
@@ -56,6 +57,7 @@ namespace Vegetation
class DebugComponent
: public AZ::Component
, private AzFramework::EntityDebugDisplayEventBus::Handler
, private AzFramework::BoundsRequestBus::Handler
, private DebugRequestBus::Handler
, private DebugNotificationBus::Handler
, private SystemConfigurationRequestBus::Handler
@@ -80,6 +82,9 @@ namespace Vegetation
//////////////////////////////////////////////////////////////////////////
// EntityDebugDisplayEventBus
// Ideally this would use ViewportDebugDisplayEventBus::DisplayViewport, but that doesn't currently work in game mode,
// so instead we use this plus the BoundsRequestBus with a large AABB to get ourselves rendered.
void DisplayEntityViewport(
const AzFramework::ViewportInfo& viewportInfo, AzFramework::DebugDisplayRequests& debugDisplay) override;
@@ -110,6 +115,20 @@ namespace Vegetation
void UpdateSystemConfig(const AZ::ComponentConfig* config) override;
void GetSystemConfig([[maybe_unused]] AZ::ComponentConfig* config) const override {}; // ignore this call
//////////////////////////////////////////////////////////////////////////
// BoundsRequestBus
AZ::Aabb GetWorldBounds() override
{
// DisplayEntityViewport relies on the BoundsRequestBus to get the entity bounds to determine when to call debug drawing
// for that entity. Since this is a level component that can draw infinitely far in every direction, we return an
// effectively infinite AABB so that it always draws.
return AZ::Aabb::CreateFromMinMax(AZ::Vector3(-AZ::Constants::FloatMax), AZ::Vector3(AZ::Constants::FloatMax));
}
AZ::Aabb GetLocalBounds() override
{
// The local and world bounds will be the same for this component.
return GetWorldBounds();
}
protected:
void PrepareNextReport();
-6
View File
@@ -1,6 +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
"""
@@ -1,81 +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
Device Farm Create Bundle
"""
import argparse
import logging
import os
import shutil
import stat
logger = logging.getLogger(__name__)
def on_rm_error( func, path, exc_info):
# path contains the path of the file that couldn't be removed
# let's just assume that it's read-only and unlink it.
os.chmod( path, stat.S_IWRITE )
os.unlink( path )
def copy_python_code_tree(src, dest):
shutil.copytree(src, dest, ignore=shutil.ignore_patterns('*.pyc', '__pycache__'))
def create_test_bundle(project, project_launcher_tests_folder, python_test_tools_folder):
temp_folder = os.path.join('temp', project)
# Place all artifacts to send to device farm in this output folder
zip_output_folder = os.path.join(temp_folder, 'zip_output')
# clear the old virtual env folder
logger.info("deleting old zip folder ...")
if os.path.isdir(zip_output_folder):
logger.info("Removing virtual env folder \"{}\" ...".format(zip_output_folder))
shutil.rmtree(zip_output_folder, onerror = on_rm_error)
# create the output folder where we dump everything to be zipped up.
os.makedirs(zip_output_folder)
# core files to add (iOS won't be referenced on Android, but it won't hurt anything)
core_files = [
'run_launcher_tests.py',
'run_launcher_tests_ios.py',
'run_launcher_tests_android.py',
os.path.join('..', '..', project, 'project.json')]
for file in core_files:
shutil.copy2(file, os.path.join(zip_output_folder, os.path.basename(file)))
logger.info("Including test code ...")
test_output_folder = os.path.join(zip_output_folder, 'tests')
copy_python_code_tree(project_launcher_tests_folder, test_output_folder)
# Copy remote console from PythonTestTools
logger.info("Including python PythonTestTools remote console ...")
shutil.copy2(
os.path.join(python_test_tools_folder, 'shared', 'remote_console_commands.py'),
os.path.join(test_output_folder, 'remote_console_commands.py'))
# Zip the tests/ folder, wheelhouse/ folder, and the requirements.txt file into a single archive:
test_bundle_path = os.path.join(temp_folder, 'test_bundle')
logger.info("Generating test bundle zip {} ...".format(test_bundle_path))
shutil.make_archive(test_bundle_path, 'zip', zip_output_folder)
def main():
parser = argparse.ArgumentParser(description='Create the test bundle zip file for use on the Device Farm.')
parser.add_argument('--project', required=True, help='Lumberyard Project')
parser.add_argument('--project-launcher-tests-folder', required=True, help='Absolute path of the folder that contains the test code source.')
parser.add_argument('--python-test-tools-folder', required=True, help='Absolute path of the PythonTestTools folder.')
args = parser.parse_args()
logging.basicConfig(level=logging.DEBUG)
create_test_bundle(args.project, args.project_launcher_tests_folder, args.python_test_tools_folder)
if __name__== "__main__":
main()
@@ -1,11 +0,0 @@
@echo off
REM
REM Copyright (c) Contributors to the Open 3D Engine Project.
REM For complete copyright and license terms please see the LICENSE at the root of this distribution.
REM
REM SPDX-License-Identifier: Apache-2.0 OR MIT
REM
REM
REM
python device_farm_create_bundle.py --project StarterGame --project-launcher-tests-folder "../../StarterGame/LauncherTests" --python-test-tools-folder "../PythonTestTools/test_tools"
@@ -1,7 +0,0 @@
[
{
"attribute": "ARN",
"operator": "IN",
"value": "[%DEVICE_ARN_LIST%]"
}
]
@@ -1,287 +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
Device Farm Schecule Run
"""
import argparse
import datetime
import json
import logging
import os
import subprocess
import sys
import time
import requests
logger = logging.getLogger(__name__)
def bake_template(filename, values):
"""Open a template and replace values. Return path to baked file."""
# Open the options json template and replace with real values.
with open(filename, 'r') as in_file:
data = in_file.read()
for key, value in values.iteritems():
data = data.replace(key, str(value))
filename_out = os.path.join('temp', filename)
with open(filename_out, 'w') as out_file:
out_file.write(data)
return filename_out
def execute_aws_command(args):
""" Execut the aws cli devicefarm command. """
# Use .cmd on Windows, not sure exactly why, but aws will not be found without it.
aws_executable = 'aws.cmd' if sys.platform.startswith('win') else 'aws'
aws_args = [aws_executable, 'devicefarm', '--region', 'us-west-2'] + args
logger.info("Running {} ...".format(" ".join(aws_args)))
p = subprocess.Popen(aws_args, stdout=subprocess.PIPE)
out, err = p.communicate()
if p.returncode != 0:
msg = "Command '{}' failed. return code: {} out: {} err: {}".format(
" ".join(aws_args),
p.returncode,
out,
err
)
raise Exception(msg)
return out
def find_or_create_project(project_name):
""" Find the project by name, or create a new one. """
list_projects_data = json.loads(execute_aws_command(['list-projects']))
# return the arn if it is found
for project_data in list_projects_data['projects']:
if project_data['name'] == project_name:
logger.info("Found existing project named {}.".format(project_name))
return project_data['arn']
# project not found, create a new project with the give name
project_data = json.loads(execute_aws_command(['create-project', '--name', project_name]))
return project_data['project']['arn']
def find_or_create_device_pool(project_name, device_pool_name, device_arns):
""" Find the device pool in the project by name, or create a new one. """
list_device_pools_data = json.loads(execute_aws_command(['list-device-pools', '--arn', project_name]))
# return the arn if it is found
for device_pool_data in list_device_pools_data['devicePools']:
if device_pool_data['name'] == device_pool_name:
logger.info("Found existing device pool named {}.".format(device_pool_name))
return device_pool_data['arn']
device_pool_json_path_out = bake_template(
'device_farm_default_device_pool_template.json',
{'%DEVICE_ARN_LIST%' : device_arns})
# create a default device pool
args = [
'create-device-pool',
'--project-arn',
project_name,
'--name',
device_pool_name,
'--rules',
"file://{}".format(device_pool_json_path_out)]
device_pools_data = json.loads(execute_aws_command(args))
return device_pools_data['devicePool']['arn']
def create_upload(project_arn, path, type):
""" Create an upload and return the ARN """
args = ['create-upload', '--project-arn', project_arn, '--name', os.path.basename(path), '--type', type]
upload_data = json.loads(execute_aws_command(args))
return upload_data['upload']['arn'], upload_data['upload']['url']
def send_upload(filename, url):
""" Upload a file with a put request. """
logger.info("Sending upload {} ...".format(filename))
with open(filename, 'rb') as uploadfile:
data = uploadfile.read()
headers = {"content-type": "application/octet-stream"}
output = requests.put(url, data=data, allow_redirects=True, headers=headers)
logger.info("Sent upload {}.".format(output))
def wait_for_upload_to_finish(poll_time, upload_arn):
""" Wait for an upload to finish by polling for status """
logger.info("Waiting for upload {} ...".format(upload_arn))
upload_data = json.loads(execute_aws_command(['get-upload', '--arn', upload_arn]))
while not upload_data['upload']['status'] in ['SUCCEEDED', 'FAILED']:
time.sleep(poll_time)
upload_data = json.loads(execute_aws_command(['get-upload', '--arn', upload_arn]))
if upload_data['upload']['status'] != 'SUCCEEDED':
raise Exception('Upload failed.')
def upload(poll_time, project_arn, path, type):
""" Create the upload on the Device Farm, upload the file and wait for completion. """
arn, url = create_upload(project_arn, path, type)
send_upload(path, url)
wait_for_upload_to_finish(poll_time, arn)
return arn
def schedule_run(project_arn, app_arn, device_pool_arn, test_spec_arn, test_bundle_arn, execution_timeout):
""" Schecule the test run on the Device Farm """
run_name = "LY LT {}".format(datetime.datetime.now().strftime("%I:%M%p on %B %d, %Y"))
logger.info("Scheduling run {} ...".format(run_name))
schedule_run_test_json_path_out = bake_template(
'device_farm_schedule_run_test_template.json',
{'%TEST_SPEC_ARN%' : test_spec_arn, '%TEST_PACKAGE_ARN%' : test_bundle_arn})
execution_configuration_json_path_out = bake_template(
'device_farm_schedule_run_execution_configuration_template.json',
{'%EXECUTION_TIMEOUT%' : execution_timeout})
args = [
'schedule-run',
'--project-arn',
project_arn,
'--app-arn',
app_arn,
'--device-pool-arn',
device_pool_arn,
'--name',
"\"{}\"".format(run_name),
'--test',
"file://{}".format(schedule_run_test_json_path_out),
'--execution-configuration',
"file://{}".format(execution_configuration_json_path_out)]
schedule_run_data = json.loads(execute_aws_command(args))
return schedule_run_data['run']['arn']
def download_file(url, output_path):
""" download a file from a url, save in output_path """
try:
r = requests.get(url, stream=True)
r.raise_for_status()
output_folder = os.path.dirname(output_path)
if not os.path.exists(output_folder):
os.makedirs(output_folder)
with open(output_path, 'wb') as f:
for chunk in r.iter_content(chunk_size=8192):
if chunk:
f.write(chunk)
except requests.exceptions.RequestException as e:
logging.exception("Failed request for downloading file from {}.".format(url))
return False
except IOError as e:
logging.exception("Failed writing to file {}.".format(output_path))
return False
return True
def download_artifacts(run_arn, artifacts_output_folder):
"""
Download run artifacts and write to path set in artifacts_output_folder.
"""
logging.basicConfig(level=logging.DEBUG)
list_jobs_data = json.loads(execute_aws_command(['list-jobs', '--arn', run_arn]))
for job_data in list_jobs_data['jobs']:
logger.info("Downloading artifacts for {} ...".format(job_data['name']))
safe_job_name = "".join(x for x in job_data['name'] if x.isalnum())
list_artifacts_data = json.loads(execute_aws_command(['list-artifacts', '--arn', job_data['arn'], '--type', 'FILE']))
for artifact_data in list_artifacts_data['artifacts']:
# A run may contain many jobs. Usually each job is one device type.
# Each job has 3 stages: setup, test and shutdown. You can tell what
# stage an artifact is from based on the ARN.
# We only care about artifacts from the main stage of the job,
# not the setup or tear down artifacts. So parse the ARN and look
# for the 00001 identifier.
print artifact_data['arn']
if artifact_data['arn'].split('/')[3] == '00001':
logger.info("Downloading artifacts {} ...".format(artifact_data['name']))
output_filename = "{}.{}".format(
"".join(x for x in artifact_data['name'] if x.isalnum()),
artifact_data['extension'])
output_path = os.path.join(artifacts_output_folder, safe_job_name, output_filename)
if not download_file(artifact_data['url'], output_path):
msg = "Failed to download file from {} and save to {}".format(artifact_data['url'], output_path)
logger.error(msg)
def main():
parser = argparse.ArgumentParser(description='Upload and app and schedule a run on the Device Farm.')
parser.add_argument('--app-path', required=True, help='Path of the app file.')
parser.add_argument('--test-spec-path', required=True, help='Path of the test spec yaml.')
parser.add_argument('--test-bundle-path', required=True, help='Path of the test bundle zip.')
parser.add_argument('--project-name', required=True, help='The name of the project.')
parser.add_argument('--device-pool-name', required=True, help='The name of the device pool.')
parser.add_argument('--device-arns',
default='\\"arn:aws:devicefarm:us-west-2::device:6CCDF49186B64E3FB27B9346AC9FAEC1\\"',
help='List of device ARNs. Used when existing pool is not found by name. Default is Galaxy S8.')
parser.add_argument('--wait-for-result', default="true", help='Set to "true" to wait for result of run.')
parser.add_argument('--download-artifacts', default="true", help='Set to "true" to download artifacts after run. requires --wait-for-result')
parser.add_argument('--artifacts-output-folder', default="temp", help='Folder to place the downloaded artifacts.')
parser.add_argument('--upload-poll-time', default=10, help='How long to wait between polling upload status.')
parser.add_argument('--run-poll-time', default=60, help='How long to wait between polling run status.')
parser.add_argument('--run-execution-timeout', default=60, help='Run execution timeout.')
parser.add_argument('--test-names', nargs='+', help='A list of test names to run, default runs all tests.')
args = parser.parse_args()
logging.basicConfig(level=logging.DEBUG)
# Find the project by name, or create a new one.
project_arn = find_or_create_project(args.project_name)
# Find the device pool in the project by name, or create a new one.
device_pool_arn = find_or_create_device_pool(project_arn, args.device_pool_name, args.device_arns)
# Bake out EXTRA_ARGS option with args.test_names
extra_args = ""
if args.test_names:
extra_args = "--test-names {}".format(" ".join("\"{}\"".format(test_name) for test_name in args.test_names))
test_spec_path_out = bake_template(
args.test_spec_path,
{'%EXTRA_ARGS%' : extra_args})
# Upload test spec and test bundle (Appium js is just a generic avenue to our own custom code).
test_spec_arn = upload(args.upload_poll_time, project_arn, test_spec_path_out, 'APPIUM_NODE_TEST_SPEC')
test_bundle_arn = upload(args.upload_poll_time, project_arn, args.test_bundle_path, 'APPIUM_NODE_TEST_PACKAGE')
# Upload the app.
type = 'ANDROID_APP' if args.app_path.lower().endswith('.apk') else 'IOS_APP'
app_arn = upload(args.upload_poll_time, project_arn, args.app_path, type)
# Schedule the test run.
run_arn = schedule_run(project_arn, app_arn, device_pool_arn, test_spec_arn, test_bundle_arn, args.run_execution_timeout)
logger.info('Run scheduled.')
# Wait for run, exit with failure if test run fails.
# strcmp with true for easy of use jenkins boolean env var.
if args.wait_for_result.lower() == 'true':
# Runs can take a long time, so just poll once a mintue by default.
run_data = json.loads(execute_aws_command(['get-run', '--arn', run_arn]))
while run_data['run']['result'] == 'PENDING':
logger.info("Run status: {} waiting {} seconds ...".format(run_data['run']['result'], args.run_poll_time))
time.sleep(args.run_poll_time)
run_data = json.loads(execute_aws_command(['get-run', '--arn', run_arn]))
# Download run artifacts. strcmp with true for easy of use jenkins boolean env var.
if args.download_artifacts.lower() == 'true':
download_artifacts(run_arn, args.artifacts_output_folder)
# If the run did not pass raise an exception to fail this jenkins job.
if run_data['run']['result'] != 'PASSED':
# Dump all of the run info.
logger.info(run_data)
# Raise an exception to fail this test.
msg = "Run fail with result {}\nRun ARN: {}".format(run_data['run']['result'], run_arn)
raise Exception(msg)
logger.info('Run passed.')
if __name__== "__main__":
main()
@@ -1,15 +0,0 @@
@echo off
REM
REM Copyright (c) Contributors to the Open 3D Engine Project.
REM For complete copyright and license terms please see the LICENSE at the root of this distribution.
REM
REM SPDX-License-Identifier: Apache-2.0 OR MIT
REM
REM
REM
call ../../python/python.cmd run_launcher_tests_local_validation.py --dev-root-folder "../.." --project "StarterGame"
if %ERRORLEVEL% == 0 (
call ../../python/python.cmd device_farm_create_bundle.py --project StarterGame --project-launcher-tests-folder "../../StarterGame/LauncherTests" --python-test-tools-folder "../PythonTestTools/test_tools"
call ../../python/python.cmd device_farm_schedule_run.py --app-path "../../BinAndroidArmv8Clang/StarterGameLauncher_w_assets.apk" --project-name "LyAutomatedLauncher" --device-pool-name "LyAndroid" --test-spec-path "device_farm_test_spec_android.yaml" --test-bundle-path "temp/StarterGame/test_bundle.zip" --artifacts-output-folder "temp/StarterGame"
)
@@ -1,4 +0,0 @@
{
"jobTimeoutMinutes": %EXECUTION_TIMEOUT%,
"videoCapture": true
}
@@ -1,10 +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
#
../../python/python.sh run_launcher_tests_local_validation.py --dev-root-folder "../.." --project "StarterGame" || exit
../../python/python.sh device_farm_create_bundle.py --project StarterGame --project-launcher-tests-folder "../../StarterGame/LauncherTests" --python-test-tools-folder "../PythonTestTools/test_tools"
# Current known limitation on iOS, only one test at a time is supported, see run_launcher_tests_ios.py run_test
../../python/python.sh device_farm_schedule_run.py --app-path "$1" --project-name "LyAutomatedLauncherIOS" --device-pool-name "LyIOS" --test-spec-path "device_farm_test_spec_ios.yaml" --test-bundle-path "temp/StarterGame/test_bundle.zip" --artifacts-output-folder "temp/StarterGame" --device-arns "\\\"arn:aws:devicefarm:us-west-2::device:D125AEEE8614463BAE106865CAF4470E\\\"" --test-names "progress"
@@ -1,5 +0,0 @@
{
"type": "APPIUM_NODE",
"testPackageArn": "%TEST_PACKAGE_ARN%",
"testSpecArn": "%TEST_SPEC_ARN%"
}
@@ -1,16 +0,0 @@
version: 0.1
phases:
install:
commands:
pre_test:
commands:
- adb -P 5037 -s "$DEVICEFARM_DEVICE_UDID" install -r $DEVICEFARM_APP_PATH
test:
commands:
- python ./run_launcher_tests_android.py --project-json-path "./project.json" --project-launcher-tests-folder "./tests" --screenshots-folder "$SCREENSHOT_PATH" %EXTRA_ARGS%
post_test:
commands:
@@ -1,16 +0,0 @@
version: 0.1
phases:
install:
commands:
pre_test:
commands:
- idevicedebug -u $DEVICEFARM_DEVICE_UDID run com.amazon.lumberyard.startergame
- sleep 10s
test:
commands:
- python ./run_launcher_tests_ios.py --project-json-path "./project.json" --project-launcher-tests-folder "./tests" --screenshots-folder "$SCREENSHOT_PATH" %EXTRA_ARGS%
post_test:
commands:
@@ -1,132 +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
"""
import json
import logging
import os
import time
import shutil
import subprocess
import sys
logger = logging.getLogger(__name__)
class PlatformDriver:
def __init__(self, project_json_path, project_launcher_tests_folder, test_names, screenshots_folder, screenshots_interval):
self.project_json_path = project_json_path
self.project_launcher_tests_folder = project_launcher_tests_folder
self.test_names = test_names
self.screenshots_folder = screenshots_folder
self.screenshots_interval = screenshots_interval
def read_json_data(self, path):
"""Read a json file and return the data"""
try:
with open(path) as json_file:
json_data = json.load(json_file)
except Exception, e:
logger.error("Failed to read json file: '{}'".format(path))
logger.error("Exception: '{}'".format(e))
sys.exit(1)
return json_data
def read_project_name(self):
project_data = self.read_json_data(self.project_json_path)
return project_data['project_name']
def run_test(self, map, dynamic_slice, timeout, pass_string, fail_string):
""" Meant to be overridden in derived classes. """
return True
def run_launcher_tests(self):
"""Discovers all of the available tests in launcher_tests.json and runs them.
"""
# Delete the old screenshots folder if it exists
if os.path.exists(self.screenshots_folder):
shutil.rmtree(self.screenshots_folder)
# Read the launcher_tests.json file.
launcher_tests_data = self.read_json_data(os.path.join(self.project_launcher_tests_folder, 'launcher_tests.json'))
# Run each of the tests found in the launcher_tests.json file.
ok = True
for launcher_test_data in launcher_tests_data['launcher_tests']:
# Skip over this test if specific tests are specified and this is not one of them.
if self.test_names and launcher_test_data.get('name').lower() not in [x.lower() for x in self.test_names]:
continue
ok = ok and self.run_test(
launcher_test_data.get('name'),
launcher_test_data.get('map'),
launcher_test_data.get('dynamic_slice'),
launcher_test_data.get('timeout'),
launcher_test_data.get('pass_string', 'AUTO_LAUNCHER_TEST_COMPLETE'),
launcher_test_data.get('fail_string', 'AUTO_LAUNCHER_TEST_FAIL'))
return ok
def monitor_process_output(self, test_name, command, pass_string, fail_string, timeout, log_file=None):
self.process = subprocess.Popen(command, stdout=subprocess.PIPE)
# On windows, function was failing (I think) because it checked the poll before the process
# had a chance to start, so added a short delay to give it some time to startup.
# It also failed if the log_file was open()'d when it didn't exist yet.
# Delay seems to have fixed the problem. 0.25 sec was too short.
time.sleep(0.5)
if log_file:
# The process we're starting sends it's output to the log file instead of stdout
# so we need to monitor that instead of the stdout.
fp = open(log_file)
# Detect log output messages or timeout exceeded
start = time.time()
last_time = start
screenshot_time_remaining = self.screenshots_interval
screenshot_index = 0
logger.info('Waiting for test to complete.')
message = ""
result = False
while True:
if log_file:
line = fp.readline()
else:
line = self.process.stdout.readline()
if line == '' and self.process.poll() is not None:
break
if line:
logger.info(line.rstrip())
if pass_string in line:
message = "Detected {}. Test completed.".format(pass_string)
result = True
break
if fail_string in line:
message = "Detected {}. Test failed.".format(fail_string)
break
if time.time() - start > timeout:
message = "Timeout of {} reached. Test failed.".format(timeout)
break
rc = self.process.poll()
cur_time = time.time()
screenshot_time_remaining = screenshot_time_remaining - (cur_time - last_time)
last_time = cur_time
if screenshot_time_remaining <= 0:
self.take_screenshot(os.path.join(self.screenshots_folder, "{}_screen{}".format(test_name.replace(' ', '_'), screenshot_index)))
screenshot_index = screenshot_index + 1
screenshot_time_remaining = self.screenshots_interval
logger.info(message)
if log_file:
fp.close()
return result
@@ -1,109 +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
"""
import argparse
import itertools
import logging
import os
from run_launcher_tests import PlatformDriver
import subprocess
import time
logger = logging.getLogger(__name__)
class AndroidDriver(PlatformDriver):
def read_package_name(self):
project_data = self.read_json_data(self.project_json_path)
return project_data['android_settings']['package_name']
def run_test(self, test_name, map, dynamic_slice, timeout, pass_string, fail_string):
package = self.read_package_name()
project = self.read_project_name()
package_and_activity = "{}/{}.{}Activity".format(package, package, project)
# clear any old logcat
command_line = ['adb', 'logcat', '-c']
p = subprocess.Popen(command_line)
p.communicate()
# increase ring buffer size because we are going to be printing a large amount of data in a short time.
command_line = ['adb', 'logcat', '-G', '10m']
p = subprocess.Popen(command_line)
p.communicate()
# Start the process and pass in the test args
logger.info("Start the activity {} ...".format(package_and_activity))
command_line = ['adb', 'shell', 'am', 'start', '-a', 'android.intent.action.MAIN', '-n', package_and_activity]
command_line += ['-e', 'ltest_map', map]
command_line += ['-e', 'ltest_slice', dynamic_slice]
p = subprocess.Popen(command_line)
p.communicate()
# Get the pid of the app to use in the logcat monitoring. If we don't
# do this we might get residual output from previous test run. Even
# though we do a clear.
for _ in itertools.repeat(None, 10):
command_line = ['adb', 'shell', 'pidof', package]
p = subprocess.Popen(command_line, stdout=subprocess.PIPE)
stdoutdata, stderrdata = p.communicate()
pid = stdoutdata.strip()
if pid:
logger.info("Get pid of {}".format(pid))
break
else:
logger.info('Failed to get pid, waiting 1 second to retry ...')
time.sleep(1)
if not pid:
raise Exception('Unable to determin the pid of the process.')
command_line = ['adb', '-d', 'logcat', "--pid={}".format(pid), 'LMBR:I', '*:S']
test_result = self.monitor_process_output(test_name, command_line, pass_string, fail_string, timeout)
logger.info("Kill the app ...")
p = subprocess.Popen(['adb', 'shell', 'am', 'force-stop', package])
p.communicate()
# Stop here if we failed.
if not test_result:
raise Exception("Test failed.")
def take_screenshot(self, output_path_no_ext):
# Create the output folder if it is not there
output_folder = os.path.dirname(output_path_no_ext)
if not os.path.exists(output_folder):
os.makedirs(output_folder)
# Take the screenshot
p = subprocess.Popen(['adb', 'shell', 'screencap', '-p', '/sdcard/screen.png'])
p.communicate()
# copy it off of the device
p = subprocess.Popen(['adb', 'pull', '/sdcard/screen.png', "{}.png".format(output_path_no_ext)])
p.communicate()
def main():
parser = argparse.ArgumentParser(description='Sets up and runs Android Launcher Tests.')
parser.add_argument('--project-json-path', required=True, help='Path to the project.json project settings file.')
parser.add_argument('--project-launcher-tests-folder', required=True, help='Path to the LauncherTests folder in a Project.')
parser.add_argument('--test-names', nargs='+', help='A list of test names to run, default runs all tests.')
parser.add_argument('--screenshots-folder', default="./temp/Android/screenshots", help='Output folder for screenshots.')
parser.add_argument('--screenshots-interval', default=5, help='Time interval between taking screenshots in seconds.')
args = parser.parse_args()
logging.basicConfig(level=logging.DEBUG)
logger.info("Running Launcher tests at {} ...".format(args.project_launcher_tests_folder))
driver = AndroidDriver(args.project_json_path, args.project_launcher_tests_folder, args.test_names, args.screenshots_folder, args.screenshots_interval)
driver.run_launcher_tests()
if __name__== "__main__":
main()
@@ -1,92 +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
"""
import argparse
import logging
import os
from run_launcher_tests import PlatformDriver
import subprocess
logger = logging.getLogger(__name__)
class IOSDriver(PlatformDriver):
def __init__(self, project_json_path, project_launcher_tests_folder, test_names, screenshots_folder, screenshots_interval, device_udid):
self.project_json_path = project_json_path
self.project_launcher_tests_folder = project_launcher_tests_folder
self.test_names = test_names
self.screenshots_folder = screenshots_folder
self.screenshots_interval = screenshots_interval
self.device_udid = device_udid
def run_test(self, test_name, map, dynamic_slice, timeout, pass_string, fail_string):
project = self.read_project_name()
bundle_id = "com.amazon.lumberyard.{}".format(project)
# Start the process and pass in the test args
command_line = ['idevicedebug', '-u', self.device_udid, 'run', bundle_id]
command_line += ['-ltest_map', map]
command_line += ['-ltest_slice', dynamic_slice]
test_result = self.monitor_process_output(test_name, command_line, pass_string, fail_string, timeout)
# TODO: Figure out some way to kill the running app. Because we dont know how to do this,
# we currently have a limitation on iOS we can only run one test at a time.
# Stop here if we failed.
if not test_result:
raise Exception("Test failed.")
def take_screenshot(self, output_path_no_ext):
# Create the output folder if it is not there
output_folder = os.path.dirname(output_path_no_ext)
if not os.path.exists(output_folder):
os.makedirs(output_folder)
# idevicescreenshot to take a screenshot and save to output path.
p = subprocess.Popen(['idevicescreenshot', "{}.tiff".format(output_path_no_ext)])
p.communicate()
def discover_device():
""" Discover the connected device and get the UDID."""
logger.info("Getting the connected device UDID ...")
p = subprocess.Popen(['idevice_id', '--list'], stdout=subprocess.PIPE)
out, err = p.communicate()
if not out:
raise Exception("No output.\nout:{}\nerr:{}\n".format(out, err))
lines = out.splitlines()
if not len(lines):
raise Exception("No devices connected.\nout:{}\nerr:{}\n".format(out, err))
if len(lines) != 1:
raise Exception("More than one device connected. Use --device-udid\nout:{}\nerr:{}\n".format(out, err))
return lines[0]
def main():
parser = argparse.ArgumentParser(description='Sets up and runs iOS Launcher Tests.')
parser.add_argument('--project-json-path', required=True, help='Path to the project.json project settings file.')
parser.add_argument('--project-launcher-tests-folder', required=True, help='Path to the LauncherTests folder in a Project.')
parser.add_argument('--device-udid', help='The UDID of the iOS device. Will auto detect if just one device is attached.')
parser.add_argument('--test-names', nargs='+', help='A list of test names to run, default runs all tests.')
parser.add_argument('--screenshots-folder', default="./temp/iOS/screenshots", help='Output folder for screenshots.')
parser.add_argument('--screenshots-interval', default=5, help='Time interval between taking screenshots in seconds.')
args = parser.parse_args()
logging.basicConfig(level=logging.DEBUG)
device_udid = args.device_udid
if not device_udid:
device_udid = discover_device()
logger.info("Running Launcher tests at {} ...".format(args.project_launcher_tests_folder))
driver = IOSDriver(args.project_json_path, args.project_launcher_tests_folder, args.test_names, args.screenshots_folder, args.screenshots_interval, device_udid)
driver.run_launcher_tests()
if __name__== "__main__":
main()
@@ -1,78 +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
"""
import argparse
import json
import logging
import os
import sys
logger = logging.getLogger(__name__)
def report_error_and_exit(msg):
"""Log an error message and exit with error code 1."""
logger.error(msg)
sys.exit(1)
def check_autoexe_cfg(dev_root_folder, project):
"""
Make sure that the project's autoexec.cfg does not contain a Map command.
The Launcher Test framework is responsible for loading the map.
"""
# Open the autoexec.cfg file and read the contents
autoexec_cfg_path = os.path.join(dev_root_folder, project, 'autoexec.cfg')
try:
with open(autoexec_cfg_path) as f:
content = f.readlines()
except:
report_error_and_exit("Failed to read contents of {}".format(autoexec_cfg_path))
# Make sure no map command is detected, the Launcher Test code will be in charge of loading a map
for line in content:
if line.lower().startswith('map '):
report_error_and_exit("Map command '{}' detected in {}".format(line.strip(), autoexec_cfg_path))
def check_gems_enabled(dev_root_folder, project):
"""Check the project's gems to make sure the AutomatedLauncherTesting gem is enabled."""
# Read the gems.json file
gems_json_path = os.path.join(dev_root_folder, project, 'gems.json')
try:
with open(gems_json_path) as f:
json_data = json.load(f)
except:
report_error_and_exit("Failed to read contents of {}".format(gems_json_path))
# Make sure AutomatedLauncherTesting is enabled
found = False
for gem_data in json_data['Gems']:
if 'AutomatedLauncherTesting' in gem_data['Path']:
found = True
break
if not found:
report_error_and_exit("Automated Launcer Testing GEM not enabled in {}".format(gems_json_path))
def main():
parser = argparse.ArgumentParser(description='Run validation on the local environment to check for required Launcher Tests config.')
parser.add_argument('--dev-root-folder', required=True, help='Path to the root Lumberyard dev folder.')
parser.add_argument('--project', required=True, help='Lumberyard project.')
args = parser.parse_args()
logging.basicConfig(level=logging.DEBUG)
logger.info("Running validation for project {} ...".format(args.project))
check_autoexe_cfg(args.dev_root_folder, args.project)
check_gems_enabled(args.dev_root_folder, args.project)
logger.info('Validation complete.')
if __name__== '__main__':
main()
@@ -1,67 +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
"""
import argparse
import logging
import os
from run_launcher_tests import PlatformDriver
import test_tools.platforms.win.win as win
from test_tools.shared.platform_map import PLATFORM_MAP
logger = logging.getLogger(__name__)
class WinDriver(PlatformDriver):
def __init__(self, project_json_path, project_launcher_tests_folder):
self.project_json_path = project_json_path
self.project_launcher_tests_folder = project_launcher_tests_folder
self.platform_map = PLATFORM_MAP[win.platform_name() + "_" + win.default_compiler_option()]
def run_test(self, map, dynamic_slice, timeout, pass_string, fail_string):
project = self.read_project_name()
# Start the process and pass in the test args
dev_dir = os.path.dirname(os.path.realpath(__file__))
dev_dir = os.path.join(dev_dir, os.path.pardir)
dev_dir = os.path.join(dev_dir, os.path.pardir)
dev_dir = os.path.realpath(dev_dir)
launcher_dir = os.path.join(dev_dir, self.platform_map["bin_dir"])
command_line = [os.path.join(launcher_dir, project + 'Launcher.exe')]
command_line += ['-ltest_map', map]
command_line += ['-ltest_slice', dynamic_slice]
log_file = os.path.join(dev_dir, "cache", project, "pc", "user", "log", "Game.log")
test_result = self.monitor_process_output(command_line, pass_string, fail_string, timeout, log_file)
if self.process:
self.process.kill()
# Stop here if we failed.
if not test_result:
raise Exception("Test failed.")
return test_result
def main():
parser = argparse.ArgumentParser(description='Sets up and runs Windows Launcher Tests.')
parser.add_argument('--project-json-path', required=True, help='Path to the project.json project settings file.')
parser.add_argument('--project-launcher-tests-folder', required=True, help='Path to the LauncherTests folder in a Project.')
args = parser.parse_args()
logging.basicConfig(level=logging.DEBUG)
logger.info("Running Launcher tests at {} ...".format(args.project_launcher_tests_folder))
driver = WinDriver(args.project_json_path, args.project_launcher_tests_folder)
driver.run_launcher_tests()
if __name__== "__main__":
main()
@@ -1,12 +0,0 @@
@echo off
REM
REM Copyright (c) Contributors to the Open 3D Engine Project.
REM For complete copyright and license terms please see the LICENSE at the root of this distribution.
REM
REM SPDX-License-Identifier: Apache-2.0 OR MIT
REM
REM
REM
REM Provided as an example of how to run the Automated Launcher Test on a developer's local machine.
../../python/python.cmd run_launcher_tests_win.py --project-json-path "../../SamplesProject/project.json" --project-launcher-tests-folder "../../SamplesProject/LauncherTests"
-1
View File
@@ -15,7 +15,6 @@
"Gems/AtomTressFX",
"Gems/AudioEngineWwise",
"Gems/AudioSystem",
"Gems/AutomatedLauncherTesting",
"Gems/AWSClientAuth",
"Gems/AWSCore",
"Gems/AWSGameLift",