Merge branch 'development' of https://github.com/o3de/o3de into sc-editor-asset-redux

This commit is contained in:
chcurran
2021-10-18 12:55:21 -07:00
342 changed files with 3344 additions and 643742 deletions
@@ -54,6 +54,30 @@ def get_mesh_node_names(sceneGraph):
return meshDataList, paths
def add_material_component(entity_id):
# Create an override AZ::Render::EditorMaterialComponent
editor_material_component = azlmbr.entity.EntityUtilityBus(
azlmbr.bus.Broadcast,
"GetOrAddComponentByTypeName",
entity_id,
"EditorMaterialComponent")
# this fills out the material asset to a known product AZMaterial asset relative path
json_update = json.dumps({
"Controller": { "Configuration": { "materials": [
{
"Key": {},
"Value": { "MaterialAsset":{
"assetHint": "materials/basic_grey.azmaterial"
}}
}]
}}
});
result = azlmbr.entity.EntityUtilityBus(azlmbr.bus.Broadcast, "UpdateComponentForEntity", entity_id, editor_material_component, json_update)
if not result:
raise RuntimeError("UpdateComponentForEntity for editor_material_component failed")
def update_manifest(scene):
import json
import uuid, os
@@ -75,6 +99,7 @@ def update_manifest(scene):
created_entities = []
previous_entity_id = azlmbr.entity.InvalidEntityId
first_mesh = True
# Loop every mesh node in the scene
for activeMeshIndex in range(len(mesh_name_list)):
@@ -112,6 +137,11 @@ def update_manifest(scene):
if not result:
raise RuntimeError("UpdateComponentForEntity failed for Mesh component")
# an example of adding a material component to override the default material
if previous_entity_id is not None and first_mesh:
first_mesh = False
add_material_component(entity_id)
# Get the transform component
transform_component = azlmbr.entity.EntityUtilityBus(azlmbr.bus.Broadcast, "GetOrAddComponentByTypeName", entity_id, "27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0")
@@ -17,13 +17,6 @@
"Gems/PhysicsEntities"
]
},
"PhysXSamples":
{
"SourcePaths":
[
"Gems/PhysXSamples"
]
},
"PrimitiveAssets":
{
"SourcePaths":
@@ -19,10 +19,16 @@ namespace Editor
if (GetIEditor()->IsInGameMode())
{
#ifdef PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB
AzFramework::XcbEventHandlerBus::Broadcast(&AzFramework::XcbEventHandler::HandleXcbEvent, static_cast<xcb_generic_event_t*>(message));
// We need to handle RAW Input events in a separate loop. This is a workaround to enable XInput2 RAW Inputs using Editor mode.
// TODO To have this call here might be not be perfect.
AzFramework::XcbEventHandlerBus::Broadcast(&AzFramework::XcbEventHandler::PollSpecialEvents);
// Now handle the rest of the events.
AzFramework::XcbEventHandlerBus::Broadcast(
&AzFramework::XcbEventHandler::HandleXcbEvent, static_cast<xcb_generic_event_t*>(message));
#endif
return true;
}
return false;
}
}
} // namespace Editor
@@ -192,11 +192,86 @@ namespace AZ
};
//! SettingsRegistry notifier handler which is responsible for loading
//! the project.json file at the new project path
//! if an update to '<BootstrapSettingsRootKey>/project_path' key occurs.
struct ProjectPathChangedEventHandler
{
ProjectPathChangedEventHandler(AZ::SettingsRegistryInterface& registry)
: m_registry{ registry }
{
}
void operator()(AZStd::string_view path, AZ::SettingsRegistryInterface::Type)
{
// Update the project settings when the project path is set
using FixedValueString = AZ::SettingsRegistryInterface::FixedValueString;
const auto projectPathKey = FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) + "/project_path";
AZ::IO::FixedMaxPath newProjectPath;
if (SettingsRegistryMergeUtils::IsPathAncestorDescendantOrEqual(projectPathKey, path)
&& m_registry.Get(newProjectPath.Native(), projectPathKey) && newProjectPath != m_oldProjectPath)
{
// Update old Project path before attempting to merge in new Settings Registry values in order to prevent recursive calls
m_oldProjectPath = newProjectPath;
// Merge the project.json file into settings registry under ProjectSettingsRootKey path.
AZ::IO::FixedMaxPath projectMetadataFile{ AZ::SettingsRegistryMergeUtils::FindEngineRoot(m_registry) / newProjectPath };
projectMetadataFile /= "project.json";
m_registry.MergeSettingsFile(projectMetadataFile.Native(),
AZ::SettingsRegistryInterface::Format::JsonMergePatch, AZ::SettingsRegistryMergeUtils::ProjectSettingsRootKey);
// Update all the runtime file paths based on the new "project_path" value.
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(m_registry);
}
}
private:
AZ::IO::FixedMaxPath m_oldProjectPath;
AZ::SettingsRegistryInterface& m_registry;
};
//! SettingsRegistry notifier handler which adds the project name as a specialization tag
//! to the registry
//! if an update to '<ProjectSettingsRootKey>/project_name' key occurs.
struct ProjectNameChangedEventHandler
{
ProjectNameChangedEventHandler(AZ::SettingsRegistryInterface& registry)
: m_registry{ registry }
{
}
void operator()(AZStd::string_view path, AZ::SettingsRegistryInterface::Type)
{
// Update the project specialization when the project name is set
using FixedValueString = AZ::SettingsRegistryInterface::FixedValueString;
const auto projectNameKey = FixedValueString(AZ::SettingsRegistryMergeUtils::ProjectSettingsRootKey) + "/project_name";
FixedValueString newProjectName;
if (SettingsRegistryMergeUtils::IsPathAncestorDescendantOrEqual(projectNameKey, path)
&& m_registry.Get(newProjectName, projectNameKey) && newProjectName != m_oldProjectName)
{
// Add the project_name as a specialization for loading the build system dependency .setreg files
auto newProjectNameSpecialization = FixedValueString::format("%s/%.*s", AZ::SettingsRegistryMergeUtils::SpecializationsRootKey,
aznumeric_cast<int>(newProjectName.size()), newProjectName.data());
auto oldProjectNameSpecialization = FixedValueString::format("%s/%s", AZ::SettingsRegistryMergeUtils::SpecializationsRootKey,
m_oldProjectName.c_str());
m_registry.Remove(oldProjectNameSpecialization);
m_oldProjectName = newProjectName;
m_registry.Set(newProjectNameSpecialization, true);
}
}
private:
AZ::SettingsRegistryInterface::FixedValueString m_oldProjectName;
AZ::SettingsRegistryInterface& m_registry;
};
//! SettingsRegistry notifier handler which updates relevant registry settings based
//! on an update to '/Amazon/AzCore/Bootstrap/project_path' key.
struct UpdateProjectSettingsEventHandler
struct UpdateCommandLineEventHandler
{
UpdateProjectSettingsEventHandler(AZ::SettingsRegistryInterface& registry, AZ::CommandLine& commandLine)
UpdateCommandLineEventHandler(AZ::SettingsRegistryInterface& registry, AZ::CommandLine& commandLine)
: m_registry{ registry }
, m_commandLine{ commandLine }
{
@@ -204,70 +279,14 @@ namespace AZ
void operator()(AZStd::string_view path, AZ::SettingsRegistryInterface::Type)
{
using FixedValueString = AZ::SettingsRegistryInterface::FixedValueString;
// #1 Update the project settings when the project path is set
const auto projectPathKey = FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) + "/project_path";
AZ::IO::FixedMaxPath newProjectPath;
if (SettingsRegistryMergeUtils::IsPathAncestorDescendantOrEqual(projectPathKey, path)
&& m_registry.Get(newProjectPath.Native(), projectPathKey) && newProjectPath != m_oldProjectPath)
{
UpdateProjectSettingsFromProjectPath(AZ::IO::PathView(newProjectPath));
}
// #2 Update the project specialization when the project name is set
const auto projectNameKey = FixedValueString(AZ::SettingsRegistryMergeUtils::ProjectSettingsRootKey) + "/project_name";
FixedValueString newProjectName;
if (SettingsRegistryMergeUtils::IsPathAncestorDescendantOrEqual(projectNameKey, path)
&& m_registry.Get(newProjectName, projectNameKey) && newProjectName != m_oldProjectName)
{
UpdateProjectSpecializationFromProjectName(newProjectName);
}
// #3 Update the ComponentApplication CommandLine instance when the command line settings are merged into the Settings Registry
// Update the ComponentApplication CommandLine instance when the command line settings are merged into the Settings Registry
if (path == AZ::SettingsRegistryMergeUtils::CommandLineValueChangedKey)
{
UpdateCommandLine();
AZ::SettingsRegistryMergeUtils::GetCommandLineFromRegistry(m_registry, m_commandLine);
}
}
//! Add the project name as a specialization underneath the /Amazon/AzCore/Settings/Specializations path
//! and remove the current project name specialization if one exists.
void UpdateProjectSpecializationFromProjectName(AZStd::string_view newProjectName)
{
using FixedValueString = AZ::SettingsRegistryInterface::FixedValueString;
// Add the project_name as a specialization for loading the build system dependency .setreg files
auto newProjectNameSpecialization = FixedValueString::format("%s/%.*s", AZ::SettingsRegistryMergeUtils::SpecializationsRootKey,
aznumeric_cast<int>(newProjectName.size()), newProjectName.data());
auto oldProjectNameSpecialization = FixedValueString::format("%s/%s", AZ::SettingsRegistryMergeUtils::SpecializationsRootKey,
m_oldProjectName.c_str());
m_registry.Remove(oldProjectNameSpecialization);
m_oldProjectName = newProjectName;
m_registry.Set(newProjectNameSpecialization, true);
}
void UpdateProjectSettingsFromProjectPath(AZ::IO::PathView newProjectPath)
{
// Update old Project path before attempting to merge in new Settings Registry values in order to prevent recursive calls
m_oldProjectPath = newProjectPath;
// Merge the project.json file into settings registry under ProjectSettingsRootKey path.
AZ::IO::FixedMaxPath projectMetadataFile{ AZ::SettingsRegistryMergeUtils::FindEngineRoot(m_registry) / newProjectPath };
projectMetadataFile /= "project.json";
m_registry.MergeSettingsFile(projectMetadataFile.Native(),
AZ::SettingsRegistryInterface::Format::JsonMergePatch, AZ::SettingsRegistryMergeUtils::ProjectSettingsRootKey);
// Update all the runtime file paths based on the new "project_path" value.
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(m_registry);
}
void UpdateCommandLine()
{
AZ::SettingsRegistryMergeUtils::GetCommandLineFromRegistry(m_registry, m_commandLine);
}
private:
AZ::IO::FixedMaxPath m_oldProjectPath;
AZ::SettingsRegistryInterface::FixedValueString m_oldProjectName;
AZ::SettingsRegistryInterface& m_registry;
AZ::CommandLine& m_commandLine;
};
@@ -462,7 +481,12 @@ namespace AZ
// 1. The 'project_path' key changes
// 2. The project specialization when the 'project-name' key changes
// 3. The ComponentApplication command line when the command line is stored to the registry
m_projectChangedHandler = m_settingsRegistry->RegisterNotifier(UpdateProjectSettingsEventHandler{ *m_settingsRegistry, m_commandLine });
m_projectPathChangedHandler = m_settingsRegistry->RegisterNotifier(ProjectPathChangedEventHandler{
*m_settingsRegistry });
m_projectNameChangedHandler = m_settingsRegistry->RegisterNotifier(ProjectNameChangedEventHandler{
*m_settingsRegistry });
m_commandLineUpdatedHandler = m_settingsRegistry->RegisterNotifier(UpdateCommandLineEventHandler{
*m_settingsRegistry, m_commandLine });
// Merge Command Line arguments
constexpr bool executeRegDumpCommands = false;
@@ -515,11 +539,12 @@ namespace AZ
Destroy();
}
// The m_projectChangedHandler stores an AZStd::function internally
// which allocates using the AZ SystemAllocator
// m_projectChangedHandler is being default value initialized
// to clear out the AZStd::function
m_projectChangedHandler = {};
// The SettingsRegistry Notify handlers stores an AZStd::function internally
// which may allocates using the AZ SystemAllocator(if the functor > 16 bytes)
// The handlers are being default value initialized to clear out the AZStd::function
m_commandLineUpdatedHandler = {};
m_projectNameChangedHandler = {};
m_projectPathChangedHandler = {};
// Delete the AZ::IConsole if it was created by this application instance
if (m_ownsConsole)
@@ -390,7 +390,9 @@ namespace AZ
AZ::IO::FixedMaxPath m_engineRoot;
AZ::IO::FixedMaxPath m_appRoot;
AZ::SettingsRegistryInterface::NotifyEventHandler m_projectChangedHandler;
AZ::SettingsRegistryInterface::NotifyEventHandler m_projectPathChangedHandler;
AZ::SettingsRegistryInterface::NotifyEventHandler m_projectNameChangedHandler;
AZ::SettingsRegistryInterface::NotifyEventHandler m_commandLineUpdatedHandler;
// ConsoleFunctorHandle is responsible for unregistering the Settings Registry Console
// from the m_console member when it goes out of scope
@@ -639,9 +639,10 @@ namespace AZ
{
// Make sure the there is a JSON object at the ConsoleRuntimeCommandKey or ConsoleAutoexecKey
// So that JSON Patch is able to add values underneath that object (JSON Patch doesn't create intermediate objects)
settingsRegistry.MergeSettings(R"({ "Amazon": { "AzCore": { "Runtime": { "ConsoleCommands": {} } } })"
R"(,"O3DE": { "Autoexec": { "ConsoleCommands": {} } } })",
SettingsRegistryInterface::Format::JsonMergePatch);
settingsRegistry.MergeSettings(R"({})", SettingsRegistryInterface::Format::JsonMergePatch,
IConsole::ConsoleRuntimeCommandKey);
settingsRegistry.MergeSettings(R"({})", SettingsRegistryInterface::Format::JsonMergePatch,
IConsole::ConsoleAutoexecCommandKey);
m_consoleCommandKeyHandler = settingsRegistry.RegisterNotifier(ConsoleCommandKeyNotificationHandler{ settingsRegistry, *this });
JsonApplyPatchSettings applyPatchSettings;
@@ -40,6 +40,12 @@ namespace AZ
static unsigned int Record(StackFrame* frames, unsigned int maxNumOfFrames, unsigned int suppressCount = 0, void* nativeThread = 0);
};
class StackConverter
{
public:
static unsigned int FromNative(StackFrame* frames, unsigned int maxNumOfFrames, void* nativeContext);
};
class SymbolStorage
{
public:
+10 -6
View File
@@ -31,6 +31,8 @@ namespace AZ
{
namespace Debug
{
struct StackFrame;
namespace Platform
{
#if defined(AZ_ENABLE_DEBUG_TOOLS)
@@ -551,17 +553,19 @@ namespace AZ
{
StackFrame frames[25];
// Without StackFrame explicit alignment frames array is aligned to 4 bytes
// which causes the stack tracing to fail.
//size_t bla = AZStd::alignment_of<StackFrame>::value;
//printf("Alignment value %d address 0x%08x : 0x%08x\n",bla,frames);
SymbolStorage::StackLine lines[AZ_ARRAY_SIZE(frames)];
unsigned int numFrames = 0;
if (!nativeContext)
{
suppressCount += 1; /// If we don't provide a context we will capture in the RecordFunction, so skip us (Trace::PrinCallstack).
suppressCount += 1; /// If we don't provide a context we will capture in the RecordFunction, so skip us (Trace::PrintCallstack).
numFrames = StackRecorder::Record(frames, AZ_ARRAY_SIZE(frames), suppressCount);
}
unsigned int numFrames = StackRecorder::Record(frames, AZ_ARRAY_SIZE(frames), suppressCount, nativeContext);
else
{
numFrames = StackConverter::FromNative(frames, AZ_ARRAY_SIZE(frames), nativeContext);
}
if (numFrames)
{
SymbolStorage::DecodeFrames(frames, numFrames, lines);
@@ -192,26 +192,34 @@ namespace AZ
//! @param path An offset at which traversal should start.
//! @return Whether or not entries could be visited.
virtual bool Visit(const VisitorCallback& callback, AZStd::string_view path) const = 0;
//! Register a callback that will be called whenever an entry gets a new/updated value.
//!
//! @callback The function to call when an entry gets a new/updated value.
[[nodiscard]] virtual NotifyEventHandler RegisterNotifier(const NotifyCallback& callback) = 0;
//! Register a callback that will be called whenever an entry gets a new/updated value.
//! @callback The function to call when an entry gets a new/updated value.
[[nodiscard]] virtual NotifyEventHandler RegisterNotifier(NotifyCallback&& callback) = 0;
//! @return NotifyEventHandler instance which must persist to receive event signal
[[nodiscard]] virtual NotifyEventHandler RegisterNotifier(NotifyCallback callback) = 0;
//! Register a notify event handler with the NotifyEvent.
//! The handler will be called whenever an entry gets a new/updated value.
//! @param handler The handler to register with the NotifyEvent.
virtual void RegisterNotifier(NotifyEventHandler& handler) = 0;
//! Register a function that will be called before a file is merged.
//! @callback The function to call before a file is merged.
[[nodiscard]] virtual PreMergeEventHandler RegisterPreMergeEvent(const PreMergeEventCallback& callback) = 0;
//! Register a function that will be called before a file is merged.
//! @callback The function to call before a file is merged.
[[nodiscard]] virtual PreMergeEventHandler RegisterPreMergeEvent(PreMergeEventCallback&& callback) = 0;
//! @param callback The function to call before a file is merged.
//! @return PreMergeEventHandler instance which must persist to receive event signal
[[nodiscard]] virtual PreMergeEventHandler RegisterPreMergeEvent(PreMergeEventCallback callback) = 0;
//! Register a pre-merge handler with the PreMergeEvent.
//! The handler will be called before a file is merged.
//! @param handler The hanlder to register with the PreMergeEvent.
virtual void RegisterPreMergeEvent(PreMergeEventHandler& handler) = 0;
//! Register a function that will be called after a file is merged.
//! @callback The function to call after a file is merged.
[[nodiscard]] virtual PostMergeEventHandler RegisterPostMergeEvent(const PostMergeEventCallback& callback) = 0;
//! Register a function that will be called after a file is merged.
//! @callback The function to call after a file is merged.
[[nodiscard]] virtual PostMergeEventHandler RegisterPostMergeEvent(PostMergeEventCallback&& callback) = 0;
//! @param callback The function to call after a file is merged.
//! @return PostMergeEventHandler instance which must persist to receive event signal
[[nodiscard]] virtual PostMergeEventHandler RegisterPostMergeEvent(PostMergeEventCallback callback) = 0;
//! Register a post-merge hahndler with the PostMergeEvent.
//! The handler will be called after a file is merged.
//! @param handler The handler to register with the PostmergeEVent.
virtual void RegisterPostMergeEvent(PostMergeEventHandler& hanlder) = 0;
//! Gets the boolean value at the provided path.
//! @param result The target to write the result to.
@@ -326,23 +334,25 @@ namespace AZ
//! - all digits and dot -> floating point number
//! - Everything else is considered a string.
//! @param argument The command line argument.
//! @param structure which contains functors which determine what characters are delimiters
//! @param anchorKey The key where the merged command line argument will be anchored under
//! @param commandLineSettings structure which contains functors which determine what characters are delimiters
//! @return True if the command line argument could be parsed, otherwise false.
virtual bool MergeCommandLineArgument(AZStd::string_view argument, AZStd::string_view rootKey = "",
virtual bool MergeCommandLineArgument(AZStd::string_view argument, AZStd::string_view anchorKey = "",
const CommandLineArgumentSettings& commandLineSettings = {}) = 0;
//! Merges the json data provided into the settings registry.
//! @param data The json data stored in a string.
//! @param format The format of the provided data.
//! @param anchorKey The key where the merged json content will be anchored under.
//! @return True if the data was successfully merged, otherwise false.
virtual bool MergeSettings(AZStd::string_view data, Format format) = 0;
virtual bool MergeSettings(AZStd::string_view data, Format format, AZStd::string_view anchorKey = "") = 0;
//! Loads a settings file and merges it into the registry.
//! @param path The path to the registry file.
//! @param format The format of the text data in the file at the provided path.
//! @param rootKey The key where the root of the settings file will be stored under.
//! @param anchorKey The key where the content of the settings file will be anchored.
//! @param scratchBuffer An optional buffer that's used to load the file into. Use this when loading multiple patches to
//! reduce the number of intermediate memory allocations.
//! @return True if the registry file was successfully merged, otherwise false.
virtual bool MergeSettingsFile(AZStd::string_view path, Format format, AZStd::string_view rootKey = "",
virtual bool MergeSettingsFile(AZStd::string_view path, Format format, AZStd::string_view anchorKey = "",
AZStd::vector<char>* scratchBuffer = nullptr) = 0;
//! Loads all settings files in a folder and merges them into the registry.
//! With the specializations "a" and "b" and platform "c" the files would be loaded in the order:
@@ -357,11 +367,12 @@ namespace AZ
//! @param platform An optional name of a platform. Platform overloads are located at <path>/Platform/<platform>/
//! Files in a platform are applied in the same order as for the main folder but always after the same file
//! in the main folder.
//! @param anchorKey The registry path location where the settings will be anchored
//! @param scratchBuffer An optional buffer that's used to load the file into. Use this when loading multiple patches to
//! reduce the number of intermediate memory allocations.
//! @return True if the registry folder was successfully merged, otherwise false.
virtual bool MergeSettingsFolder(AZStd::string_view path, const Specializations& specializations,
AZStd::string_view platform = {}, AZStd::string_view rootKey = "", AZStd::vector<char>* scratchBuffer = nullptr) = 0;
AZStd::string_view platform = {}, AZStd::string_view anchorKey = "", AZStd::vector<char>* scratchBuffer = nullptr) = 0;
//! Stores the settings structure which is used when merging settings to the Settings Registry
//! using JSON Merge Patch or JSON Merge Patch.
@@ -13,7 +13,7 @@
#include <AzCore/IO/FileReader.h>
#include <AzCore/IO/Path/Path.h>
#include <AzCore/JSON/error/en.h>
#include <AzCore/NativeUI//NativeUIRequests.h>
#include <AzCore/NativeUI/NativeUIRequests.h>
#include <AzCore/Serialization/Json/JsonSerialization.h>
#include <AzCore/Serialization/Json/StackedString.h>
#include <AzCore/Settings/SettingsRegistryImpl.h>
@@ -21,6 +21,34 @@
#include <AzCore/std/sort.h>
#include <AzCore/std/parallel/scoped_lock.h>
namespace AZ::SettingsRegistryImplInternal
{
AZ::SettingsRegistryInterface::Type RapidjsonToSettingsRegistryType(const rapidjson::Value& value)
{
using Type = AZ::SettingsRegistryInterface::Type;
switch (value.GetType())
{
case rapidjson::Type::kNullType:
return Type::Null;
case rapidjson::Type::kFalseType:
return Type::Boolean;
case rapidjson::Type::kTrueType:
return Type::Boolean;
case rapidjson::Type::kObjectType:
return Type::Object;
case rapidjson::Type::kArrayType:
return Type::Array;
case rapidjson::Type::kStringType:
return Type::String;
case rapidjson::Type::kNumberType:
return value.IsDouble() ? Type::FloatingPoint :
Type::Integer;
}
return Type::NoType;
}
}
namespace AZ
{
template<typename T>
@@ -28,7 +56,7 @@ namespace AZ
{
if (path.empty())
{
// rapidjson::Pointer assets that the supplied string
// rapidjson::Pointer asserts that the supplied string
// is not nullptr even if the supplied size is 0
// Setting to empty string to prevent assert
path = "";
@@ -70,7 +98,7 @@ namespace AZ
{
if (path.empty())
{
// rapidjson::Pointer assets that the supplied string
// rapidjson::Pointer asserts that the supplied string
// is not nullptr even if the supplied size is 0
// Setting to empty string to prevent assert
path = "";
@@ -161,7 +189,7 @@ namespace AZ
{
if (path.empty())
{
// rapidjson::Pointer assets that the supplied string
// rapidjson::Pointer asserts that the supplied string
// is not nullptr even if the supplied size is 0
// Setting to empty string to prevent assert
path = "";
@@ -212,17 +240,7 @@ namespace AZ
return Visit(visitor, path);
}
auto SettingsRegistryImpl::RegisterNotifier(const NotifyCallback& callback) -> NotifyEventHandler
{
NotifyEventHandler notifyHandler{ callback };
{
AZStd::scoped_lock lock(m_notifierMutex);
notifyHandler.Connect(m_notifiers);
}
return notifyHandler;
}
auto SettingsRegistryImpl::RegisterNotifier(NotifyCallback&& callback) -> NotifyEventHandler
auto SettingsRegistryImpl::RegisterNotifier(NotifyCallback callback) -> NotifyEventHandler
{
NotifyEventHandler notifyHandler{ AZStd::move(callback) };
{
@@ -232,23 +250,19 @@ namespace AZ
return notifyHandler;
}
auto SettingsRegistryImpl::RegisterNotifier(NotifyEventHandler& notifyHandler) -> void
{
AZStd::scoped_lock lock(m_notifierMutex);
notifyHandler.Connect(m_notifiers);
}
void SettingsRegistryImpl::ClearNotifiers()
{
AZStd::scoped_lock lock(m_notifierMutex);
m_notifiers.DisconnectAllHandlers();
}
auto SettingsRegistryImpl::RegisterPreMergeEvent(const PreMergeEventCallback& callback) -> PreMergeEventHandler
{
PreMergeEventHandler preMergeHandler{ callback };
{
AZStd::scoped_lock lock(m_settingMutex);
preMergeHandler.Connect(m_preMergeEvent);
}
return preMergeHandler;
}
auto SettingsRegistryImpl::RegisterPreMergeEvent(PreMergeEventCallback&& callback) -> PreMergeEventHandler
auto SettingsRegistryImpl::RegisterPreMergeEvent(PreMergeEventCallback callback) -> PreMergeEventHandler
{
PreMergeEventHandler preMergeHandler{ AZStd::move(callback) };
{
@@ -258,17 +272,13 @@ namespace AZ
return preMergeHandler;
}
auto SettingsRegistryImpl::RegisterPostMergeEvent(const PostMergeEventCallback& callback) -> PostMergeEventHandler
auto SettingsRegistryImpl::RegisterPreMergeEvent(PreMergeEventHandler& preMergeHandler) -> void
{
PostMergeEventHandler postMergeHandler{ callback };
{
AZStd::scoped_lock lock(m_settingMutex);
postMergeHandler.Connect(m_postMergeEvent);
}
return postMergeHandler;
AZStd::scoped_lock lock(m_settingMutex);
preMergeHandler.Connect(m_preMergeEvent);
}
auto SettingsRegistryImpl::RegisterPostMergeEvent(PostMergeEventCallback&& callback) -> PostMergeEventHandler
auto SettingsRegistryImpl::RegisterPostMergeEvent(PostMergeEventCallback callback) -> PostMergeEventHandler
{
PostMergeEventHandler postMergeHandler{ AZStd::move(callback) };
{
@@ -278,6 +288,12 @@ namespace AZ
return postMergeHandler;
}
auto SettingsRegistryImpl::RegisterPostMergeEvent(PostMergeEventHandler& postMergeHandler) -> void
{
AZStd::scoped_lock lock(m_settingMutex);
postMergeHandler.Connect(m_postMergeEvent);
}
void SettingsRegistryImpl::ClearMergeEvents()
{
AZStd::scoped_lock lock(m_settingMutex);
@@ -297,7 +313,36 @@ namespace AZ
localNotifierEvent = AZStd::move(m_notifiers);
}
localNotifierEvent.Signal(jsonPath, type);
// Signal the NotifyEvent for each queued argument
decltype(m_signalNotifierQueue) localNotifierQueue;
{
AZStd::scoped_lock signalLock(m_signalMutex);
m_signalNotifierQueue.push_back({ FixedValueString{jsonPath}, type });
// If the signal count was 0, then a dispatch is in progress
if (m_signalCount++ == 0)
{
AZStd::swap(localNotifierQueue, m_signalNotifierQueue);
}
}
while (!localNotifierQueue.empty())
{
for (SignalNotifierArgs notifierArgs : localNotifierQueue)
{
localNotifierEvent.Signal(notifierArgs.m_jsonPath, notifierArgs.m_type);
}
// Clear the local notifier queue and check if more notifiers have been added
localNotifierQueue = {};
{
AZStd::scoped_lock signalLock(m_signalMutex);
AZStd::swap(localNotifierQueue, m_signalNotifierQueue);
}
}
{
AZStd::scoped_lock signalLock(m_signalMutex);
--m_signalCount;
}
{
// Swap the local handlers with the current m_notifiers which
@@ -314,39 +359,19 @@ namespace AZ
{
if (path.empty())
{
//rapidjson::Pointer assets that the supplied string
//rapidjson::Pointer asserts that the supplied string
// is not nullptr even if the supplied size is 0
// Setting to empty string to prevent assert
path = "";
}
rapidjson::Pointer pointer(path.data(), path.length());
if (pointer.IsValid())
{
AZStd::scoped_lock lock(m_settingMutex);
const rapidjson::Value* value = pointer.Get(m_settings);
if (value)
if (const rapidjson::Value* value = pointer.Get(m_settings); value != nullptr)
{
switch (value->GetType())
{
case rapidjson::Type::kNullType:
return Type::Null;
case rapidjson::Type::kFalseType:
return Type::Boolean;
case rapidjson::Type::kTrueType:
return Type::Boolean;
case rapidjson::Type::kObjectType:
return Type::Object;
case rapidjson::Type::kArrayType:
return Type::Array;
case rapidjson::Type::kStringType:
return Type::String;
case rapidjson::Type::kNumberType:
return
value->IsDouble() ? Type::FloatingPoint :
Type::Integer;
}
return SettingsRegistryImplInternal::RapidjsonToSettingsRegistryType(*value);
}
}
return Type::NoType;
@@ -392,7 +417,7 @@ namespace AZ
{
if (path.empty())
{
// rapidjson::Pointer assets that the supplied string
// rapidjson::Pointer asserts that the supplied string
// is not nullptr even if the supplied size is 0
// Setting to empty string to prevent assert
path = "";
@@ -471,13 +496,12 @@ namespace AZ
{
if (path.empty())
{
//rapidjson::Pointer assets that the supplied string
// rapidjson::Pointer asserts that the supplied string
// is not nullptr even if the supplied size is 0
// Setting to empty string to prevent assert
path = "";
}
rapidjson::Pointer pointer(path.data(), path.length());
if (pointer.IsValid())
{
@@ -486,10 +510,14 @@ namespace AZ
value, nullptr, valueTypeID, m_serializationSettings);
if (jsonResult.GetProcessing() != JsonSerializationResult::Processing::Halted)
{
AZStd::scoped_lock lock(m_settingMutex);
rapidjson::Value& setting = pointer.Create(m_settings, m_settings.GetAllocator());
setting = AZStd::move(store);
SignalNotifier(path, Type::Object);
auto anchorType = Type::NoType;
{
AZStd::scoped_lock lock(m_settingMutex);
rapidjson::Value& setting = pointer.Create(m_settings, m_settings.GetAllocator());
setting = AZStd::move(store);
anchorType = SettingsRegistryImplInternal::RapidjsonToSettingsRegistryType(setting);
}
SignalNotifier(path, anchorType);
return true;
}
}
@@ -500,7 +528,7 @@ namespace AZ
{
if (path.empty())
{
// rapidjson::Pointer assets that the supplied string
// rapidjson::Pointer asserts that the supplied string
// is not nullptr even if the supplied size is 0
// Setting to empty string to prevent assert
path = "";
@@ -605,7 +633,7 @@ namespace AZ
return Set(key, value);
}
bool SettingsRegistryImpl::MergeSettings(AZStd::string_view data, Format format)
bool SettingsRegistryImpl::MergeSettings(AZStd::string_view data, Format format, AZStd::string_view anchorKey)
{
rapidjson::Document jsonPatch;
constexpr int flags = rapidjson::kParseStopWhenDoneFlag | rapidjson::kParseCommentsFlag | rapidjson::kParseTrailingCommasFlag;
@@ -631,17 +659,43 @@ namespace AZ
return false;
}
AZStd::scoped_lock lock(m_settingMutex);
JsonSerializationResult::ResultCode mergeResult =
JsonSerialization::ApplyPatch(m_settings, m_settings.GetAllocator(), jsonPatch, mergeApproach);
if (mergeResult.GetProcessing() != JsonSerializationResult::Processing::Completed)
rapidjson::Pointer anchorPath;
if (!anchorKey.empty())
{
AZ_Error("Settings Registry", false, "Failed to fully merge data into registry.");
return false;
anchorPath = rapidjson::Pointer(anchorKey.data(), anchorKey.size());
if (!anchorPath.IsValid())
{
rapidjson::Pointer pointer(AZ_SETTINGS_REGISTRY_HISTORY_KEY "/-");
AZ_Error("Settings Registry", false, R"(Anchor path "%.*s" is invalid.)", AZ_STRING_ARG(anchorKey));
AZStd::scoped_lock lock(m_settingMutex);
pointer.Create(m_settings, m_settings.GetAllocator()).SetObject()
.AddMember(rapidjson::StringRef("Error"), rapidjson::StringRef("Invalid anchor key."), m_settings.GetAllocator())
.AddMember(rapidjson::StringRef("Path"),
rapidjson::Value(anchorKey.data(), aznumeric_caster(anchorKey.size()), m_settings.GetAllocator()),
m_settings.GetAllocator());
return false;
}
}
SignalNotifier("", Type::Object);
auto anchorType = AZ::SettingsRegistryInterface::Type::NoType;
{
AZStd::scoped_lock lock(m_settingMutex);
rapidjson::Value& anchorRoot = anchorPath.IsValid() ? anchorPath.Create(m_settings, m_settings.GetAllocator())
: m_settings;
JsonSerializationResult::ResultCode mergeResult =
JsonSerialization::ApplyPatch(anchorRoot, m_settings.GetAllocator(), jsonPatch, mergeApproach);
if (mergeResult.GetProcessing() != JsonSerializationResult::Processing::Completed)
{
AZ_Error("Settings Registry", false, "Failed to fully merge data into registry.");
return false;
}
// The settings have been successfully merged, query the type at the anchor key
anchorType = SettingsRegistryImplInternal::RapidjsonToSettingsRegistryType(anchorRoot);
}
SignalNotifier(anchorKey, anchorType);
return true;
}
@@ -1225,10 +1279,12 @@ namespace AZ
ScopedMergeEvent scopedMergeEvent(m_preMergeEvent, m_postMergeEvent, path, rootKey);
JsonSerializationResult::ResultCode mergeResult(JsonSerializationResult::Tasks::Merge);
auto anchorType = Type::NoType;
if (rootKey.empty())
{
AZStd::scoped_lock lock(m_settingMutex);
mergeResult = JsonSerialization::ApplyPatch(m_settings, m_settings.GetAllocator(), jsonPatch, mergeApproach, m_applyPatchSettings);
anchorType = SettingsRegistryImplInternal::RapidjsonToSettingsRegistryType(m_settings);
}
else
{
@@ -1238,6 +1294,7 @@ namespace AZ
AZStd::scoped_lock lock(m_settingMutex);
Value& rootValue = root.Create(m_settings, m_settings.GetAllocator());
mergeResult = JsonSerialization::ApplyPatch(rootValue, m_settings.GetAllocator(), jsonPatch, mergeApproach, m_applyPatchSettings);
anchorType = SettingsRegistryImplInternal::RapidjsonToSettingsRegistryType(rootValue);
}
else
{
@@ -1265,7 +1322,7 @@ namespace AZ
pointer.Create(m_settings, m_settings.GetAllocator()).SetString(path, m_settings.GetAllocator());
}
SignalNotifier("", Type::Object);
SignalNotifier(rootKey, anchorType);
return true;
}
@@ -48,14 +48,14 @@ namespace AZ
Type GetType(AZStd::string_view path) const override;
bool Visit(Visitor& visitor, AZStd::string_view path) const override;
bool Visit(const VisitorCallback& callback, AZStd::string_view path) const override;
[[nodiscard]] NotifyEventHandler RegisterNotifier(const NotifyCallback& callback) override;
[[nodiscard]] NotifyEventHandler RegisterNotifier(NotifyCallback&& callback) override;
[[nodiscard]] NotifyEventHandler RegisterNotifier(NotifyCallback callback) override;
void RegisterNotifier(NotifyEventHandler& hanlder) override;
void ClearNotifiers();
[[nodiscard]] PreMergeEventHandler RegisterPreMergeEvent(const PreMergeEventCallback& callback) override;
[[nodiscard]] PreMergeEventHandler RegisterPreMergeEvent(PreMergeEventCallback&& callback) override;
[[nodiscard]] PostMergeEventHandler RegisterPostMergeEvent(const PostMergeEventCallback& callback) override;
[[nodiscard]] PostMergeEventHandler RegisterPostMergeEvent(PostMergeEventCallback&& callback) override;
[[nodiscard]] PreMergeEventHandler RegisterPreMergeEvent(PreMergeEventCallback callback) override;
void RegisterPreMergeEvent(PreMergeEventHandler& handler) override;
[[nodiscard]] PostMergeEventHandler RegisterPostMergeEvent(PostMergeEventCallback callback) override;
void RegisterPostMergeEvent(PostMergeEventHandler& handler) override;
void ClearMergeEvents();
bool Get(bool& result, AZStd::string_view path) const override;
@@ -76,13 +76,13 @@ namespace AZ
bool Remove(AZStd::string_view path) override;
bool MergeCommandLineArgument(AZStd::string_view argument, AZStd::string_view rootKey,
bool MergeCommandLineArgument(AZStd::string_view argument, AZStd::string_view anchorKey,
const CommandLineArgumentSettings& commandLineSettings) override;
bool MergeSettings(AZStd::string_view data, Format format) override;
bool MergeSettingsFile(AZStd::string_view path, Format format, AZStd::string_view rootKey,
bool MergeSettings(AZStd::string_view data, Format format, AZStd::string_view anchorKey = "") override;
bool MergeSettingsFile(AZStd::string_view path, Format format, AZStd::string_view anchorKey = "",
AZStd::vector<char>* scratchBuffer = nullptr) override;
bool MergeSettingsFolder(AZStd::string_view path, const Specializations& specializations,
AZStd::string_view platform, AZStd::string_view rootKey = "", AZStd::vector<char>* scratchBuffer = nullptr) override;
AZStd::string_view platform, AZStd::string_view anchorKey = "", AZStd::vector<char>* scratchBuffer = nullptr) override;
void SetApplyPatchSettings(const AZ::JsonApplyPatchSettings& applyPatchSettings) override;
void GetApplyPatchSettings(AZ::JsonApplyPatchSettings& applyPatchSettings) override;
@@ -121,6 +121,19 @@ namespace AZ
PreMergeEvent m_preMergeEvent;
PostMergeEvent m_postMergeEvent;
//! NOTE: During SignalNotifier, the registered notify event handlers are moved to a local NotifyEvent
//! Therefore setting a value within the registry during signaling will queue future SignalNotifer calls
//! These calls will then be invoked after the current signaling has completex
//! This is done to avoid deadlock if another thread attempts to access register a notifier or signal one
mutable AZStd::mutex m_signalMutex;
struct SignalNotifierArgs
{
FixedValueString m_jsonPath;
Type m_type;
};
AZStd::deque<SignalNotifierArgs> m_signalNotifierQueue;
AZStd::atomic_int m_signalCount{};
rapidjson::Document m_settings;
JsonSerializerSettings m_serializationSettings;
JsonDeserializerSettings m_deserializationSettings;
@@ -23,12 +23,12 @@ namespace AZ
MOCK_CONST_METHOD1(GetType, Type(AZStd::string_view));
MOCK_CONST_METHOD2(Visit, bool(Visitor&, AZStd::string_view));
MOCK_CONST_METHOD2(Visit, bool(const VisitorCallback&, AZStd::string_view));
MOCK_METHOD1(RegisterNotifier, NotifyEventHandler(const NotifyCallback&));
MOCK_METHOD1(RegisterNotifier, NotifyEventHandler(NotifyCallback&&));
MOCK_METHOD1(RegisterPreMergeEvent, PreMergeEventHandler(const PreMergeEventCallback&));
MOCK_METHOD1(RegisterPreMergeEvent, PreMergeEventHandler(PreMergeEventCallback&&));
MOCK_METHOD1(RegisterPostMergeEvent, PostMergeEventHandler(const PostMergeEventCallback&));
MOCK_METHOD1(RegisterPostMergeEvent, PostMergeEventHandler(PostMergeEventCallback&&));
MOCK_METHOD1(RegisterNotifier, NotifyEventHandler(NotifyCallback));
MOCK_METHOD1(RegisterNotifier, void(NotifyEventHandler&));
MOCK_METHOD1(RegisterPreMergeEvent, PreMergeEventHandler(PreMergeEventCallback));
MOCK_METHOD1(RegisterPreMergeEvent, void(PreMergeEventHandler&));
MOCK_METHOD1(RegisterPostMergeEvent, PostMergeEventHandler(PostMergeEventCallback));
MOCK_METHOD1(RegisterPostMergeEvent, void(PostMergeEventHandler&));
MOCK_CONST_METHOD2(Get, bool(bool&, AZStd::string_view));
MOCK_CONST_METHOD2(Get, bool(s64&, AZStd::string_view));
@@ -49,7 +49,7 @@ namespace AZ
MOCK_METHOD1(Remove, bool(AZStd::string_view));
MOCK_METHOD3(MergeCommandLineArgument, bool(AZStd::string_view, AZStd::string_view, const CommandLineArgumentSettings&));
MOCK_METHOD2(MergeSettings, bool(AZStd::string_view, Format));
MOCK_METHOD3(MergeSettings, bool(AZStd::string_view, Format, AZStd::string_view));
MOCK_METHOD4(MergeSettingsFile, bool(AZStd::string_view, Format, AZStd::string_view, AZStd::vector<char>*));
MOCK_METHOD5(
MergeSettingsFolder,
@@ -17,6 +17,11 @@ namespace AZ
return false;
}
unsigned int StackConverter::FromNative(StackFrame*, unsigned int, void*)
{
return 0;
}
void SymbolStorage::LoadModuleData(const void*, unsigned int)
{}
@@ -78,6 +78,12 @@ StackRecorder::Record(StackFrame* frames, unsigned int maxNumOfFrames, unsigned
return count;
}
unsigned int StackConverter::FromNative([[maybe_unused]] StackFrame* frames, [[maybe_unused]] unsigned int maxNumOfFrames, [[maybe_unused]] void* nativeContext)
{
AZ_Assert(false, "StackConverter::FromNative() is not supported for UnixLike platform yet");
return 0;
}
void
SymbolStorage::DecodeFrames(const StackFrame* frames, unsigned int numFrames, StackLine* textLines)
{
@@ -1048,9 +1048,9 @@ cleanup:
unsigned int
StackRecorder::Record(StackFrame* frames, unsigned int maxNumOfFrames, unsigned int suppressCount, void* nativeThread)
{
#if defined(AZ_ENABLE_DEBUG_TOOLS)
unsigned int numFrames = 0;
#if defined(AZ_ENABLE_DEBUG_TOOLS)
if (nativeThread == NULL)
{
++suppressCount; // Skip current call
@@ -1079,9 +1079,8 @@ cleanup:
STACKFRAME64 sf;
memset(&sf, 0, sizeof(STACKFRAME64));
DWORD imageType;
DWORD imageType = IMAGE_FILE_MACHINE_AMD64;
imageType = IMAGE_FILE_MACHINE_AMD64;
sf.AddrPC.Offset = context.Rip;
sf.AddrPC.Mode = AddrModeFlat;
sf.AddrFrame.Offset = context.Rsp;
@@ -1090,8 +1089,7 @@ cleanup:
sf.AddrStack.Mode = AddrModeFlat;
EnterCriticalSection(&g_csDbgHelpDll);
s32 frame = -(s32)suppressCount;
for (; frame < (s32)maxNumOfFrames; ++frame)
for (s32 frame = -static_cast<s32>(suppressCount); frame < static_cast<s32>(maxNumOfFrames); ++frame)
{
if (!g_StackWalk64(imageType, g_currentProcess, hThread, &sf, &context, 0, g_SymFunctionTableAccess64, g_SymGetModuleBase64, 0))
{
@@ -1111,15 +1109,68 @@ cleanup:
}
LeaveCriticalSection(&g_csDbgHelpDll);
}
return numFrames;
}
#else
(void)frames;
(void)maxNumOfFrames;
(void)suppressCount;
(void)nativeThread;
return 0;
AZ_UNUSED(frames);
AZ_UNUSED(maxNumOfFrames);
AZ_UNUSED(suppressCount);
AZ_UNUSED(nativeThread);
#endif // AZ_ENABLE_DEBUG_TOOLS
return numFrames;
}
unsigned int StackConverter::FromNative(StackFrame* frames, unsigned int maxNumOfFrames, void* nativeContext)
{
unsigned int numFrames = 0;
#if defined(AZ_ENABLE_DEBUG_TOOLS)
if (!g_dbgHelpLoaded)
{
LoadDbgHelp();
}
HANDLE hThread;
DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), GetCurrentProcess(), &hThread, 0, false, DUPLICATE_SAME_ACCESS);
PCONTEXT nativeContextType = reinterpret_cast<PCONTEXT>(nativeContext);
STACKFRAME64 sf;
memset(&sf, 0, sizeof(STACKFRAME64));
DWORD imageType = IMAGE_FILE_MACHINE_AMD64;
sf.AddrPC.Offset = nativeContextType->Rip;
sf.AddrPC.Mode = AddrModeFlat;
sf.AddrFrame.Offset = nativeContextType->Rsp;
sf.AddrFrame.Mode = AddrModeFlat;
sf.AddrStack.Offset = nativeContextType->Rsp;
sf.AddrStack.Mode = AddrModeFlat;
EnterCriticalSection(&g_csDbgHelpDll);
for (unsigned int frame = 0; frame < maxNumOfFrames; ++frame)
{
if (!g_StackWalk64(imageType, g_currentProcess, hThread, &sf, nativeContext, 0, g_SymFunctionTableAccess64, g_SymGetModuleBase64, 0))
{
break;
}
if (sf.AddrPC.Offset == sf.AddrReturn.Offset)
{
// "StackWalk64-Endless-Callstack!"
break;
}
frames[numFrames++].m_programCounter = sf.AddrPC.Offset;
}
LeaveCriticalSection(&g_csDbgHelpDll);
#else
AZ_UNUSED(frames);
AZ_UNUSED(maxNumOfFrames);
AZ_UNUSED(nativeContext);
#endif
return numFrames;
}
//////////////////////////////////////////////////////////////////////////
+4 -4
View File
@@ -1413,7 +1413,7 @@ namespace UnitTest
>;
TYPED_TEST_CASE(HashedSetDifferentAllocatorFixture, SetTemplateConfigs);
#if GTEST_OS_SUPPORTS_DEATH_TEST
#if GTEST_HAS_DEATH_TEST
TYPED_TEST(HashedSetDifferentAllocatorFixture, InsertNodeHandleWithDifferentAllocatorsLogsTraceMessages)
{
using ContainerType = typename TypeParam::ContainerType;
@@ -1435,7 +1435,7 @@ namespace UnitTest
}
}, ".*");
}
#endif // GTEST_OS_SUPPORTS_DEATH_TEST
#endif // GTEST_HAS_DEATH_TEST
template<typename ContainerType>
class HashedMapContainers
@@ -1811,7 +1811,7 @@ namespace UnitTest
>;
TYPED_TEST_CASE(HashedMapDifferentAllocatorFixture, MapTemplateConfigs);
#if GTEST_OS_SUPPORTS_DEATH_TEST
#if GTEST_HAS_DEATH_TEST
TYPED_TEST(HashedMapDifferentAllocatorFixture, InsertNodeHandleWithDifferentAllocatorsLogsTraceMessages)
{
using ContainerType = typename TypeParam::ContainerType;
@@ -1833,7 +1833,7 @@ namespace UnitTest
}
} , ".*");
}
#endif // GTEST_OS_SUPPORTS_DEATH_TEST
#endif // GTEST_HAS_DEATH_TEST
namespace HashedContainerTransparentTestInternal
{
@@ -1095,7 +1095,7 @@ namespace UnitTest
>;
TYPED_TEST_CASE(TreeSetDifferentAllocatorFixture, SetTemplateConfigs);
#if GTEST_OS_SUPPORTS_DEATH_TEST
#if GTEST_HAS_DEATH_TEST
TYPED_TEST(TreeSetDifferentAllocatorFixture, InsertNodeHandleWithDifferentAllocatorsLogsTraceMessages)
{
using ContainerType = typename TypeParam::ContainerType;
@@ -1117,7 +1117,7 @@ namespace UnitTest
}
}, ".*");
}
#endif // GTEST_OS_SUPPORTS_DEATH_TEST
#endif // GTEST_HAS_DEATH_TEST
TYPED_TEST(TreeSetDifferentAllocatorFixture, SwapMovesElementsWhenAllocatorsDiffer)
{
@@ -1516,7 +1516,7 @@ namespace UnitTest
>;
TYPED_TEST_CASE(TreeMapDifferentAllocatorFixture, MapTemplateConfigs);
#if GTEST_OS_SUPPORTS_DEATH_TEST
#if GTEST_HAS_DEATH_TEST
TYPED_TEST(TreeMapDifferentAllocatorFixture, InsertNodeHandleWithDifferentAllocatorsLogsTraceMessages)
{
using ContainerType = typename TypeParam::ContainerType;
@@ -1538,7 +1538,7 @@ namespace UnitTest
}
}, ".*");
}
#endif // GTEST_OS_SUPPORTS_DEATH_TEST
#endif // GTEST_HAS_DEATH_TEST
TYPED_TEST(TreeMapDifferentAllocatorFixture, SwapMovesElementsWhenAllocatorsDiffer)
{
@@ -1595,7 +1595,7 @@ namespace UnitTest
}
};
#if GTEST_OS_SUPPORTS_DEATH_TEST
#if GTEST_HAS_DEATH_TEST
TEST_F(ThreadEventsDeathTest, UsingClientBus_AvoidsDeadlock)
{
EXPECT_EXIT(
@@ -1608,5 +1608,5 @@ namespace UnitTest
, ::testing::ExitedWithCode(0),".*");
}
#endif // GTEST_OS_SUPPORTS_DEATH_TEST
#endif // GTEST_HAS_DEATH_TEST
}
@@ -0,0 +1,31 @@
/*
* 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/UnitTest/TestTypes.h>
namespace UnitTest
{
class UnhandledExceptions
: public ScopedAllocatorSetupFixture
{
public:
void causeAccessViolation()
{
int* someVariable = reinterpret_cast<int*>(0);
*someVariable = 0;
}
};
#if GTEST_HAS_DEATH_TEST
TEST_F(UnhandledExceptions, Handle)
{
EXPECT_DEATH(causeAccessViolation(), "");
}
#endif
}
@@ -144,14 +144,13 @@ namespace UnitTest
}
};
#if GTEST_OS_SUPPORTS_DEATH_TEST
// SPEC-2669: Disabled since it is causing hangs on Linux
#if GTEST_HAS_DEATH_TEST
TEST_F(AllocatorsTestFixtureLeakDetectionDeathTest_SKIPCODECOVERAGE, AllocatorLeak)
{
// testing that the TraceBusHook will fail on cause the test to die
EXPECT_DEATH(TestAllocatorLeak(), "");
}
#endif // GTEST_OS_SUPPORTS_DEATH_TEST
#endif // GTEST_HAS_DEATH_TEST
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Testing ScopedAllocatorSetupFixture. Testing that detects leaks
@@ -327,7 +327,7 @@ namespace JsonSerializationTests
SerializerWithOneType::Unreflect(m_jsonRegistrationContext.get());
}
#if GTEST_OS_SUPPORTS_DEATH_TEST
#if GTEST_HAS_DEATH_TEST
using JsonSerializationDeathTests = JsonRegistrationContextTests;
TEST_F(JsonSerializationDeathTests, DoubleUnregisterSerializer_Asserts)
{
@@ -338,5 +338,6 @@ namespace JsonSerializationTests
}, ".*"
);
}
#endif // GTEST_OS_SUPPORTS_DEATH_TEST
#endif // GTEST_HAS_DEATH_TEST
} //namespace JsonSerializationTests
@@ -1299,6 +1299,35 @@ namespace SettingsRegistryTests
EXPECT_FALSE(m_registry->MergeCommandLineArgument(" ", {}, {}));
}
//
// MergeSettings
//
TEST_F(SettingsRegistryTest, MergeSettings_MergeJsonWithAnchorKey_StoresSettingsUnderneathKey)
{
constexpr AZStd::string_view anchorKey = "/Anchor/Root/0";
constexpr auto mergeFormat = AZ::SettingsRegistryInterface::Format::JsonMergePatch;
EXPECT_TRUE(m_registry->MergeSettings(R"({ "Test": "1" })", mergeFormat, anchorKey));
EXPECT_EQ(AZ::SettingsRegistryInterface::Type::Array, m_registry->GetType("/Anchor/Root"));
EXPECT_EQ(AZ::SettingsRegistryInterface::Type::Object, m_registry->GetType("/Anchor/Root/0"));
EXPECT_EQ(AZ::SettingsRegistryInterface::Type::String, m_registry->GetType("/Anchor/Root/0/Test"));
}
TEST_F(SettingsRegistryTest, MergeSettings_NotifierSignals_AtAnchorKeyAndStoresMergeType)
{
AZStd::string_view anchorKey = "/Anchor/Root";
bool callbackInvoked{};
auto callback = [anchorKey, &callbackInvoked](AZStd::string_view path, AZ::SettingsRegistryInterface::Type type)
{
EXPECT_EQ(anchorKey, path);
EXPECT_EQ(AZ::SettingsRegistryInterface::Type::Array, type);
callbackInvoked = true;
};
auto testNotifier1 = m_registry->RegisterNotifier(callback);
constexpr auto mergeFormat = AZ::SettingsRegistryInterface::Format::JsonMergePatch;
EXPECT_TRUE(m_registry->MergeSettings(R"([ "Test" ])", mergeFormat, anchorKey));
EXPECT_TRUE(callbackInvoked);
}
//
// MergeSettingsFile
//
@@ -1331,7 +1360,7 @@ namespace SettingsRegistryTests
auto callback = [this](AZStd::string_view path, AZ::SettingsRegistryInterface::Type)
{
EXPECT_TRUE(path.empty());
EXPECT_EQ("/Path", path);
AZ::s64 value = -1;
bool result = m_registry->Get(value, "/Path/Test");
EXPECT_TRUE(result);
@@ -72,6 +72,7 @@ set(FILES
Debug/AssetTracking.cpp
Debug/LocalFileEventLoggerTests.cpp
Debug/Trace.cpp
Debug/UnhandledExceptions.cpp
Name/NameJsonSerializerTests.cpp
Name/NameTests.cpp
RTTI/TypeSafeIntegralTests.cpp
@@ -1924,13 +1924,11 @@ namespace AZ::IO
ArchiveLocationPriority Archive::GetPakPriority() const
{
int pakPriority = aznumeric_cast<int>(ArchiveVars{}.nPriority);
#if defined(AZ_ENABLE_TRACING)
if (auto console = AZ::Interface<AZ::IConsole>::Get(); console != nullptr)
{
AZ::GetValueResult getCvarResult = console->GetCvarValue("sys_PakPriority", pakPriority);
[[maybe_unused]] AZ::GetValueResult getCvarResult = console->GetCvarValue("sys_PakPriority", pakPriority);
AZ_Error("Archive", getCvarResult == AZ::GetValueResult::Success, "Lookup of 'sys_PakPriority console variable failed with error %s", AZ::GetEnumString(getCvarResult));
}
#endif
return static_cast<ArchiveLocationPriority>(pakPriority);
}
@@ -30,22 +30,42 @@ namespace AzFramework
//////////////////////////////////////////////////////////////////////////
// OnSessionHealthCheck is fired in health check process
// @return The result of all OnSessionHealthCheck
// Use this notification to perform any custom health check
// @return True if OnSessionHealthCheck succeeds, false otherwise
virtual bool OnSessionHealthCheck() = 0;
// OnCreateSessionBegin is fired at the beginning of session creation
// OnCreateSessionBegin is fired at the beginning of session creation process
// Use this notification to perform any necessary configuration or initialization before
// creating session
// @param sessionConfig The properties to describe a session
// @return The result of all OnCreateSessionBegin notifications
// @return True if OnCreateSessionBegin succeeds, false otherwise
virtual bool OnCreateSessionBegin(const SessionConfig& sessionConfig) = 0;
// OnDestroySessionBegin is fired at the beginning of session termination
// @return The result of all OnDestroySessionBegin notifications
// OnCreateSessionEnd is fired at the end of session creation process
// Use this notification to perform any follow-up operation after session is created and active
virtual void OnCreateSessionEnd() = 0;
// OnDestroySessionBegin is fired at the beginning of session termination process
// Use this notification to perform any cleanup operation before destroying session,
// like gracefully disconnect players, cleanup data, etc.
// @return True if OnDestroySessionBegin succeeds, false otherwise
virtual bool OnDestroySessionBegin() = 0;
// OnUpdateSessionBegin is fired at the beginning of session update
// OnDestroySessionEnd is fired at the end of session termination process
// Use this notification to perform any follow-up operation after session is destroyed,
// like shutdown application process, etc.
virtual void OnDestroySessionEnd() = 0;
// OnUpdateSessionBegin is fired at the beginning of session update process
// Use this notification to perform any configuration or initialization to handle
// the session settings changing
// @param sessionConfig The properties to describe a session
// @param updateReason The reason for session update
virtual void OnUpdateSessionBegin(const SessionConfig& sessionConfig, const AZStd::string& updateReason) = 0;
// OnUpdateSessionBegin is fired at the end of session update process
// Use this notification to perform any follow-up operations after session is updated
virtual void OnUpdateSessionEnd() = 0;
};
using SessionNotificationBus = AZ::EBus<SessionNotifications>;
} // namespace AzFramework
@@ -23,10 +23,12 @@ namespace AzFramework
virtual ~XcbEventHandler() = default;
virtual void HandleXcbEvent(xcb_generic_event_t* event) = 0;
// ATTN This is used as a workaround for RAW Input events when using the Editor.
virtual void PollSpecialEvents(){};
};
class XcbEventHandlerBusTraits
: public AZ::EBusTraits
class XcbEventHandlerBusTraits : public AZ::EBusTraits
{
public:
//////////////////////////////////////////////////////////////////////////
@@ -0,0 +1,652 @@
/*
* 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/std/typetraits/integral_constant.h>
#include <AzFramework/API/ApplicationAPI_Linux.h>
#include <AzFramework/XcbConnectionManager.h>
#include <AzFramework/XcbInputDeviceMouse.h>
namespace AzFramework
{
xcb_window_t GetSystemCursorFocusWindow()
{
void* systemCursorFocusWindow = nullptr;
AzFramework::InputSystemCursorConstraintRequestBus::BroadcastResult(
systemCursorFocusWindow, &AzFramework::InputSystemCursorConstraintRequests::GetSystemCursorConstraintWindow);
if (!systemCursorFocusWindow)
{
return XCB_NONE;
}
// TODO Clang compile error because cast .... loses information. On GNU/Linux HWND is void* and on 64-bit
// machines its obviously 64 bit but we receive the window id from m_renderOverlay.winId() which is xcb_window_t 32-bit.
return static_cast<xcb_window_t>(reinterpret_cast<uint64_t>(systemCursorFocusWindow));
}
xcb_connection_t* XcbInputDeviceMouse::s_xcbConnection = nullptr;
xcb_screen_t* XcbInputDeviceMouse::s_xcbScreen = nullptr;
bool XcbInputDeviceMouse::m_xfixesInitialized = false;
bool XcbInputDeviceMouse::m_xInputInitialized = false;
XcbInputDeviceMouse::XcbInputDeviceMouse(InputDeviceMouse& inputDevice)
: InputDeviceMouse::Implementation(inputDevice)
, m_systemCursorState(SystemCursorState::Unknown)
, m_systemCursorPositionNormalized(0.5f, 0.5f)
, m_prevConstraintWindow(XCB_NONE)
, m_focusWindow(XCB_NONE)
, m_cursorShown(true)
{
XcbEventHandlerBus::Handler::BusConnect();
SetSystemCursorState(SystemCursorState::Unknown);
}
XcbInputDeviceMouse::~XcbInputDeviceMouse()
{
XcbEventHandlerBus::Handler::BusDisconnect();
SetSystemCursorState(SystemCursorState::Unknown);
}
InputDeviceMouse::Implementation* XcbInputDeviceMouse::Create(InputDeviceMouse& inputDevice)
{
auto* interface = AzFramework::XcbConnectionManagerInterface::Get();
if (!interface)
{
AZ_Warning("XcbInput", false, "XCB interface not available");
return nullptr;
}
s_xcbConnection = AzFramework::XcbConnectionManagerInterface::Get()->GetXcbConnection();
if (!s_xcbConnection)
{
AZ_Warning("XcbInput", false, "XCB connection not available");
return nullptr;
}
const xcb_setup_t* xcbSetup = xcb_get_setup(s_xcbConnection);
s_xcbScreen = xcb_setup_roots_iterator(xcbSetup).data;
if (!s_xcbScreen)
{
AZ_Warning("XcbInput", false, "XCB screen not available");
return nullptr;
}
// Initialize XFixes extension which we use to create pointer barriers.
if (!InitializeXFixes())
{
AZ_Warning("XcbInput", false, "XCB XFixes initialization failed");
return nullptr;
}
// Initialize XInput extension which is used to get RAW Input events.
if (!InitializeXInput())
{
AZ_Warning("XcbInput", false, "XCB XInput initialization failed");
return nullptr;
}
return aznew XcbInputDeviceMouse(inputDevice);
}
bool XcbInputDeviceMouse::IsConnected() const
{
return true;
}
void XcbInputDeviceMouse::CreateBarriers(xcb_window_t window, bool create)
{
// Don't create any barriers if we are debugging. This will cause artifacts but better then
// a confined cursor during debugging.
if (AZ::Debug::Trace::IsDebuggerPresent())
{
AZ_Warning("XcbInput", false, "Debugger running. Barriers will not be created.");
return;
}
if (create)
{
// Destroy barriers if they are active already.
if (!m_activeBarriers.empty())
{
for (const auto& barrier : m_activeBarriers)
{
xcb_xfixes_delete_pointer_barrier_checked(s_xcbConnection, barrier.id);
}
m_activeBarriers.clear();
}
// Get window information.
const XcbStdFreePtr<xcb_get_geometry_reply_t> xcbGeometryReply{ xcb_get_geometry_reply(
s_xcbConnection, xcb_get_geometry(s_xcbConnection, window), NULL) };
if (!xcbGeometryReply)
{
return;
}
const xcb_translate_coordinates_cookie_t translate_coord =
xcb_translate_coordinates(s_xcbConnection, window, s_xcbScreen->root, 0, 0);
const XcbStdFreePtr<xcb_translate_coordinates_reply_t> xkbTranslateCoordReply{ xcb_translate_coordinates_reply(
s_xcbConnection, translate_coord, NULL) };
if (!xkbTranslateCoordReply)
{
return;
}
const int16_t x0 = xkbTranslateCoordReply->dst_x < 0 ? 0 : xkbTranslateCoordReply->dst_x;
const int16_t y0 = xkbTranslateCoordReply->dst_y < 0 ? 0 : xkbTranslateCoordReply->dst_y;
const int16_t x1 = xkbTranslateCoordReply->dst_x + xcbGeometryReply->width;
const int16_t y1 = xkbTranslateCoordReply->dst_y + xcbGeometryReply->height;
// ATTN For whatever reason, when making an exact rectangle the pointer will escape the top right corner in some cases. Adding
// an offset to the lines so that they cross each other prevents that.
const int16_t offset = 30;
// Create the left barrier info.
m_activeBarriers.push_back({ xcb_generate_id(s_xcbConnection), XCB_XFIXES_BARRIER_DIRECTIONS_POSITIVE_X, x0, Clamp(y0 - offset),
x0, Clamp(y1 + offset) });
// Create the right barrier info.
m_activeBarriers.push_back({ xcb_generate_id(s_xcbConnection), XCB_XFIXES_BARRIER_DIRECTIONS_NEGATIVE_X, x1, Clamp(y0 - offset),
x1, Clamp(y1 + offset) });
// Create the top barrier info.
m_activeBarriers.push_back({ xcb_generate_id(s_xcbConnection), XCB_XFIXES_BARRIER_DIRECTIONS_POSITIVE_Y, Clamp(x0 - offset), y0,
Clamp(x1 + offset), y0 });
// Create the bottom barrier info.
m_activeBarriers.push_back({ xcb_generate_id(s_xcbConnection), XCB_XFIXES_BARRIER_DIRECTIONS_NEGATIVE_Y, Clamp(x0 - offset), y1,
Clamp(x1 + offset), y1 });
// Create the xfixes barriers.
for (const auto& barrier : m_activeBarriers)
{
xcb_void_cookie_t cookie = xcb_xfixes_create_pointer_barrier_checked(
s_xcbConnection, barrier.id, window, barrier.x0, barrier.y0, barrier.x1, barrier.y1, barrier.direction, 0, NULL);
const XcbStdFreePtr<xcb_generic_error_t> xkbError{ xcb_request_check(s_xcbConnection, cookie) };
AZ_Warning(
"XcbInput", !xkbError, "XFixes, failed to create barrier %d at (%d %d %d %d)", barrier.id, barrier.x0, barrier.y0,
barrier.x1, barrier.y1);
}
}
else
{
for (const auto& barrier : m_activeBarriers)
{
xcb_xfixes_delete_pointer_barrier_checked(s_xcbConnection, barrier.id);
}
m_activeBarriers.clear();
}
xcb_flush(s_xcbConnection);
}
bool XcbInputDeviceMouse::InitializeXFixes()
{
m_xfixesInitialized = false;
// We don't have to free query_extension_reply according to xcb documentation.
const xcb_query_extension_reply_t* query_extension_reply = xcb_get_extension_data(s_xcbConnection, &xcb_xfixes_id);
if (!query_extension_reply || !query_extension_reply->present)
{
return m_xfixesInitialized;
}
const xcb_xfixes_query_version_cookie_t query_cookie = xcb_xfixes_query_version(s_xcbConnection, 5, 0);
xcb_generic_error_t* error = NULL;
const XcbStdFreePtr<xcb_xfixes_query_version_reply_t> xkbQueryRequestReply{ xcb_xfixes_query_version_reply(
s_xcbConnection, query_cookie, &error) };
if (!xkbQueryRequestReply || error)
{
if (error)
{
AZ_Warning("XcbInput", false, "Retrieving XFixes version failed : Error code %d", error->error_code);
free(error);
}
return m_xfixesInitialized;
}
else if (xkbQueryRequestReply->major_version < 5)
{
AZ_Warning("XcbInput", false, "XFixes version fails the minimum version check (%d<5)", xkbQueryRequestReply->major_version);
return m_xfixesInitialized;
}
m_xfixesInitialized = true;
return m_xfixesInitialized;
}
bool XcbInputDeviceMouse::InitializeXInput()
{
m_xInputInitialized = false;
// We don't have to free query_extension_reply according to xcb documentation.
const xcb_query_extension_reply_t* query_extension_reply = xcb_get_extension_data(s_xcbConnection, &xcb_input_id);
if (!query_extension_reply || !query_extension_reply->present)
{
return m_xInputInitialized;
}
const xcb_input_xi_query_version_cookie_t query_version_cookie = xcb_input_xi_query_version(s_xcbConnection, 2, 2);
xcb_generic_error_t* error = NULL;
const XcbStdFreePtr<xcb_input_xi_query_version_reply_t> xkbQueryRequestReply{ xcb_input_xi_query_version_reply(
s_xcbConnection, query_version_cookie, &error) };
if (!xkbQueryRequestReply || error)
{
if (error)
{
AZ_Warning("XcbInput", false, "Retrieving XInput version failed : Error code %d", error->error_code);
free(error);
}
return m_xInputInitialized;
}
else if (xkbQueryRequestReply->major_version < 2)
{
AZ_Warning("XcbInput", false, "XInput version fails the minimum version check (%d<5)", xkbQueryRequestReply->major_version);
return m_xInputInitialized;
}
m_xInputInitialized = true;
return m_xInputInitialized;
}
void XcbInputDeviceMouse::SetEnableXInput(bool enable)
{
struct
{
xcb_input_event_mask_t head;
int mask;
} mask;
mask.head.deviceid = XCB_INPUT_DEVICE_ALL;
mask.head.mask_len = 1;
if (enable)
{
mask.mask = XCB_INPUT_XI_EVENT_MASK_RAW_MOTION | XCB_INPUT_XI_EVENT_MASK_RAW_BUTTON_PRESS |
XCB_INPUT_XI_EVENT_MASK_RAW_BUTTON_RELEASE | XCB_INPUT_XI_EVENT_MASK_MOTION | XCB_INPUT_XI_EVENT_MASK_BUTTON_PRESS |
XCB_INPUT_XI_EVENT_MASK_BUTTON_RELEASE;
}
else
{
mask.mask = XCB_NONE;
}
xcb_input_xi_select_events(s_xcbConnection, s_xcbScreen->root, 1, &mask.head);
xcb_flush(s_xcbConnection);
}
void XcbInputDeviceMouse::SetSystemCursorState(SystemCursorState systemCursorState)
{
if (systemCursorState != m_systemCursorState)
{
m_systemCursorState = systemCursorState;
m_focusWindow = GetSystemCursorFocusWindow();
HandleCursorState(m_focusWindow, systemCursorState);
}
}
void XcbInputDeviceMouse::HandleCursorState(xcb_window_t window, SystemCursorState systemCursorState)
{
bool confined = false, cursorShown = true;
switch (systemCursorState)
{
case SystemCursorState::ConstrainedAndHidden:
{
//!< Constrained to the application's main window and hidden
confined = true;
cursorShown = false;
}
break;
case SystemCursorState::ConstrainedAndVisible:
{
//!< Constrained to the application's main window and visible
confined = true;
}
break;
case SystemCursorState::UnconstrainedAndHidden:
{
//!< Free to move outside the main window but hidden while inside
cursorShown = false;
}
break;
case SystemCursorState::UnconstrainedAndVisible:
{
//!< Free to move outside the application's main window and visible
}
case SystemCursorState::Unknown:
default:
break;
}
// ATTN GetSystemCursorFocusWindow when getting out of the play in editor will return XCB_NONE
// We need however the window id to reset the cursor.
if (XCB_NONE == window && (confined || cursorShown))
{
// Reuse the previous window to reset states.
window = m_prevConstraintWindow;
m_prevConstraintWindow = XCB_NONE;
}
else
{
// Remember the window we used to modify cursor and barrier states.
m_prevConstraintWindow = window;
}
SetEnableXInput(!cursorShown);
CreateBarriers(window, confined);
ShowCursor(window, cursorShown);
}
SystemCursorState XcbInputDeviceMouse::GetSystemCursorState() const
{
return m_systemCursorState;
}
void XcbInputDeviceMouse::SetSystemCursorPositionNormalizedInternal(xcb_window_t window, AZ::Vector2 positionNormalized)
{
// TODO Basically not done at all. Added only the basic functions needed.
const XcbStdFreePtr<xcb_get_geometry_reply_t> xkbGeometryReply{ xcb_get_geometry_reply(
s_xcbConnection, xcb_get_geometry(s_xcbConnection, window), NULL) };
if (!xkbGeometryReply)
{
return;
}
const int16_t x = static_cast<int16_t>(positionNormalized.GetX() * xkbGeometryReply->width);
const int16_t y = static_cast<int16_t>(positionNormalized.GetY() * xkbGeometryReply->height);
xcb_warp_pointer(s_xcbConnection, XCB_NONE, window, 0, 0, 0, 0, x, y);
xcb_flush(s_xcbConnection);
}
void XcbInputDeviceMouse::SetSystemCursorPositionNormalized(AZ::Vector2 positionNormalized)
{
const xcb_window_t window = GetSystemCursorFocusWindow();
if (XCB_NONE == window)
{
return;
}
SetSystemCursorPositionNormalizedInternal(window, positionNormalized);
}
AZ::Vector2 XcbInputDeviceMouse::GetSystemCursorPositionNormalizedInternal(xcb_window_t window) const
{
AZ::Vector2 position = AZ::Vector2::CreateZero();
const xcb_query_pointer_cookie_t pointer = xcb_query_pointer(s_xcbConnection, window);
const XcbStdFreePtr<xcb_query_pointer_reply_t> xkbQueryPointerReply{ xcb_query_pointer_reply(s_xcbConnection, pointer, NULL) };
if (!xkbQueryPointerReply)
{
return position;
}
const XcbStdFreePtr<xcb_get_geometry_reply_t> xkbGeometryReply{ xcb_get_geometry_reply(
s_xcbConnection, xcb_get_geometry(s_xcbConnection, window), NULL) };
if (!xkbGeometryReply)
{
return position;
}
AZ_Assert(xkbGeometryReply->width != 0, "xkbGeometry response width must be non-zero. (%d)", xkbGeometryReply->width);
const float normalizedCursorPostionX = static_cast<float>(xkbQueryPointerReply->win_x) / xkbGeometryReply->width;
AZ_Assert(xkbGeometryReply->height != 0, "xkbGeometry response height must be non-zero. (%d)", xkbGeometryReply->height);
const float normalizedCursorPostionY = static_cast<float>(xkbQueryPointerReply->win_y) / xkbGeometryReply->height;
position = AZ::Vector2(normalizedCursorPostionX, normalizedCursorPostionY);
return position;
}
AZ::Vector2 XcbInputDeviceMouse::GetSystemCursorPositionNormalized() const
{
const xcb_window_t window = GetSystemCursorFocusWindow();
if (XCB_NONE == window)
{
return AZ::Vector2::CreateZero();
}
return GetSystemCursorPositionNormalizedInternal(window);
}
void XcbInputDeviceMouse::TickInputDevice()
{
ProcessRawEventQueues();
}
void XcbInputDeviceMouse::ShowCursor(xcb_window_t window, bool show)
{
xcb_void_cookie_t cookie;
if (show)
{
cookie = xcb_xfixes_show_cursor_checked(s_xcbConnection, window);
}
else
{
cookie = xcb_xfixes_hide_cursor_checked(s_xcbConnection, window);
}
const XcbStdFreePtr<xcb_generic_error_t> xkbError{ xcb_request_check(s_xcbConnection, cookie) };
if (xkbError)
{
AZ_Warning("XcbInput", false, "ShowCursor failed: %d", xkbError->error_code);
return;
}
// ATTN In the following part we will when cursor gets hidden store the position of the cursor in screen space
// not window space. We use that to re-position when showing the cursor again. Is this the correct
// behavior?
const bool cursorWasHidden = !m_cursorShown;
m_cursorShown = show;
if (!m_cursorShown)
{
m_cursorHiddenPosition = GetSystemCursorPositionNormalizedInternal(s_xcbScreen->root);
SetSystemCursorPositionNormalized(AZ::Vector2(0.5f, 0.5f));
}
else if (cursorWasHidden)
{
SetSystemCursorPositionNormalizedInternal(s_xcbScreen->root, m_cursorHiddenPosition);
}
xcb_flush(s_xcbConnection);
}
void XcbInputDeviceMouse::HandleButtonPressEvents(uint32_t detail, bool pressed)
{
bool isWheel;
float wheelDirection;
const auto* button = InputChannelFromMouseEvent(detail, isWheel, wheelDirection);
if (button)
{
QueueRawButtonEvent(*button, pressed);
}
if (isWheel)
{
float axisValue = MAX_XI_WHEEL_SENSITIVITY * wheelDirection;
QueueRawMovementEvent(InputDeviceMouse::Movement::Z, axisValue);
}
}
void XcbInputDeviceMouse::HandlePointerMotionEvents(const xcb_generic_event_t* event)
{
const xcb_input_motion_event_t* mouseMotionEvent = reinterpret_cast<const xcb_input_motion_event_t*>(event);
m_systemCursorPosition[0] = mouseMotionEvent->event_x;
m_systemCursorPosition[1] = mouseMotionEvent->event_y;
}
void XcbInputDeviceMouse::HandleRawInputEvents(const xcb_ge_generic_event_t* event)
{
const xcb_ge_generic_event_t* genericEvent = reinterpret_cast<const xcb_ge_generic_event_t*>(event);
switch (genericEvent->event_type)
{
case XCB_INPUT_RAW_BUTTON_PRESS:
{
const xcb_input_raw_button_press_event_t* mouseButtonEvent =
reinterpret_cast<const xcb_input_raw_button_press_event_t*>(event);
HandleButtonPressEvents(mouseButtonEvent->detail, true);
}
break;
case XCB_INPUT_RAW_BUTTON_RELEASE:
{
const xcb_input_raw_button_release_event_t* mouseButtonEvent =
reinterpret_cast<const xcb_input_raw_button_release_event_t*>(event);
HandleButtonPressEvents(mouseButtonEvent->detail, false);
}
break;
case XCB_INPUT_RAW_MOTION:
{
const xcb_input_raw_motion_event_t* mouseMotionEvent = reinterpret_cast<const xcb_input_raw_motion_event_t*>(event);
int axisLen = xcb_input_raw_button_press_axisvalues_length(mouseMotionEvent);
const xcb_input_fp3232_t* axisvalues = xcb_input_raw_button_press_axisvalues_raw(mouseMotionEvent);
for (int i = 0; i < axisLen; ++i)
{
const float axisValue = fp3232ToFloat(axisvalues[i]);
switch (i)
{
case 0:
QueueRawMovementEvent(InputDeviceMouse::Movement::X, axisValue);
break;
case 1:
QueueRawMovementEvent(InputDeviceMouse::Movement::Y, axisValue);
break;
}
}
}
break;
}
}
void XcbInputDeviceMouse::PollSpecialEvents()
{
while (xcb_generic_event_t* genericEvent = xcb_poll_for_queued_event(s_xcbConnection))
{
// TODO Is the following correct? If we are showing the cursor, don't poll RAW Input events.
switch (genericEvent->response_type & ~0x80)
{
case XCB_GE_GENERIC:
{
const xcb_ge_generic_event_t* geGenericEvent = reinterpret_cast<const xcb_ge_generic_event_t*>(genericEvent);
// Only handle raw inputs if we have focus.
// Handle Raw Input events first.
if ((geGenericEvent->event_type == XCB_INPUT_RAW_BUTTON_PRESS) ||
(geGenericEvent->event_type == XCB_INPUT_RAW_BUTTON_RELEASE) ||
(geGenericEvent->event_type == XCB_INPUT_RAW_MOTION))
{
HandleRawInputEvents(geGenericEvent);
free(genericEvent);
}
}
break;
}
}
}
void XcbInputDeviceMouse::HandleXcbEvent(xcb_generic_event_t* event)
{
switch (event->response_type & ~0x80)
{
// QT5 is using by default XInput which means we do need to check for XCB_GE_GENERIC event to parse all mouse related events.
case XCB_GE_GENERIC:
{
const xcb_ge_generic_event_t* genericEvent = reinterpret_cast<const xcb_ge_generic_event_t*>(event);
// Handling RAW Inputs here works in GameMode but not in Editor mode because QT is
// not handling RAW input events and passing to.
if (!m_cursorShown)
{
// Handle Raw Input events first.
if ((genericEvent->event_type == XCB_INPUT_RAW_BUTTON_PRESS) ||
(genericEvent->event_type == XCB_INPUT_RAW_BUTTON_RELEASE) || (genericEvent->event_type == XCB_INPUT_RAW_MOTION))
{
HandleRawInputEvents(genericEvent);
}
}
else
{
switch (genericEvent->event_type)
{
case XCB_INPUT_BUTTON_PRESS:
{
const xcb_input_button_press_event_t* mouseButtonEvent =
reinterpret_cast<const xcb_input_button_press_event_t*>(genericEvent);
HandleButtonPressEvents(mouseButtonEvent->detail, true);
}
break;
case XCB_INPUT_BUTTON_RELEASE:
{
const xcb_input_button_release_event_t* mouseButtonEvent =
reinterpret_cast<const xcb_input_button_release_event_t*>(genericEvent);
HandleButtonPressEvents(mouseButtonEvent->detail, false);
}
break;
case XCB_INPUT_MOTION:
{
HandlePointerMotionEvents(event);
}
break;
}
}
}
break;
case XCB_FOCUS_IN:
{
const xcb_focus_in_event_t* focusInEvent = reinterpret_cast<const xcb_focus_in_event_t*>(event);
if (m_focusWindow != focusInEvent->event)
{
m_focusWindow = focusInEvent->event;
HandleCursorState(m_focusWindow, m_systemCursorState);
}
}
break;
case XCB_FOCUS_OUT:
{
const xcb_focus_out_event_t* focusOutEvent = reinterpret_cast<const xcb_focus_out_event_t*>(event);
HandleCursorState(focusOutEvent->event, SystemCursorState::UnconstrainedAndVisible);
ProcessRawEventQueues();
ResetInputChannelStates();
m_focusWindow = XCB_NONE;
}
break;
}
}
} // namespace AzFramework
@@ -0,0 +1,193 @@
/*
* 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 <AzFramework/Input/Devices/Mouse/InputDeviceMouse.h>
#include <AzFramework/XcbConnectionManager.h>
#include <AzFramework/XcbEventHandler.h>
#include <AzFramework/XcbInterface.h>
#include <xcb/xfixes.h>
#include <xcb/xinput.h>
// The maximum number of raw input axis this mouse device supports.
constexpr uint32_t MAX_XI_RAW_AXIS = 2;
// The sensitivity of the wheel.
constexpr float MAX_XI_WHEEL_SENSITIVITY = 140.0f;
namespace AzFramework
{
class XcbInputDeviceMouse
: public InputDeviceMouse::Implementation
, public XcbEventHandlerBus::Handler
{
public:
AZ_CLASS_ALLOCATOR(XcbInputDeviceMouse, AZ::SystemAllocator, 0);
////////////////////////////////////////////////////////////////////////////////////////////
//! Constructor
//! \param[in] inputDevice Reference to the input device being implemented
XcbInputDeviceMouse(InputDeviceMouse& inputDevice);
////////////////////////////////////////////////////////////////////////////////////////////
//! Destructor
virtual ~XcbInputDeviceMouse();
static XcbInputDeviceMouse::Implementation* Create(InputDeviceMouse& inputDevice);
protected:
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::InputDeviceMouse::Implementation::IsConnected
bool IsConnected() const override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::InputDeviceMouse::Implementation::SetSystemCursorState
void SetSystemCursorState(SystemCursorState systemCursorState) override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::InputDeviceMouse::Implementation::GetSystemCursorState
SystemCursorState GetSystemCursorState() const override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::InputDeviceMouse::Implementation::SetSystemCursorPositionNormalized
void SetSystemCursorPositionNormalized(AZ::Vector2 positionNormalized) override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::InputDeviceMouse::Implementation::GetSystemCursorPositionNormalized
AZ::Vector2 GetSystemCursorPositionNormalized() const override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::InputDeviceMouse::Implementation::TickInputDevice
void TickInputDevice() override;
//! This method is called by the Editor to accommodate some events with the Editor. Never called in Game mode.
void PollSpecialEvents() override;
//! Handle X11 events.
void HandleXcbEvent(xcb_generic_event_t* event) override;
//! Initialize XFixes extension. Used for barriers.
static bool InitializeXFixes();
//! Initialize XInput extension. Used for raw input during confinement and showing/hiding the cursor.
static bool InitializeXInput();
//! Enables/Disables XInput Raw Input events.
void SetEnableXInput(bool enable);
//! Create barriers.
void CreateBarriers(xcb_window_t window, bool create);
//! Helper function.
void SystemCursorStateToLogic(SystemCursorState systemCursorState, bool& confined, bool& cursorShown);
//! Shows/Hides the cursor.
void ShowCursor(xcb_window_t window, bool show);
//! Get the normalized cursor position. The coordinates returned are relative to the specified window.
AZ::Vector2 GetSystemCursorPositionNormalizedInternal(xcb_window_t window) const;
//! Set the normalized cursor position. The normalized position will be relative to the specified window.
void SetSystemCursorPositionNormalizedInternal(xcb_window_t window, AZ::Vector2 positionNormalized);
//! Handle button press/release events.
void HandleButtonPressEvents(uint32_t detail, bool pressed);
//! Handle motion notify events.
void HandlePointerMotionEvents(const xcb_generic_event_t* event);
//! Will set cursor states and confinement modes.
void HandleCursorState(xcb_window_t window, SystemCursorState systemCursorState);
//! Will handle all raw input events.
void HandleRawInputEvents(const xcb_ge_generic_event_t* event);
//! Convert XInput fp1616 to float.
inline float fp1616ToFloat(xcb_input_fp1616_t value) const
{
return static_cast<float>((value >> 16) + (value & 0xffff) / 0xffff);
}
//! Convert XInput fp3232 to float.
inline float fp3232ToFloat(xcb_input_fp3232_t value) const
{
return static_cast<float>(value.integral) + static_cast<float>(value.frac / (float)(1ull << 32));
}
const InputChannelId* InputChannelFromMouseEvent(xcb_button_t button, bool& isWheel, float& direction) const
{
isWheel = false;
direction = 1.0f;
switch (button)
{
case XCB_BUTTON_INDEX_1:
return &InputDeviceMouse::Button::Left;
case XCB_BUTTON_INDEX_2:
return &InputDeviceMouse::Button::Right;
case XCB_BUTTON_INDEX_3:
return &InputDeviceMouse::Button::Middle;
case XCB_BUTTON_INDEX_4:
isWheel = true;
direction = 1.0f;
break;
case XCB_BUTTON_INDEX_5:
isWheel = true;
direction = -1.0f;
break;
default:
break;
}
return nullptr;
}
// Barriers work only with positive values. We clamp here to zero.
inline int16_t Clamp(int16_t value) const
{
return value < 0 ? 0 : value;
}
private:
//! The current system cursor state
SystemCursorState m_systemCursorState;
//! The cursor position before it got hidden.
AZ::Vector2 m_cursorHiddenPosition;
AZ::Vector2 m_systemCursorPositionNormalized;
uint32_t m_systemCursorPosition[MAX_XI_RAW_AXIS];
static xcb_connection_t* s_xcbConnection;
static xcb_screen_t* s_xcbScreen;
//! Will be true if the xfixes extension could be initialized.
static bool m_xfixesInitialized;
//! Will be true if the xinput2 extension could be initialized.
static bool m_xInputInitialized;
//! The window that had focus
xcb_window_t m_prevConstraintWindow;
//! The current window that has focus
xcb_window_t m_focusWindow;
//! Will be true if the cursor is shown else false.
bool m_cursorShown;
struct XFixesBarrierProperty
{
xcb_xfixes_barrier_t id;
uint32_t direction;
int16_t x0, y0, x1, y1;
};
//! Array that holds barrier information used to confine the cursor.
std::vector<XFixesBarrierProperty> m_activeBarriers;
};
} // namespace AzFramework
@@ -8,25 +8,31 @@
#include <AzFramework/Application/Application.h>
#include <AzFramework/Windowing/NativeWindow.h>
#include <AzFramework/XcbNativeWindow.h>
#include <AzFramework/XcbConnectionManager.h>
#include <AzFramework/XcbInterface.h>
#include <AzFramework/XcbNativeWindow.h>
#include <xcb/xcb.h>
namespace AzFramework
{
[[maybe_unused]] const char XcbErrorWindow[] = "XcbNativeWindow";
static constexpr uint8_t s_XcbFormatDataSize = 32; // Format indicator for xcb for client messages
static constexpr uint16_t s_DefaultXcbWindowBorderWidth = 4; // The default border with in pixels if a border was specified
static constexpr uint8_t s_XcbResponseTypeMask = 0x7f; // Mask to extract the specific event type from an xcb event
static constexpr uint8_t s_XcbFormatDataSize = 32; // Format indicator for xcb for client messages
static constexpr uint16_t s_DefaultXcbWindowBorderWidth = 4; // The default border with in pixels if a border was specified
static constexpr uint8_t s_XcbResponseTypeMask = 0x7f; // Mask to extract the specific event type from an xcb event
#define _NET_WM_STATE_REMOVE 0l
#define _NET_WM_STATE_ADD 1l
#define _NET_WM_STATE_TOGGLE 2l
////////////////////////////////////////////////////////////////////////////////////////////////
XcbNativeWindow::XcbNativeWindow()
XcbNativeWindow::XcbNativeWindow()
: NativeWindow::Implementation()
, m_xcbConnection(nullptr)
, m_xcbRootScreen(nullptr)
, m_xcbWindow(XCB_NONE)
{
if (auto xcbConnectionManager = AzFramework::XcbConnectionManagerInterface::Get();
xcbConnectionManager != nullptr)
if (auto xcbConnectionManager = AzFramework::XcbConnectionManagerInterface::Get(); xcbConnectionManager != nullptr)
{
m_xcbConnection = xcbConnectionManager->GetXcbConnection();
}
@@ -34,89 +40,184 @@ namespace AzFramework
}
////////////////////////////////////////////////////////////////////////////////////////////////
XcbNativeWindow::~XcbNativeWindow() = default;
XcbNativeWindow::~XcbNativeWindow()
{
if (XCB_NONE != m_xcbWindow)
{
xcb_destroy_window(m_xcbConnection, m_xcbWindow);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
void XcbNativeWindow::InitWindow(const AZStd::string& title,
const WindowGeometry& geometry,
const WindowStyleMasks& styleMasks)
void XcbNativeWindow::InitWindow(const AZStd::string& title, const WindowGeometry& geometry, const WindowStyleMasks& styleMasks)
{
// Get the parent window
// Get the parent window
const xcb_setup_t* xcbSetup = xcb_get_setup(m_xcbConnection);
xcb_screen_t* xcbRootScreen = xcb_setup_roots_iterator(xcbSetup).data;
xcb_window_t xcbParentWindow = xcbRootScreen->root;
m_xcbRootScreen = xcb_setup_roots_iterator(xcbSetup).data;
xcb_window_t xcbParentWindow = m_xcbRootScreen->root;
// Create an XCB window from the connection
m_xcbWindow = xcb_generate_id(m_xcbConnection);
uint16_t borderWidth = 0;
const uint32_t mask = styleMasks.m_platformAgnosticStyleMask;
if ((mask & WindowStyleMasks::WINDOW_STYLE_BORDERED) ||
(mask & WindowStyleMasks::WINDOW_STYLE_RESIZEABLE))
if ((mask & WindowStyleMasks::WINDOW_STYLE_BORDERED) || (mask & WindowStyleMasks::WINDOW_STYLE_RESIZEABLE))
{
borderWidth = s_DefaultXcbWindowBorderWidth;
}
uint32_t eventMask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK;
const uint32_t interestedEvents =
XCB_EVENT_MASK_STRUCTURE_NOTIFY
| XCB_EVENT_MASK_BUTTON_PRESS
| XCB_EVENT_MASK_BUTTON_RELEASE
| XCB_EVENT_MASK_KEY_PRESS
| XCB_EVENT_MASK_KEY_RELEASE
| XCB_EVENT_MASK_POINTER_MOTION
;
uint32_t valueList[] = { xcbRootScreen->black_pixel,
interestedEvents };
const uint32_t interestedEvents = XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_KEY_PRESS | XCB_EVENT_MASK_KEY_RELEASE |
XCB_EVENT_MASK_FOCUS_CHANGE | XCB_EVENT_MASK_PROPERTY_CHANGE;
uint32_t valueList[] = { m_xcbRootScreen->black_pixel, interestedEvents };
xcb_void_cookie_t xcbCheckResult;
xcbCheckResult = xcb_create_window_checked(m_xcbConnection,
XCB_COPY_FROM_PARENT,
m_xcbWindow,
xcbParentWindow,
aznumeric_cast<int16_t>(geometry.m_posX),
aznumeric_cast<int16_t>(geometry.m_posY),
aznumeric_cast<int16_t>(geometry.m_width),
aznumeric_cast<int16_t>(geometry.m_height),
borderWidth,
XCB_WINDOW_CLASS_INPUT_OUTPUT,
xcbRootScreen->root_visual,
eventMask,
valueList);
xcbCheckResult = xcb_create_window_checked(
m_xcbConnection, XCB_COPY_FROM_PARENT, m_xcbWindow, xcbParentWindow, aznumeric_cast<int16_t>(geometry.m_posX),
aznumeric_cast<int16_t>(geometry.m_posY), aznumeric_cast<int16_t>(geometry.m_width), aznumeric_cast<int16_t>(geometry.m_height),
borderWidth, XCB_WINDOW_CLASS_INPUT_OUTPUT, m_xcbRootScreen->root_visual, eventMask, valueList);
AZ_Assert(ValidateXcbResult(xcbCheckResult), "Failed to create xcb window.");
SetWindowTitle(title);
// Setup the window close event
const static char* wmProtocolString = "WM_PROTOCOLS";
xcb_intern_atom_cookie_t cookieProtocol = xcb_intern_atom(m_xcbConnection, 1, strlen(wmProtocolString), wmProtocolString);
xcb_intern_atom_reply_t* replyProtocol = xcb_intern_atom_reply(m_xcbConnection, cookieProtocol, nullptr);
AZ_Error(XcbErrorWindow, replyProtocol != nullptr, "Unable to query xcb '%s' atom", wmProtocolString);
m_xcbAtomProtocols = replyProtocol->atom;
const static char* wmDeleteWindowString = "WM_DELETE_WINDOW";
xcb_intern_atom_cookie_t cookieDeleteWindow = xcb_intern_atom(m_xcbConnection, 0, strlen(wmDeleteWindowString), wmDeleteWindowString);
xcb_intern_atom_reply_t* replyDeleteWindow = xcb_intern_atom_reply(m_xcbConnection, cookieDeleteWindow, nullptr);
AZ_Error(XcbErrorWindow, replyDeleteWindow != nullptr, "Unable to query xcb '%s' atom", wmDeleteWindowString);
m_xcbAtomDeleteWindow = replyDeleteWindow->atom;
xcbCheckResult = xcb_change_property_checked(m_xcbConnection,
XCB_PROP_MODE_REPLACE,
m_xcbWindow,
m_xcbAtomProtocols,
XCB_ATOM_ATOM,
s_XcbFormatDataSize,
1,
&m_xcbAtomDeleteWindow);
AZ_Assert(ValidateXcbResult(xcbCheckResult), "Failed to change the xcb atom property for WM_CLOSE event");
m_posX = geometry.m_posX;
m_posY = geometry.m_posY;
m_width = geometry.m_width;
m_height = geometry.m_height;
InitializeAtoms();
xcb_client_message_event_t event;
event.response_type = XCB_CLIENT_MESSAGE;
event.type = _NET_REQUEST_FRAME_EXTENTS;
event.window = m_xcbWindow;
event.format = 32;
event.sequence = 0;
event.data.data32[0] = 0l;
event.data.data32[1] = 0l;
event.data.data32[2] = 0l;
event.data.data32[3] = 0l;
event.data.data32[4] = 0l;
xcbCheckResult = xcb_send_event(
m_xcbConnection, 1, m_xcbRootScreen->root, XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT,
(const char*)&event);
AZ_Assert(ValidateXcbResult(xcbCheckResult), "Failed to set _NET_REQUEST_FRAME_EXTENTS");
// The WM will be able to kill the application if it gets unresponsive.
int32_t pid = getpid();
xcb_change_property(m_xcbConnection, XCB_PROP_MODE_REPLACE, m_xcbWindow, _NET_WM_PID, XCB_ATOM_CARDINAL, 32, 1, &pid);
xcb_flush(m_xcbConnection);
}
xcb_atom_t XcbNativeWindow::GetAtom(const char* atomName)
{
xcb_intern_atom_cookie_t intern_atom_cookie = xcb_intern_atom(m_xcbConnection, 0, strlen(atomName), atomName);
XcbStdFreePtr<xcb_intern_atom_reply_t> xkbinternAtom{ xcb_intern_atom_reply(m_xcbConnection, intern_atom_cookie, NULL) };
if (!xkbinternAtom)
{
AZ_Error(XcbErrorWindow, xkbinternAtom != nullptr, "Unable to query xcb '%s' atom", atomName);
return XCB_NONE;
}
return xkbinternAtom->atom;
}
int XcbNativeWindow::SetAtom(xcb_window_t window, xcb_atom_t atom, xcb_atom_t type, size_t len, void* data)
{
xcb_void_cookie_t cookie = xcb_change_property_checked(m_xcbConnection, XCB_PROP_MODE_REPLACE, window, atom, type, 32, len, data);
XcbStdFreePtr<xcb_generic_error_t> xkbError{ xcb_request_check(m_xcbConnection, cookie) };
if (!xkbError)
{
return 0;
}
return xkbError->error_code;
}
////////////////////////////////////////////////////////////////////////////////////////////////
void XcbNativeWindow::InitializeAtoms()
{
AZStd::vector<xcb_atom_t> Atoms;
_NET_ACTIVE_WINDOW = GetAtom("_NET_ACTIVE_WINDOW");
_NET_WM_BYPASS_COMPOSITOR = GetAtom("_NET_WM_BYPASS_COMPOSITOR");
// ---------------------------------------------------------------------
// Handle all WM Protocols atoms.
//
WM_PROTOCOLS = GetAtom("WM_PROTOCOLS");
// This atom is used to close a window. Emitted when user clicks the close button.
WM_DELETE_WINDOW = GetAtom("WM_DELETE_WINDOW");
Atoms.push_back(WM_DELETE_WINDOW);
xcb_change_property(
m_xcbConnection, XCB_PROP_MODE_REPLACE, m_xcbWindow, WM_PROTOCOLS, XCB_ATOM_ATOM, 32, Atoms.size(), Atoms.data());
xcb_flush(m_xcbConnection);
// ---------------------------------------------------------------------
// Handle all WM State atoms.
//
_NET_WM_STATE = GetAtom("_NET_WM_STATE");
_NET_WM_STATE_FULLSCREEN = GetAtom("_NET_WM_STATE_FULLSCREEN");
_NET_WM_STATE_MAXIMIZED_VERT = GetAtom("_NET_WM_STATE_MAXIMIZED_VERT");
_NET_WM_STATE_MAXIMIZED_HORZ = GetAtom("_NET_WM_STATE_MAXIMIZED_HORZ");
_NET_MOVERESIZE_WINDOW = GetAtom("_NET_MOVERESIZE_WINDOW");
_NET_REQUEST_FRAME_EXTENTS = GetAtom("_NET_REQUEST_FRAME_EXTENTS");
_NET_FRAME_EXTENTS = GetAtom("_NET_FRAME_EXTENTS");
_NET_WM_PID = GetAtom("_NET_WM_PID");
}
void XcbNativeWindow::GetWMStates()
{
xcb_get_property_cookie_t cookie = xcb_get_property(m_xcbConnection, 0, m_xcbWindow, _NET_WM_STATE, XCB_ATOM_ATOM, 0, 1024);
xcb_generic_error_t* error = nullptr;
XcbStdFreePtr<xcb_get_property_reply_t> xkbGetPropertyReply{ xcb_get_property_reply(m_xcbConnection, cookie, &error) };
if (!xkbGetPropertyReply || error || !((xkbGetPropertyReply->format == 32) && (xkbGetPropertyReply->type == XCB_ATOM_ATOM)))
{
AZ_Warning("ApplicationLinux", false, "Acquiring _NET_WM_STATE information from the WM failed.");
if (error)
{
AZ_TracePrintf("Error", "Error code %d", error->error_code);
free(error);
}
return;
}
m_fullscreenState = false;
m_horizontalyMaximized = false;
m_verticallyMaximized = false;
const xcb_atom_t* states = static_cast<const xcb_atom_t*>(xcb_get_property_value(xkbGetPropertyReply.get()));
for (int i = 0; i < xkbGetPropertyReply->length; i++)
{
if (states[i] == _NET_WM_STATE_FULLSCREEN)
{
m_fullscreenState = true;
}
else if (states[i] == _NET_WM_STATE_MAXIMIZED_HORZ)
{
m_horizontalyMaximized = true;
}
else if (states[i] == _NET_WM_STATE_MAXIMIZED_VERT)
{
m_verticallyMaximized = true;
}
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
@@ -146,7 +247,7 @@ namespace AzFramework
xcb_flush(m_xcbConnection);
}
XcbEventHandlerBus::Handler::BusDisconnect();
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
NativeWindowHandle XcbNativeWindow::GetWindowHandle() const
@@ -158,14 +259,9 @@ namespace AzFramework
void XcbNativeWindow::SetWindowTitle(const AZStd::string& title)
{
xcb_void_cookie_t xcbCheckResult;
xcbCheckResult = xcb_change_property(m_xcbConnection,
XCB_PROP_MODE_REPLACE,
m_xcbWindow,
XCB_ATOM_WM_NAME,
XCB_ATOM_STRING,
8,
static_cast<uint32_t>(title.size()),
title.c_str());
xcbCheckResult = xcb_change_property(
m_xcbConnection, XCB_PROP_MODE_REPLACE, m_xcbWindow, XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 8, static_cast<uint32_t>(title.size()),
title.c_str());
AZ_Assert(ValidateXcbResult(xcbCheckResult), "Failed to set window title.");
}
@@ -175,7 +271,7 @@ namespace AzFramework
const uint32_t values[] = { clientAreaSize.m_width, clientAreaSize.m_height };
xcb_configure_window(m_xcbConnection, m_xcbWindow, XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT, values);
m_width = clientAreaSize.m_width;
m_height = clientAreaSize.m_height;
}
@@ -185,16 +281,77 @@ namespace AzFramework
{
// [GFX TODO][GHI - 2678]
// Using 60 for now until proper support is added
return 60;
}
bool XcbNativeWindow::GetFullScreenState() const
{
return m_fullscreenState;
}
void XcbNativeWindow::SetFullScreenState(bool fullScreenState)
{
// TODO This is a pretty basic full-screen implementation using WM's _NET_WM_STATE_FULLSCREEN state.
// Do we have to provide also the old way?
GetWMStates();
xcb_client_message_event_t event;
event.response_type = XCB_CLIENT_MESSAGE;
event.type = _NET_WM_STATE;
event.window = m_xcbWindow;
event.format = 32;
event.sequence = 0;
event.data.data32[0] = fullScreenState ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE;
event.data.data32[1] = _NET_WM_STATE_FULLSCREEN;
event.data.data32[2] = 0;
event.data.data32[3] = 1;
event.data.data32[4] = 0;
xcb_void_cookie_t xcbCheckResult = xcb_send_event(
m_xcbConnection, 1, m_xcbRootScreen->root, XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT,
(const char*)&event);
AZ_Assert(ValidateXcbResult(xcbCheckResult), "Failed to set _NET_WM_STATE_FULLSCREEN");
// Also try to disable/enable the compositor if possible. Might help in some cases.
const long _NET_WM_BYPASS_COMPOSITOR_HINT_ON = m_fullscreenState ? 1 : 0;
SetAtom(m_xcbWindow, _NET_WM_BYPASS_COMPOSITOR, XCB_ATOM_CARDINAL, 32, (char*)&_NET_WM_BYPASS_COMPOSITOR_HINT_ON);
if (!fullScreenState)
{
if (m_horizontalyMaximized || m_verticallyMaximized)
{
printf("Remove maximized state.\n");
xcb_client_message_event_t event;
event.response_type = XCB_CLIENT_MESSAGE;
event.type = _NET_WM_STATE;
event.window = m_xcbWindow;
event.format = 32;
event.sequence = 0;
event.data.data32[0] = _NET_WM_STATE_MAXIMIZED_VERT;
event.data.data32[1] = _NET_WM_STATE_MAXIMIZED_HORZ;
event.data.data32[2] = 0;
event.data.data32[3] = 0;
event.data.data32[4] = 0;
xcb_void_cookie_t xcbCheckResult = xcb_send_event(
m_xcbConnection, 1, m_xcbRootScreen->root, XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT,
(const char*)&event);
AZ_Assert(
ValidateXcbResult(xcbCheckResult), "Failed to remove _NET_WM_STATE_MAXIMIZED_VERT | _NET_WM_STATE_MAXIMIZED_HORZ");
}
}
xcb_flush(m_xcbConnection);
m_fullscreenState = fullScreenState;
}
////////////////////////////////////////////////////////////////////////////////////////////////
bool XcbNativeWindow::ValidateXcbResult(xcb_void_cookie_t cookie)
{
bool result = true;
if (xcb_generic_error_t* error = xcb_request_check(m_xcbConnection, cookie))
{
AZ_TracePrintf("Error","Error code %d", error->error_code);
AZ_TracePrintf("Error", "Error code %d", error->error_code);
result = false;
}
return result;
@@ -205,20 +362,20 @@ namespace AzFramework
{
switch (event->response_type & s_XcbResponseTypeMask)
{
case XCB_CONFIGURE_NOTIFY:
case XCB_CONFIGURE_NOTIFY:
{
xcb_configure_notify_event_t* cne = reinterpret_cast<xcb_configure_notify_event_t*>(event);
WindowSizeChanged(aznumeric_cast<uint32_t>(cne->width),
aznumeric_cast<uint32_t>(cne->height));
if ((cne->width != m_width) || (cne->height != m_height))
{
WindowSizeChanged(aznumeric_cast<uint32_t>(cne->width), aznumeric_cast<uint32_t>(cne->height));
}
break;
}
case XCB_CLIENT_MESSAGE:
case XCB_CLIENT_MESSAGE:
{
xcb_client_message_event_t* cme = reinterpret_cast<xcb_client_message_event_t*>(event);
if ((cme->type == m_xcbAtomProtocols) &&
(cme->format == s_XcbFormatDataSize) &&
(cme->data.data32[0] == m_xcbAtomDeleteWindow))
if ((cme->type == WM_PROTOCOLS) && (cme->format == s_XcbFormatDataSize) && (cme->data.data32[0] == WM_DELETE_WINDOW))
{
Deactivate();
@@ -239,7 +396,8 @@ namespace AzFramework
if (m_activated)
{
WindowNotificationBus::Event(reinterpret_cast<NativeWindowHandle>(m_xcbWindow), &WindowNotificationBus::Events::OnWindowResized, width, height);
WindowNotificationBus::Event(
reinterpret_cast<NativeWindowHandle>(m_xcbWindow), &WindowNotificationBus::Events::OnWindowResized, width, height);
}
}
}
@@ -27,15 +27,16 @@ namespace AzFramework
////////////////////////////////////////////////////////////////////////////////////////////
// NativeWindow::Implementation
void InitWindow(const AZStd::string& title,
const WindowGeometry& geometry,
const WindowStyleMasks& styleMasks) override;
void InitWindow(const AZStd::string& title, const WindowGeometry& geometry, const WindowStyleMasks& styleMasks) override;
void Activate() override;
void Deactivate() override;
NativeWindowHandle GetWindowHandle() const override;
void SetWindowTitle(const AZStd::string& title) override;
void ResizeClientArea(WindowSize clientAreaSize) override;
uint32_t GetDisplayRefreshRate() const override;
uint32_t GetDisplayRefreshRate() const override;
bool GetFullScreenState() const override;
void SetFullScreenState(bool fullScreenState) override;
////////////////////////////////////////////////////////////////////////////////////////////
// XcbEventHandlerBus::Handler
@@ -44,10 +45,46 @@ namespace AzFramework
private:
bool ValidateXcbResult(xcb_void_cookie_t cookie);
void WindowSizeChanged(const uint32_t width, const uint32_t height);
int SetAtom(xcb_window_t window, xcb_atom_t atom, xcb_atom_t type, size_t len, void* data);
xcb_connection_t* m_xcbConnection = nullptr;
xcb_window_t m_xcbWindow = 0;
xcb_atom_t m_xcbAtomProtocols;
xcb_atom_t m_xcbAtomDeleteWindow;
// Initialize one atom.
xcb_atom_t GetAtom(const char* atomName);
// Initialize all used atoms.
void InitializeAtoms();
void GetWMStates();
xcb_connection_t* m_xcbConnection = nullptr;
xcb_screen_t* m_xcbRootScreen = nullptr;
xcb_window_t m_xcbWindow = 0;
int32_t m_posX;
int32_t m_posY;
bool m_fullscreenState = false;
bool m_horizontalyMaximized = false;
bool m_verticallyMaximized = false;
// Use exact atom names for easy readability and usage.
xcb_atom_t WM_PROTOCOLS;
xcb_atom_t WM_DELETE_WINDOW;
// This atom is used to activate a window.
xcb_atom_t _NET_ACTIVE_WINDOW;
// This atom is use to bypass a compositor. Used during fullscreen mode.
xcb_atom_t _NET_WM_BYPASS_COMPOSITOR;
// This atom is used to change the state of a window using the WM.
xcb_atom_t _NET_WM_STATE;
// This atom is used to enable/disable fullscreen mode of a window.
xcb_atom_t _NET_WM_STATE_FULLSCREEN;
// This atom is used to extend the window to max vertically.
xcb_atom_t _NET_WM_STATE_MAXIMIZED_VERT;
// This atom is used to extend the window to max horizontally.
xcb_atom_t _NET_WM_STATE_MAXIMIZED_HORZ;
// This atom is used to position and resize a window.
xcb_atom_t _NET_MOVERESIZE_WINDOW;
// This atom is used to request the extent of the window.
xcb_atom_t _NET_REQUEST_FRAME_EXTENTS;
// This atom is used to identify the reply event for _NET_REQUEST_FRAME_EXTENTS
xcb_atom_t _NET_FRAME_EXTENTS;
// This atom is used to allow WM to kill app if not responsive anymore
xcb_atom_t _NET_WM_PID;
};
} // namespace AzFramework
@@ -12,6 +12,8 @@ set(FILES
AzFramework/XcbConnectionManager.h
AzFramework/XcbInputDeviceKeyboard.cpp
AzFramework/XcbInputDeviceKeyboard.h
AzFramework/XcbInputDeviceMouse.cpp
AzFramework/XcbInputDeviceMouse.h
AzFramework/XcbInterface.h
AzFramework/XcbNativeWindow.cpp
AzFramework/XcbNativeWindow.h
@@ -0,0 +1,27 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#if PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB
#include <AzFramework/XcbInputDeviceMouse.h>
#endif
namespace AzFramework
{
InputDeviceMouse::Implementation* InputDeviceMouse::Implementation::Create(InputDeviceMouse& inputDevice)
{
#if PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB
return XcbInputDeviceMouse::Create(inputDevice);
#elif PAL_TRAIT_LINUX_WINDOW_MANAGER_WAYLAND
#error "Linux Window Manager Wayland not supported."
return nullptr;
#else
#error "Linux Window Manager not recognized."
return nullptr;
#endif // PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB
}
} // namespace AzFramework
@@ -22,8 +22,10 @@ if (${PAL_TRAIT_LINUX_WINDOW_MANAGER} STREQUAL "xcb")
PRIVATE
3rdParty::X11::xcb
3rdParty::X11::xcb_xkb
3rdParty::X11::xcb_xfixes
3rdParty::X11::xkbcommon
3rdParty::X11::xkbcommon_X11
xcb-xinput
)
elseif(PAL_TRAIT_LINUX_WINDOW_MANAGER STREQUAL "wayland")
@@ -22,8 +22,8 @@ set(FILES
AzFramework/Windowing/NativeWindow_Linux.cpp
../Common/Unimplemented/AzFramework/Input/Devices/Gamepad/InputDeviceGamepad_Unimplemented.cpp
AzFramework/Input/Devices/Keyboard/InputDeviceKeyboard_Linux.cpp
AzFramework/Input/Devices/Mouse/InputDeviceMouse_Linux.cpp
../Common/Unimplemented/AzFramework/Input/Devices/Motion/InputDeviceMotion_Unimplemented.cpp
../Common/Unimplemented/AzFramework/Input/Devices/Mouse/InputDeviceMouse_Unimplemented.cpp
../Common/Unimplemented/AzFramework/Input/Devices/Touch/InputDeviceTouch_Unimplemented.cpp
AzFramework/Input/User/LocalUserId_Platform.h
../Common/Default/AzFramework/Input/User/LocalUserId_Default.h
+6 -2
View File
@@ -92,10 +92,14 @@ namespace AZ
void ApplyGlobalParameters(int* argc, char** argv)
{
// this is a hook that can be used to apply any other global non-google parameters
// that we use.
// this is a hook that can be used to apply any other global parameters that we use.
AZ_UNUSED(argc);
AZ_UNUSED(argv);
// Disable gtest catching unhandled exceptions, instead, AzTestRunner will do it through:
// AZ::Debug::Trace::HandleExceptions(true). This gives us a stack trace when the exception
// is thrown (googletest does not).
testing::FLAGS_gtest_catch_exceptions = false;
}
//! Print out parameters that are not used by the framework
@@ -12,7 +12,7 @@
#include <Prefab/PrefabSystemComponentInterface.h>
#include <Prefab/PrefabSystemScriptingHandler.h>
#include <AzCore/Component/Entity.h>
#include <AzCore/Component/TransformBus.h>
#include <Prefab/EditorPrefabComponent.h>
#include <ToolsComponents/TransformComponent.h>
namespace AzToolsFramework::Prefab
@@ -72,6 +72,7 @@ namespace AzToolsFramework::Prefab
entities, commonRoot, &topLevelEntities);
auto containerEntity = AZStd::make_unique<AZ::Entity>();
containerEntity->CreateComponent<Prefab::EditorPrefabComponent>();
for (AZ::Entity* entity : topLevelEntities)
{
@@ -766,15 +766,8 @@ namespace AzToolsFramework
{
return false;
}
outPrefabAssetPath = product->GetRelativePath();
auto asset = AZ::Data::AssetManager::Instance().GetAsset(
product->GetAssetId(),
azrtti_typeid<AZ::Prefab::ProceduralPrefabAsset>(),
AZ::Data::AssetLoadBehavior::Default);
return asset.BlockUntilLoadComplete() != AZ::Data::AssetData::AssetStatus::Error;
return true;
}
void PrefabIntegrationManager::WarnUserOfError(AZStd::string_view title, AZStd::string_view message)
@@ -115,6 +115,33 @@ namespace AzToolsFramework
}
}
CursorEntityIdQuery::CursorEntityIdQuery(AZ::EntityId entityId, AZ::EntityId rootEntityId)
: m_entityId(entityId)
, m_containerAncestorEntityId(rootEntityId)
{
}
AZ::EntityId CursorEntityIdQuery::EntityIdUnderCursor() const
{
return m_entityId;
}
AZ::EntityId CursorEntityIdQuery::ContainerAncestorEntityId() const
{
return m_containerAncestorEntityId;
}
bool CursorEntityIdQuery::HasContainerAncestorEntityId() const
{
if (m_entityId.IsValid())
{
return m_entityId != m_containerAncestorEntityId;
}
return false;
}
EditorHelpers::EditorHelpers(const EditorVisibleEntityDataCache* entityDataCache)
: m_entityDataCache(entityDataCache)
{
@@ -131,7 +158,7 @@ namespace AzToolsFramework
m_invalidClicks = AZStd::make_unique<InvalidClicks>(AZStd::move(invalidClicks));
}
AZ::EntityId EditorHelpers::HandleMouseInteraction(
CursorEntityIdQuery EditorHelpers::FindEntityIdUnderCursor(
const AzFramework::CameraState& cameraState, const ViewportInteraction::MouseInteractionEvent& mouseInteraction)
{
AZ_PROFILE_FUNCTION(AzToolsFramework);
@@ -202,17 +229,18 @@ namespace AzToolsFramework
m_invalidClicks->AddInvalidClick(mouseInteraction.m_mouseInteraction.m_mousePick.m_screenCoordinates);
}
return AZ::EntityId();
return CursorEntityIdQuery(AZ::EntityId(), AZ::EntityId());
}
// container entity support - if the entity that is being selected is part of a closed container,
// change the selection to the container instead.
if (ContainerEntityInterface* containerEntityInterface = AZ::Interface<ContainerEntityInterface>::Get())
{
return containerEntityInterface->FindHighestSelectableEntity(entityIdUnderCursor);
const auto highestSelectableEntity = containerEntityInterface->FindHighestSelectableEntity(entityIdUnderCursor);
return CursorEntityIdQuery(entityIdUnderCursor, highestSelectableEntity);
}
return entityIdUnderCursor;
return CursorEntityIdQuery(entityIdUnderCursor, AZ::EntityId());
}
void EditorHelpers::Display2d(
@@ -32,6 +32,28 @@ namespace AzToolsFramework
struct MouseInteractionEvent;
}
//!< Represents the result of a query to find the id of the entity under the cursor (if any).
class CursorEntityIdQuery
{
public:
CursorEntityIdQuery(AZ::EntityId entityId, AZ::EntityId rootEntityId);
//! Returns the entity id under the cursor (if any).
//! @note In the case of no entity id under the cursor, an invalid entity id is returned.
AZ::EntityId EntityIdUnderCursor() const;
//! Returns the topmost container entity id in the hierarchy if the entity id under the cursor is inside a container entity, otherwise returns the entity id.
//! @note In the case of no entity id under the cursor, an invalid entity id is returned.
AZ::EntityId ContainerAncestorEntityId() const;
//! Returns true if the query has a container ancestor entity id, otherwise false.
bool HasContainerAncestorEntityId() const;
private:
AZ::EntityId m_entityId; //<! The entity id under the cursor.
AZ::EntityId m_containerAncestorEntityId; //<! For entities in container entities, the topmost container entity id in the hierarchy, otherwise the entity id under the cursor.
};
//! EditorHelpers are the visualizations that appear for entities
//! when 'Display Helpers' is toggled on inside the editor.
//! These include but are not limited to entity icons and shape visualizations.
@@ -47,9 +69,9 @@ namespace AzToolsFramework
EditorHelpers& operator=(const EditorHelpers&) = delete;
~EditorHelpers() = default;
//! Handle any mouse interaction with the EditorHelpers.
//! Finds the id of the entity under the cursor (if any). For entities in container entities, also finds the topmost container entity id in the hierarchy.
//! Used to check if a particular entity was selected.
AZ::EntityId HandleMouseInteraction(
CursorEntityIdQuery FindEntityIdUnderCursor(
const AzFramework::CameraState& cameraState, const ViewportInteraction::MouseInteractionEvent& mouseInteraction);
//! Do the drawing responsible for the EditorHelpers.
@@ -80,7 +80,7 @@ namespace AzToolsFramework
const AzFramework::CameraState cameraState = GetCameraState(viewportId);
m_cachedEntityIdUnderCursor = m_editorHelpers->HandleMouseInteraction(cameraState, mouseInteraction);
m_cachedEntityIdUnderCursor = m_editorHelpers->FindEntityIdUnderCursor(cameraState, mouseInteraction).ContainerAncestorEntityId();
// when left clicking, if we successfully clicked an entity, assign that
// to the entity field selected in the entity inspector (RPE)
@@ -27,6 +27,7 @@
#include <AzToolsFramework/Manipulators/ScaleManipulators.h>
#include <AzToolsFramework/Manipulators/TranslationManipulators.h>
#include <AzToolsFramework/Maths/TransformUtils.h>
#include <AzToolsFramework/Prefab/PrefabFocusInterface.h>
#include <AzToolsFramework/ToolsComponents/EditorLockComponentBus.h>
#include <AzToolsFramework/ToolsComponents/EditorVisibilityBus.h>
#include <AzToolsFramework/ToolsComponents/TransformComponent.h>
@@ -1799,7 +1800,8 @@ namespace AzToolsFramework
const AzFramework::ViewportId viewportId = mouseInteraction.m_mouseInteraction.m_interactionId.m_viewportId;
const AzFramework::CameraState cameraState = GetCameraState(viewportId);
m_cachedEntityIdUnderCursor = m_editorHelpers->HandleMouseInteraction(cameraState, mouseInteraction);
const auto cursorEntityIdQuery = m_editorHelpers->FindEntityIdUnderCursor(cameraState, mouseInteraction);
m_cachedEntityIdUnderCursor = cursorEntityIdQuery.ContainerAncestorEntityId();
const auto selectClickEvent = ClickDetectorEventFromViewportInteraction(mouseInteraction);
m_cursorState.SetCurrentPosition(mouseInteraction.m_mouseInteraction.m_mousePick.m_screenCoordinates);
@@ -1825,8 +1827,6 @@ namespace AzToolsFramework
}
}
const AZ::EntityId entityIdUnderCursor = m_cachedEntityIdUnderCursor;
EditorContextMenuUpdate(m_contextMenu, mouseInteraction);
m_boxSelect.HandleMouseInteraction(mouseInteraction);
@@ -1842,6 +1842,21 @@ namespace AzToolsFramework
return true;
}
const AZ::EntityId entityIdUnderCursor = m_cachedEntityIdUnderCursor;
if (mouseInteraction.m_mouseEvent == ViewportInteraction::MouseEvent::DoubleClick &&
mouseInteraction.m_mouseInteraction.m_mouseButtons.Left())
{
if (cursorEntityIdQuery.HasContainerAncestorEntityId())
{
if (auto prefabFocusInterface = AZ::Interface<Prefab::PrefabFocusInterface>::Get())
{
prefabFocusInterface->FocusOnPrefabInstanceOwningEntityId(cursorEntityIdQuery.ContainerAncestorEntityId());
return false;
}
}
}
bool stickySelect = false;
ViewportInteraction::ViewportSettingsRequestBus::EventResult(
stickySelect, viewportId, &ViewportInteraction::ViewportSettingsRequestBus::Events::StickySelectEnabled);
@@ -291,6 +291,9 @@ namespace AssetBundler
int main(int argc, char* argv[])
{
AZ::Debug::Trace::HandleExceptions(true);
AZ::Test::ApplyGlobalParameters(&argc, argv);
INVOKE_AZ_UNIT_TEST_MAIN();
AZ::AllocatorInstance<AZ::SystemAllocator>::Create();
@@ -262,7 +262,7 @@ namespace UnitTests
auto result = m_data->m_reporter->ComputeDestination(entryContainer, m_data->m_platformConfig.GetScanFolderByPath(scanFolderEntry.m_scanFolder.c_str()), source, destination, destInfo);
ASSERT_EQ(result.IsSuccess(), expectSuccess) << result.GetError().c_str();
ASSERT_EQ(result.IsSuccess(), expectSuccess) << (!result.IsSuccess() ? result.GetError().c_str() : "");
if (expectSuccess)
{
@@ -29,6 +29,9 @@ int main(int argc, char* argv[])
{
qputenv("QT_MAC_DISABLE_FOREGROUND_APPLICATION_TRANSFORM", "1");
AZ::Debug::Trace::HandleExceptions(true);
AZ::Test::ApplyGlobalParameters(&argc, argv);
// If "--unittest" is present on the command line, run unit testing
// and return immediately. Otherwise, continue as normal.
AZ::Test::addTestEnvironment(new BaseAssetProcessorTestEnvironment());
+2
View File
@@ -189,6 +189,8 @@ namespace AzTestRunner
int wrapped_main(int argc/*=0*/, char** argv/*=nullptr*/)
{
AZ::Debug::Trace::HandleExceptions(true);
if (argc>0 && argv!=nullptr)
{
return wrapped_command_arg_main(argc, argv);
@@ -7,7 +7,7 @@
*/
#include <GemRepo/GemRepoAddDialog.h>
#include <FormLineEditWidget.h>
#include <FormFolderBrowseEditWidget.h>
#include <QVBoxLayout>
#include <QLabel>
@@ -40,7 +40,7 @@ namespace O3DE::ProjectManager
instructionContextLabel->setAlignment(Qt::AlignLeft);
vLayout->addWidget(instructionContextLabel);
m_repoPath = new FormLineEditWidget(tr("Repository Path"), "", this);
m_repoPath = new FormFolderBrowseEditWidget(tr("Repository Path"), "", this);
m_repoPath->setFixedWidth(600);
vLayout->addWidget(m_repoPath);
@@ -36,7 +36,7 @@ namespace O3DE::ProjectManager
QString m_summary = "No summary provided.";
QString m_additionalInfo = "";
QString m_directoryLink = "";
QString m_repoLink = "";
QString m_repoUri = "";
QStringList m_includedGemPaths = {};
QDateTime m_lastUpdated;
};
@@ -60,8 +60,8 @@ namespace O3DE::ProjectManager
// Repo name and url link
m_nameLabel->setText(m_model->GetName(modelIndex));
m_repoLinkLabel->setText(m_model->GetRepoLink(modelIndex));
m_repoLinkLabel->SetUrl(m_model->GetRepoLink(modelIndex));
m_repoLinkLabel->setText(m_model->GetRepoUri(modelIndex));
m_repoLinkLabel->SetUrl(m_model->GetRepoUri(modelIndex));
// Repo summary
m_summaryLabel->setText(m_model->GetSummary(modelIndex));
@@ -145,6 +145,11 @@ namespace O3DE::ProjectManager
GemRepoModel::SetEnabled(*model, modelIndex, !isAdded);
return true;
}
else if (keyEvent->key() == Qt::Key_X)
{
emit RemoveRepo(modelIndex);
return true;
}
}
if (event->type() == QEvent::MouseButtonPress)
@@ -154,6 +159,7 @@ namespace O3DE::ProjectManager
QRect fullRect, itemRect, contentRect;
CalcRects(option, fullRect, itemRect, contentRect);
const QRect buttonRect = CalcButtonRect(contentRect);
const QRect deleteButtonRect = CalcDeleteButtonRect(contentRect);
if (buttonRect.contains(mouseEvent->pos()))
{
@@ -161,6 +167,11 @@ namespace O3DE::ProjectManager
GemRepoModel::SetEnabled(*model, modelIndex, !isAdded);
return true;
}
else if (deleteButtonRect.contains(mouseEvent->pos()))
{
emit RemoveRepo(modelIndex);
return true;
}
}
return QStyledItemDelegate::editorEvent(event, model, option, modelIndex);
@@ -214,9 +225,14 @@ namespace O3DE::ProjectManager
painter->restore();
}
QRect GemRepoItemDelegate::CalcDeleteButtonRect(const QRect& contentRect) const
{
const QPoint topLeft = QPoint(contentRect.right() - s_iconSize, contentRect.center().y() - s_iconSize / 2);
return QRect(topLeft, QSize(s_iconSize, s_iconSize));
}
void GemRepoItemDelegate::DrawEditButtons(QPainter* painter, const QRect& contentRect) const
{
painter->drawPixmap(contentRect.right() - s_iconSize * 2 - s_iconSpacing, contentRect.center().y() - s_iconSize / 2, m_editIcon);
painter->drawPixmap(contentRect.right() - s_iconSize, contentRect.center().y() - s_iconSize / 2, m_deleteIcon);
}
@@ -66,10 +66,14 @@ namespace O3DE::ProjectManager
inline constexpr static int s_refreshIconSize = 14;
inline constexpr static int s_refreshIconSpacing = 10;
signals:
void RemoveRepo(const QModelIndex& modelIndex);
protected:
void CalcRects(const QStyleOptionViewItem& option, QRect& outFullRect, QRect& outItemRect, QRect& outContentRect) const;
QRect GetTextRect(QFont& font, const QString& text, qreal fontSize) const;
QRect CalcButtonRect(const QRect& contentRect) const;
QRect CalcDeleteButtonRect(const QRect& contentRect) const;
void DrawButton(QPainter* painter, const QRect& contentRect, const QModelIndex& modelIndex) const;
void DrawEditButtons(QPainter* painter, const QRect& contentRect) const;
@@ -9,6 +9,8 @@
#include <GemRepo/GemRepoListView.h>
#include <GemRepo/GemRepoItemDelegate.h>
#include <QShortcut>
namespace O3DE::ProjectManager
{
GemRepoListView::GemRepoListView(QAbstractItemModel* model, QItemSelectionModel* selectionModel, QWidget* parent)
@@ -19,6 +21,9 @@ namespace O3DE::ProjectManager
setModel(model);
setSelectionModel(selectionModel);
setItemDelegate(new GemRepoItemDelegate(model, this));
GemRepoItemDelegate* itemDelegate = new GemRepoItemDelegate(model, this);
connect(itemDelegate, &GemRepoItemDelegate::RemoveRepo, this, &GemRepoListView::RemoveRepo);
setItemDelegate(itemDelegate);
}
} // namespace O3DE::ProjectManager
@@ -25,5 +25,8 @@ namespace O3DE::ProjectManager
public:
explicit GemRepoListView(QAbstractItemModel* model, QItemSelectionModel* selectionModel, QWidget* parent = nullptr);
~GemRepoListView() = default;
signals:
void RemoveRepo(const QModelIndex& modelIndex);
};
} // namespace O3DE::ProjectManager
@@ -37,7 +37,7 @@ namespace O3DE::ProjectManager
item->setData(gemRepoInfo.m_summary, RoleSummary);
item->setData(gemRepoInfo.m_isEnabled, RoleIsEnabled);
item->setData(gemRepoInfo.m_directoryLink, RoleDirectoryLink);
item->setData(gemRepoInfo.m_repoLink, RoleRepoLink);
item->setData(gemRepoInfo.m_repoUri, RoleRepoUri);
item->setData(gemRepoInfo.m_lastUpdated, RoleLastUpdated);
item->setData(gemRepoInfo.m_path, RolePath);
item->setData(gemRepoInfo.m_additionalInfo, RoleAdditionalInfo);
@@ -83,9 +83,9 @@ namespace O3DE::ProjectManager
return modelIndex.data(RoleDirectoryLink).toString();
}
QString GemRepoModel::GetRepoLink(const QModelIndex& modelIndex)
QString GemRepoModel::GetRepoUri(const QModelIndex& modelIndex)
{
return modelIndex.data(RoleRepoLink).toString();
return modelIndex.data(RoleRepoUri).toString();
}
QDateTime GemRepoModel::GetLastUpdated(const QModelIndex& modelIndex)
@@ -35,7 +35,7 @@ namespace O3DE::ProjectManager
static QString GetSummary(const QModelIndex& modelIndex);
static QString GetAdditionalInfo(const QModelIndex& modelIndex);
static QString GetDirectoryLink(const QModelIndex& modelIndex);
static QString GetRepoLink(const QModelIndex& modelIndex);
static QString GetRepoUri(const QModelIndex& modelIndex);
static QDateTime GetLastUpdated(const QModelIndex& modelIndex);
static QString GetPath(const QModelIndex& modelIndex);
@@ -55,7 +55,7 @@ namespace O3DE::ProjectManager
RoleSummary,
RoleIsEnabled,
RoleDirectoryLink,
RoleRepoLink,
RoleRepoUri,
RoleLastUpdated,
RolePath,
RoleAdditionalInfo,
@@ -25,6 +25,7 @@
#include <QTableWidget>
#include <QFrame>
#include <QStackedWidget>
#include <QMessageBox>
namespace O3DE::ProjectManager
{
@@ -79,21 +80,48 @@ namespace O3DE::ProjectManager
if (repoAddDialog->exec() == QDialog::DialogCode::Accepted)
{
QString repoUrl = repoAddDialog->GetRepoPath();
if (repoUrl.isEmpty())
QString repoUri = repoAddDialog->GetRepoPath();
if (repoUri.isEmpty())
{
QMessageBox::warning(this, tr("No Input"), tr("Please provide a repo Uri."));
return;
}
AZ::Outcome<void, AZStd::string> addGemRepoResult = PythonBindingsInterface::Get()->AddGemRepo(repoUrl);
if (addGemRepoResult.IsSuccess())
bool addGemRepoResult = PythonBindingsInterface::Get()->AddGemRepo(repoUri);
if (addGemRepoResult)
{
Reinit();
}
else
{
QMessageBox::critical(this, tr("Operation failed"),
QString("Failed to add gem repo: %1.<br>Error:<br>%2").arg(repoUrl, addGemRepoResult.GetError().c_str()));
QString failureMessage = tr("Failed to add gem repo: %1.").arg(repoUri);
QMessageBox::critical(this, tr("Operation failed"), failureMessage);
AZ_Error("Project Manger", false, failureMessage.toUtf8());
}
}
}
void GemRepoScreen::HandleRemoveRepoButton(const QModelIndex& modelIndex)
{
QString repoName = m_gemRepoModel->GetName(modelIndex);
QMessageBox::StandardButton warningResult = QMessageBox::warning(
this, tr("Remove Repo"), tr("Are you sure you would like to remove gem repo: %1?").arg(repoName),
QMessageBox::No | QMessageBox::Yes);
if (warningResult == QMessageBox::Yes)
{
QString repoUri = m_gemRepoModel->GetRepoUri(modelIndex);
bool removeGemRepoResult = PythonBindingsInterface::Get()->RemoveGemRepo(repoUri);
if (removeGemRepoResult)
{
Reinit();
}
else
{
QString failureMessage = tr("Failed to remove gem repo: %1.").arg(repoUri);
QMessageBox::critical(this, tr("Operation failed"), failureMessage);
AZ_Error("Project Manger", false, failureMessage.toUtf8());
}
}
}
@@ -251,6 +279,8 @@ namespace O3DE::ProjectManager
m_gemRepoListView = new GemRepoListView(m_gemRepoModel, m_gemRepoModel->GetSelectionModel(), this);
middleVLayout->addWidget(m_gemRepoListView);
connect(m_gemRepoListView, &GemRepoListView::RemoveRepo, this, &GemRepoScreen::HandleRemoveRepoButton);
hLayout->addLayout(middleVLayout);
m_gemRepoInspector = new GemRepoInspector(m_gemRepoModel, this);
@@ -39,6 +39,7 @@ namespace O3DE::ProjectManager
public slots:
void HandleAddRepoButton();
void HandleRemoveRepoButton(const QModelIndex& modelIndex);
private:
void FillModel();
@@ -939,17 +939,60 @@ namespace O3DE::ProjectManager
}
}
AZ::Outcome<void, AZStd::string> PythonBindings::AddGemRepo(const QString& repoUri)
bool PythonBindings::AddGemRepo(const QString& repoUri)
{
// o3de scripts need method added
(void)repoUri;
return AZ::Failure<AZStd::string>("Adding Gem Repo not implemented yet in o3de scripts.");
bool registrationResult = false;
bool result = ExecuteWithLock(
[&]
{
auto pyUri = QString_To_Py_String(repoUri);
auto pythonRegistrationResult = m_register.attr("register")(
pybind11::none(), pybind11::none(), pybind11::none(), pybind11::none(), pybind11::none(), pybind11::none(), pyUri);
// Returns an exit code so boolify it then invert result
registrationResult = !pythonRegistrationResult.cast<bool>();
});
return result && registrationResult;
}
bool PythonBindings::RemoveGemRepo(const QString& repoUri)
{
bool registrationResult = false;
bool result = ExecuteWithLock(
[&]
{
auto pythonRegistrationResult = m_register.attr("register")(
pybind11::none(), // engine_path
pybind11::none(), // project_path
pybind11::none(), // gem_path
pybind11::none(), // external_subdir_path
pybind11::none(), // template_path
pybind11::none(), // restricted_path
QString_To_Py_String(repoUri), // repo_uri
pybind11::none(), // default_engines_folder
pybind11::none(), // default_projects_folder
pybind11::none(), // default_gems_folder
pybind11::none(), // default_templates_folder
pybind11::none(), // default_restricted_folder
pybind11::none(), // default_third_party_folder
pybind11::none(), // external_subdir_engine_path
pybind11::none(), // external_subdir_project_path
true, // remove
false // force
);
// Returns an exit code so boolify it then invert result
registrationResult = !pythonRegistrationResult.cast<bool>();
});
return result && registrationResult;
}
GemRepoInfo PythonBindings::GetGemRepoInfo(pybind11::handle repoUri)
{
GemRepoInfo gemRepoInfo;
gemRepoInfo.m_repoLink = Py_To_String(repoUri);
gemRepoInfo.m_repoUri = Py_To_String(repoUri);
auto data = m_manifest.attr("get_repo_json_data")(repoUri);
if (pybind11::isinstance<pybind11::dict>(data))
@@ -957,7 +1000,7 @@ namespace O3DE::ProjectManager
try
{
// required
gemRepoInfo.m_repoLink = Py_To_String(data["repo_uri"]);
gemRepoInfo.m_repoUri = Py_To_String(data["repo_uri"]);
gemRepoInfo.m_name = Py_To_String(data["repo_name"]);
gemRepoInfo.m_creator = Py_To_String(data["origin"]);
@@ -1019,13 +1062,13 @@ namespace O3DE::ProjectManager
#else
GemRepoInfo mockJohnRepo("JohnCreates", "John Smith", QDateTime(QDate(2021, 8, 31), QTime(11, 57)), true);
mockJohnRepo.m_summary = "John's Summary. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce sollicitudin dapibus urna";
mockJohnRepo.m_repoLink = "https://github.com/o3de/o3de";
mockJohnRepo.m_repoUri = "https://github.com/o3de/o3de";
mockJohnRepo.m_additionalInfo = "John's additional info. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce sollicitu.";
gemRepos.push_back(mockJohnRepo);
GemRepoInfo mockJaneRepo("JanesGems", "Jane Doe", QDateTime(QDate(2021, 9, 10), QTime(18, 23)), false);
mockJaneRepo.m_summary = "Jane's Summary.";
mockJaneRepo.m_repoLink = "https://github.com/o3de/o3de.org";
mockJaneRepo.m_repoUri = "https://github.com/o3de/o3de.org";
gemRepos.push_back(mockJaneRepo);
#endif // MOCK_GEM_REPO_INFO
@@ -58,7 +58,8 @@ namespace O3DE::ProjectManager
AZ::Outcome<QVector<ProjectTemplateInfo>> GetProjectTemplates(const QString& projectPath = {}) override;
// Gem Repos
AZ::Outcome<void, AZStd::string> AddGemRepo(const QString& repoUri) override;
bool AddGemRepo(const QString& repoUri) override;
bool RemoveGemRepo(const QString& repoUri) override;
AZ::Outcome<QVector<GemRepoInfo>, AZStd::string> GetAllGemRepoInfos() override;
private:
@@ -169,11 +169,18 @@ namespace O3DE::ProjectManager
// Gem Repos
/**
* A gem repo to engine. Registers this gem repo with the current engine.
* @param repoUri the absolute filesystem path or url to the gem repo manifest file.
* @return An outcome with the success flag as well as an error message in case of a failure.
* Registers this gem repo with the current engine.
* @param repoUri the absolute filesystem path or url to the gem repo.
* @return true on success, false on failure.
*/
virtual AZ::Outcome<void, AZStd::string> AddGemRepo(const QString& repoUri) = 0;
virtual bool AddGemRepo(const QString& repoUri) = 0;
/**
* Unregisters this gem repo with the current engine.
* @param repoUri the absolute filesystem path or url to the gem repo.
* @return true on success, false on failure.
*/
virtual bool RemoveGemRepo(const QString& repoUri) = 0;
/**
* Get all available gem repo infos. Gathers all repos registered with the engine.
+3
View File
@@ -18,6 +18,9 @@ int runDefaultRunner(int argc, char* argv[])
int main(int argc, char* argv[])
{
AZ::Debug::Trace::HandleExceptions(true);
AZ::Test::ApplyGlobalParameters(&argc, argv);
if (argc == 1)
{
// if no parameters are provided, add the --unittests parameter
@@ -23,6 +23,9 @@ int runDefaultRunner(int argc, char* argv[])
int main(int argc, char* argv[])
{
AZ::Debug::Trace::HandleExceptions(true);
AZ::Test::ApplyGlobalParameters(&argc, argv);
// ran with no parameters?
if (argc == 1)
{
@@ -94,4 +94,41 @@ namespace AWSGameLift
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
};
using AWSGameLiftMatchmakingRequestBus = AZ::EBus<AzFramework::IMatchmakingRequests, AWSGameLiftMatchmakingRequests>;
//! IAWSGameLiftMatchmakingEventRequests
//! GameLift Gem matchmaking event interfaces which is used to track matchmaking ticket event
//! Developer should define the way to poll matchmaking ticket event and behavior based on the ticket status
//! Use AWSGameLiftClientLocalTicketTracker as an example, it uses continuous polling to query matchmaking ticket:
//! StartPolling - local ticket tracker starts monitor process for matchmaking ticket, and joins player
//! to the match once ticket is complete
//! StopPolling - local ticket tracker cancels ongoing matchmaking ticket and stops monitoring process
class IAWSGameLiftMatchmakingEventRequests
{
public:
AZ_RTTI(IAWSGameLiftMatchmakingEventRequests, "{C2DA440E-74E0-411E-813D-5880B50B0C9E}");
IAWSGameLiftMatchmakingEventRequests() = default;
virtual ~IAWSGameLiftMatchmakingEventRequests() = default;
//! StartPolling
//! Request to start process for polling matchmaking ticket based on given ticket id and player Id
//! @param ticketId The requested matchmaking ticket id
//! @param playerId The requested matchmaking player id
virtual void StartPolling(const AZStd::string& ticketId, const AZStd::string& playerId) = 0;
//! StopPolling
//! Request to stop process for polling matchmaking ticket
virtual void StopPolling() = 0;
};
// IAWSGameLiftMatchmakingEventRequests EBus wrapper for scripting
class AWSGameLiftMatchmakingEventRequests
: public AZ::EBusTraits
{
public:
using MutexType = AZStd::recursive_mutex;
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
};
using AWSGameLiftMatchmakingEventRequestBus = AZ::EBus<IAWSGameLiftMatchmakingEventRequests, AWSGameLiftMatchmakingEventRequests>;
} // namespace AWSGameLift
@@ -30,12 +30,14 @@ namespace AWSGameLift
void AWSGameLiftClientLocalTicketTracker::ActivateTracker()
{
AZ::Interface<IAWSGameLiftMatchmakingInternalRequests>::Register(this);
AZ::Interface<IAWSGameLiftMatchmakingEventRequests>::Register(this);
AWSGameLiftMatchmakingEventRequestBus::Handler::BusConnect();
}
void AWSGameLiftClientLocalTicketTracker::DeactivateTracker()
{
AZ::Interface<IAWSGameLiftMatchmakingInternalRequests>::Unregister(this);
AWSGameLiftMatchmakingEventRequestBus::Handler::BusDisconnect();
AZ::Interface<IAWSGameLiftMatchmakingEventRequests>::Unregister(this);
StopPolling();
}
@@ -12,7 +12,7 @@
#include <AzCore/std/parallel/mutex.h>
#include <AzCore/std/parallel/thread.h>
#include <Request/IAWSGameLiftMatchmakingInternalRequests.h>
#include <Request/IAWSGameLiftRequests.h>
#include <aws/gamelift/model/MatchmakingTicket.h>
@@ -30,7 +30,7 @@ namespace AWSGameLift
//! For use in production, please see GameLifts guidance about matchmaking at volume.
//! The continuous polling approach here is only suitable for low volume matchmaking and is meant to aid with development only
class AWSGameLiftClientLocalTicketTracker
: public IAWSGameLiftMatchmakingInternalRequests
: public AWSGameLiftMatchmakingEventRequestBus::Handler
{
public:
static constexpr const char AWSGameLiftClientLocalTicketTrackerName[] = "AWSGameLiftClientLocalTicketTracker";
@@ -44,7 +44,7 @@ namespace AWSGameLift
virtual void ActivateTracker();
virtual void DeactivateTracker();
// IAWSGameLiftMatchmakingInternalRequests interface implementation
// AWSGameLiftMatchmakingEventRequestBus interface implementation
void StartPolling(const AZStd::string& ticketId, const AZStd::string& playerId) override;
void StopPolling() override;
@@ -12,6 +12,7 @@
#include <AzCore/Serialization/EditContextConstants.inl>
#include <AzFramework/Session/SessionConfig.h>
#include <AWSGameLiftClientLocalTicketTracker.h>
#include <AWSGameLiftClientManager.h>
#include <AWSGameLiftClientSystemComponent.h>
#include <Request/AWSGameLiftAcceptMatchRequest.h>
@@ -59,10 +60,17 @@ namespace AWSGameLift
behaviorContext->EBus<AWSGameLiftRequestBus>("AWSGameLiftRequestBus")
->Attribute(AZ::Script::Attributes::Category, "AWSGameLift")
->Event("ConfigureGameLiftClient", &AWSGameLiftRequestBus::Events::ConfigureGameLiftClient,
{{{"Region", ""}}})
{ { { "Region", "" } } })
->Event("CreatePlayerId", &AWSGameLiftRequestBus::Events::CreatePlayerId,
{{{"IncludeBrackets", ""},
{"IncludeDashes", ""}}});
{ { { "IncludeBrackets", "" },
{ "IncludeDashes", "" } } });
behaviorContext->EBus<AWSGameLiftMatchmakingEventRequestBus>("AWSGameLiftMatchmakingEventRequestBus")
->Attribute(AZ::Script::Attributes::Category, "AWSGameLift")
->Event("StartPolling", &AWSGameLiftMatchmakingEventRequestBus::Events::StartPolling,
{ { { "TicketId", "" },
{ "PlayerId", "" } } })
->Event("StopPolling", &AWSGameLiftMatchmakingEventRequestBus::Events::StopPolling);
}
}
@@ -11,12 +11,12 @@
#include <AzCore/Component/Component.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <AWSGameLiftClientLocalTicketTracker.h>
#include <Request/IAWSGameLiftInternalRequests.h>
namespace AWSGameLift
{
class AWSGameLiftClientManager;
class AWSGameLiftClientLocalTicketTracker;
//! Gem client system component. Responsible for creating the gamelift client manager.
class AWSGameLiftClientSystemComponent
@@ -1,39 +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/RTTI/RTTI.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/std/string/string.h>
namespace AWSGameLift
{
//! IAWSGameLiftMatchmakingInternalRequests
//! GameLift Gem matchmaking internal interfaces which is used to communicate
//! with client side ticket tracker to sync matchmaking ticket data and join
//! player to the match
class IAWSGameLiftMatchmakingInternalRequests
{
public:
AZ_RTTI(IAWSGameLiftMatchmakingInternalRequests, "{C2DA440E-74E0-411E-813D-5880B50B0C9E}");
IAWSGameLiftMatchmakingInternalRequests() = default;
virtual ~IAWSGameLiftMatchmakingInternalRequests() = default;
//! StartPolling
//! Request to start process for polling matchmaking ticket based on given ticket id and player id
//! @param ticketId The requested matchmaking ticket id
//! @param playerId The requested matchmaking player id
virtual void StartPolling(const AZStd::string& ticketId, const AZStd::string& playerId) = 0;
//! StopPolling
//! Request to stop process for polling matchmaking ticket
virtual void StopPolling() = 0;
};
} // namespace AWSGameLift
@@ -36,8 +36,6 @@
#include <aws/gamelift/model/StopMatchmakingRequest.h>
#include <aws/gamelift/model/StopMatchmakingResult.h>
#include <Request/IAWSGameLiftMatchmakingInternalRequests.h>
using namespace Aws::GameLift;
class GameLiftClientMock
@@ -32,12 +32,12 @@ set(FILES
Source/Activity/AWSGameLiftLeaveSessionActivity.h
Source/Activity/AWSGameLiftSearchSessionsActivity.cpp
Source/Activity/AWSGameLiftSearchSessionsActivity.h
Source/AWSGameLiftClientLocalTicketTracker.cpp
Source/AWSGameLiftClientLocalTicketTracker.h
Source/Activity/AWSGameLiftStartMatchmakingActivity.cpp
Source/Activity/AWSGameLiftStartMatchmakingActivity.h
Source/Activity/AWSGameLiftStopMatchmakingActivity.cpp
Source/Activity/AWSGameLiftStopMatchmakingActivity.h
Source/AWSGameLiftClientLocalTicketTracker.cpp
Source/AWSGameLiftClientLocalTicketTracker.h
Source/AWSGameLiftClientManager.cpp
Source/AWSGameLiftClientManager.h
Source/AWSGameLiftClientSystemComponent.cpp
@@ -50,5 +50,4 @@ set(FILES
Source/Request/AWSGameLiftStartMatchmakingRequest.cpp
Source/Request/AWSGameLiftStopMatchmakingRequest.cpp
Source/Request/IAWSGameLiftInternalRequests.h
Source/Request/IAWSGameLiftMatchmakingInternalRequests.h
)
@@ -336,14 +336,11 @@ namespace AWSGameLift
BuildServerMatchBackfillPlayerAttributes(
players[playerIndex][AWSGameLiftMatchmakingPlayerAttributesKeyName], outPlayer);
}
}
else
{
return false;
return true;
}
}
}
return true;
return false;
}
void AWSGameLiftServerManager::BuildServerMatchBackfillPlayerAttributes(
@@ -461,12 +458,16 @@ namespace AWSGameLift
AZ_TracePrintf(AWSGameLiftServerManagerName, "Notifying GameLift server process is ending ...");
Aws::GameLift::GenericOutcome processEndingOutcome = m_gameLiftServerSDKWrapper->ProcessEnding();
AZ_TracePrintf(AWSGameLiftServerManagerName, "ProcessEnding request against Amazon GameLift service is complete.");
[[maybe_unused]] bool processEndingIsSuccess = processEndingOutcome.IsSuccess();
AZ_Error(AWSGameLiftServerManagerName, processEndingIsSuccess, AWSGameLiftServerProcessEndingErrorMessage,
processEndingOutcome.GetError().GetErrorMessage().c_str());
if (processEndingOutcome.IsSuccess())
{
AZ_TracePrintf(AWSGameLiftServerManagerName, "ProcessEnding request against Amazon GameLift service succeeded.");
AzFramework::SessionNotificationBus::Broadcast(&AzFramework::SessionNotifications::OnDestroySessionEnd);
}
else
{
AZ_Error(AWSGameLiftServerManagerName, false, AWSGameLiftServerProcessEndingErrorMessage,
processEndingOutcome.GetError().GetErrorMessage().c_str());
}
}
void AWSGameLiftServerManager::HandlePlayerLeaveSession(const AzFramework::PlayerConnectionConfig& playerConnectionConfig)
@@ -546,15 +547,16 @@ namespace AWSGameLift
{
AZ_TracePrintf(AWSGameLiftServerManagerName, "Activating GameLift game session ...");
Aws::GameLift::GenericOutcome activationOutcome = m_gameLiftServerSDKWrapper->ActivateGameSession();
AZ_TracePrintf(AWSGameLiftServerManagerName, "ActivateGameSession request against Amazon GameLift service is complete.");
if (activationOutcome.IsSuccess())
{
AZ_TracePrintf(AWSGameLiftServerManagerName, "ActivateGameSession request against Amazon GameLift service succeeded.");
// Register server manager as handler once game session has been activated
if (!AZ::Interface<AzFramework::ISessionHandlingProviderRequests>::Get())
{
AZ::Interface<AzFramework::ISessionHandlingProviderRequests>::Register(this);
}
AzFramework::SessionNotificationBus::Broadcast(&AzFramework::SessionNotifications::OnCreateSessionEnd);
}
else
{
@@ -588,17 +590,18 @@ namespace AWSGameLift
void AWSGameLiftServerManager::OnUpdateGameSession(const Aws::GameLift::Server::Model::UpdateGameSession& updateGameSession)
{
AzFramework::SessionConfig sessionConfig = BuildSessionConfig(updateGameSession.GetGameSession());
Aws::GameLift::Server::Model::UpdateReason updateReason = updateGameSession.GetUpdateReason();
AzFramework::SessionNotificationBus::Broadcast(&AzFramework::SessionNotifications::OnUpdateSessionBegin,
sessionConfig, Aws::GameLift::Server::Model::UpdateReasonMapper::GetNameForUpdateReason(updateReason).c_str());
// Update game session data locally
if (updateReason == Aws::GameLift::Server::Model::UpdateReason::MATCHMAKING_DATA_UPDATED)
{
UpdateGameSessionData(updateGameSession.GetGameSession());
}
AzFramework::SessionConfig sessionConfig = BuildSessionConfig(updateGameSession.GetGameSession());
AzFramework::SessionNotificationBus::Broadcast(
&AzFramework::SessionNotifications::OnUpdateSessionBegin,
sessionConfig,
Aws::GameLift::Server::Model::UpdateReasonMapper::GetNameForUpdateReason(updateReason).c_str());
AzFramework::SessionNotificationBus::Broadcast(&AzFramework::SessionNotifications::OnUpdateSessionEnd);
}
bool AWSGameLiftServerManager::RemoveConnectedPlayer(uint32_t playerConnectionId, AZStd::string& outPlayerSessionId)
@@ -654,7 +657,7 @@ namespace AWSGameLift
}
else
{
AZ_TracePrintf(AWSGameLiftServerManagerName, "StartMatchBackfill request against Amazon GameLift service is complete.");
AZ_TracePrintf(AWSGameLiftServerManagerName, "StartMatchBackfill request against Amazon GameLift service succeeded.");
return true;
}
}
@@ -686,7 +689,7 @@ namespace AWSGameLift
}
else
{
AZ_TracePrintf(AWSGameLiftServerManagerName, "StopMatchBackfill request against Amazon GameLift service is complete.");
AZ_TracePrintf(AWSGameLiftServerManagerName, "StopMatchBackfill request against Amazon GameLift service succeeded.");
return true;
}
}
@@ -94,7 +94,7 @@ namespace AWSGameLift
static constexpr const char AWSGameLiftMatchmakingPlayerAttributeSTypeName[] = "S";
static constexpr const char AWSGameLiftMatchmakingPlayerAttributeSServerTypeName[] = "STRING";
static constexpr const char AWSGameLiftMatchmakingPlayerAttributeNTypeName[] = "N";
static constexpr const char AWSGameLiftMatchmakingPlayerAttributeNServerTypeName[] = "NUMBER";
static constexpr const char AWSGameLiftMatchmakingPlayerAttributeNServerTypeName[] = "DOUBLE";
static constexpr const char AWSGameLiftMatchmakingPlayerAttributeSLTypeName[] = "SL";
static constexpr const char AWSGameLiftMatchmakingPlayerAttributeSLServerTypeName[] = "STRING_LIST";
static constexpr const char AWSGameLiftMatchmakingPlayerAttributeSDMTypeName[] = "SDM";
@@ -34,13 +34,20 @@ R"({
"valueAttribute":"testmode"
},
"level":{
"attributeType":"NUMBER",
"attributeType":"DOUBLE",
"valueAttribute":10.0
},
"items":{
"attributeType":"STRING_LIST",
"valueAttribute":["test1","test2","test3"]
}
}},
{"playerId":"secondplayer",
"attributes":{
"mode":{
"attributeType":"STRING",
"valueAttribute":"testmode"
}
}}
]}
]
@@ -162,8 +169,11 @@ R"({
MOCK_METHOD0(OnSessionHealthCheck, bool());
MOCK_METHOD1(OnCreateSessionBegin, bool(const AzFramework::SessionConfig&));
MOCK_METHOD0(OnCreateSessionEnd, void());
MOCK_METHOD0(OnDestroySessionBegin, bool());
MOCK_METHOD0(OnDestroySessionEnd, void());
MOCK_METHOD2(OnUpdateSessionBegin, void(const AzFramework::SessionConfig&, const AZStd::string&));
MOCK_METHOD0(OnUpdateSessionEnd, void());
};
class GameLiftServerManagerTest
@@ -254,6 +264,7 @@ R"({
EXPECT_CALL(handlerMock, OnDestroySessionBegin()).Times(1).WillOnce(testing::Return(false));
EXPECT_CALL(*(m_serverManager->m_gameLiftServerSDKWrapperMockPtr), GetTerminationTime()).Times(1);
EXPECT_CALL(*(m_serverManager->m_gameLiftServerSDKWrapperMockPtr), ProcessEnding()).Times(0);
EXPECT_CALL(handlerMock, OnDestroySessionEnd()).Times(0);
AZ_TEST_START_TRACE_SUPPRESSION;
m_serverManager->m_gameLiftServerSDKWrapperMockPtr->m_onProcessTerminateFunc();
@@ -274,13 +285,40 @@ R"({
SessionNotificationsHandlerMock handlerMock;
EXPECT_CALL(handlerMock, OnDestroySessionBegin()).Times(1).WillOnce(testing::Return(true));
EXPECT_CALL(*(m_serverManager->m_gameLiftServerSDKWrapperMockPtr), GetTerminationTime()).Times(1);
EXPECT_CALL(*(m_serverManager->m_gameLiftServerSDKWrapperMockPtr), ProcessEnding()).Times(1);
EXPECT_CALL(*(m_serverManager->m_gameLiftServerSDKWrapperMockPtr), ProcessEnding())
.Times(1)
.WillOnce(testing::Return(Aws::GameLift::GenericOutcome(nullptr)));
EXPECT_CALL(handlerMock, OnDestroySessionEnd()).Times(1);
m_serverManager->m_gameLiftServerSDKWrapperMockPtr->m_onProcessTerminateFunc();
EXPECT_FALSE(AZ::Interface<AzFramework::ISessionHandlingProviderRequests>::Get());
}
TEST_F(GameLiftServerManagerTest, OnProcessTerminate_OnDestroySessionBeginReturnsTrue_TerminationNotificationSentButFail)
{
m_serverManager->InitializeGameLiftServerSDK();
m_serverManager->NotifyGameLiftProcessReady();
if (!AZ::Interface<AzFramework::ISessionHandlingProviderRequests>::Get())
{
AZ::Interface<AzFramework::ISessionHandlingProviderRequests>::Register(m_serverManager.get());
}
SessionNotificationsHandlerMock handlerMock;
EXPECT_CALL(handlerMock, OnDestroySessionBegin()).Times(1).WillOnce(testing::Return(true));
EXPECT_CALL(*(m_serverManager->m_gameLiftServerSDKWrapperMockPtr), GetTerminationTime()).Times(1);
EXPECT_CALL(*(m_serverManager->m_gameLiftServerSDKWrapperMockPtr), ProcessEnding())
.Times(1)
.WillOnce(testing::Return(Aws::GameLift::GenericOutcome()));
EXPECT_CALL(handlerMock, OnDestroySessionEnd()).Times(0);
AZ_TEST_START_TRACE_SUPPRESSION;
m_serverManager->m_gameLiftServerSDKWrapperMockPtr->m_onProcessTerminateFunc();
AZ_TEST_STOP_TRACE_SUPPRESSION(1);
EXPECT_FALSE(AZ::Interface<AzFramework::ISessionHandlingProviderRequests>::Get());
}
TEST_F(GameLiftServerManagerTest, OnHealthCheck_OnSessionHealthCheckReturnsTrue_CallbackFunctionReturnsTrue)
{
m_serverManager->InitializeGameLiftServerSDK();
@@ -316,6 +354,7 @@ R"({
m_serverManager->NotifyGameLiftProcessReady();
SessionNotificationsHandlerMock handlerMock;
EXPECT_CALL(handlerMock, OnCreateSessionBegin(testing::_)).Times(1).WillOnce(testing::Return(false));
EXPECT_CALL(handlerMock, OnCreateSessionEnd()).Times(0);
EXPECT_CALL(handlerMock, OnDestroySessionBegin()).Times(1).WillOnce(testing::Return(true));
EXPECT_CALL(*(m_serverManager->m_gameLiftServerSDKWrapperMockPtr), ProcessEnding()).Times(1);
AZ_TEST_START_TRACE_SUPPRESSION;
@@ -329,6 +368,7 @@ R"({
m_serverManager->NotifyGameLiftProcessReady();
SessionNotificationsHandlerMock handlerMock;
EXPECT_CALL(handlerMock, OnCreateSessionBegin(testing::_)).Times(1).WillOnce(testing::Return(true));
EXPECT_CALL(handlerMock, OnCreateSessionEnd()).Times(1);
EXPECT_CALL(handlerMock, OnDestroySessionBegin()).Times(1).WillOnce(testing::Return(true));
EXPECT_CALL(*(m_serverManager->m_gameLiftServerSDKWrapperMockPtr), ActivateGameSession())
.Times(1)
@@ -349,6 +389,7 @@ R"({
m_serverManager->NotifyGameLiftProcessReady();
SessionNotificationsHandlerMock handlerMock;
EXPECT_CALL(handlerMock, OnCreateSessionBegin(testing::_)).Times(1).WillOnce(testing::Return(true));
EXPECT_CALL(handlerMock, OnCreateSessionEnd()).Times(0);
EXPECT_CALL(handlerMock, OnDestroySessionBegin()).Times(1).WillOnce(testing::Return(true));
EXPECT_CALL(*(m_serverManager->m_gameLiftServerSDKWrapperMockPtr), ActivateGameSession())
.Times(1)
@@ -359,12 +400,13 @@ R"({
AZ_TEST_STOP_TRACE_SUPPRESSION(1);
}
TEST_F(GameLiftServerManagerTest, OnUpdateGameSession_TriggerWithUnknownReason_OnUpdateSessionBeginGetCalledOnce)
TEST_F(GameLiftServerManagerTest, OnUpdateGameSession_TriggerWithUnknownReason_OnUpdateSessionGetCalledOnce)
{
m_serverManager->InitializeGameLiftServerSDK();
m_serverManager->NotifyGameLiftProcessReady();
SessionNotificationsHandlerMock handlerMock;
EXPECT_CALL(handlerMock, OnUpdateSessionBegin(testing::_, testing::_)).Times(1);
EXPECT_CALL(handlerMock, OnUpdateSessionEnd()).Times(1);
m_serverManager->m_gameLiftServerSDKWrapperMockPtr->m_onUpdateGameSessionFunc(
Aws::GameLift::Server::Model::UpdateGameSession(
@@ -373,12 +415,13 @@ R"({
"testticket"));
}
TEST_F(GameLiftServerManagerTest, OnUpdateGameSession_TriggerWithEmptyMatchmakingData_OnUpdateSessionBeginGetCalledOnce)
TEST_F(GameLiftServerManagerTest, OnUpdateGameSession_TriggerWithEmptyMatchmakingData_OnUpdateSessionGetCalledOnce)
{
m_serverManager->InitializeGameLiftServerSDK();
m_serverManager->NotifyGameLiftProcessReady();
SessionNotificationsHandlerMock handlerMock;
EXPECT_CALL(handlerMock, OnUpdateSessionBegin(testing::_, testing::_)).Times(1);
EXPECT_CALL(handlerMock, OnUpdateSessionEnd()).Times(1);
m_serverManager->m_gameLiftServerSDKWrapperMockPtr->m_onUpdateGameSessionFunc(
Aws::GameLift::Server::Model::UpdateGameSession(
@@ -387,12 +430,13 @@ R"({
"testticket"));
}
TEST_F(GameLiftServerManagerTest, OnUpdateGameSession_TriggerWithValidMatchmakingData_OnUpdateSessionBeginGetCalledOnce)
TEST_F(GameLiftServerManagerTest, OnUpdateGameSession_TriggerWithValidMatchmakingData_OnUpdateSessionGetCalledOnce)
{
m_serverManager->InitializeGameLiftServerSDK();
m_serverManager->NotifyGameLiftProcessReady();
SessionNotificationsHandlerMock handlerMock;
EXPECT_CALL(handlerMock, OnUpdateSessionBegin(testing::_, testing::_)).Times(1);
EXPECT_CALL(handlerMock, OnUpdateSessionEnd()).Times(1);
Aws::GameLift::Server::Model::GameSession gameSession;
gameSession.SetMatchmakerData(TEST_SERVER_MATCHMAKING_DATA);
@@ -401,12 +445,13 @@ R"({
gameSession, Aws::GameLift::Server::Model::UpdateReason::MATCHMAKING_DATA_UPDATED, "testticket"));
}
TEST_F(GameLiftServerManagerTest, OnUpdateGameSession_TriggerWithInvalidMatchmakingData_OnUpdateSessionBeginGetCalledOnce)
TEST_F(GameLiftServerManagerTest, OnUpdateGameSession_TriggerWithInvalidMatchmakingData_OnUpdateSessionGetCalledOnce)
{
m_serverManager->InitializeGameLiftServerSDK();
m_serverManager->NotifyGameLiftProcessReady();
SessionNotificationsHandlerMock handlerMock;
EXPECT_CALL(handlerMock, OnUpdateSessionBegin(testing::_, testing::_)).Times(1);
EXPECT_CALL(handlerMock, OnUpdateSessionEnd()).Times(1);
Aws::GameLift::Server::Model::GameSession gameSession;
gameSession.SetMatchmakerData("{invalid}");
@@ -54,10 +54,14 @@ namespace AZ
{
AZStd::shared_ptr<AZStd::vector<uint8_t>> buffer = readbackResult.m_dataBuffer;
RHI::Format format = readbackResult.m_imageDescriptor.m_format;
// convert bgra to rgba by swapping channels
const int numChannels = AZ::RHI::GetFormatComponentCount(readbackResult.m_imageDescriptor.m_format);
if (readbackResult.m_imageDescriptor.m_format == RHI::Format::B8G8R8A8_UNORM)
if (format == RHI::Format::B8G8R8A8_UNORM)
{
format = RHI::Format::R8G8B8A8_UNORM;
buffer = AZStd::make_shared<AZStd::vector<uint8_t>>(readbackResult.m_dataBuffer->size());
AZStd::copy(readbackResult.m_dataBuffer->begin(), readbackResult.m_dataBuffer->end(), buffer->begin());
@@ -89,7 +93,7 @@ namespace AZ
jobCompletion.StartAndWaitForCompletion();
}
Utils::PngFile image = Utils::PngFile::Create(readbackResult.m_imageDescriptor.m_size, readbackResult.m_imageDescriptor.m_format, *buffer);
Utils::PngFile image = Utils::PngFile::Create(readbackResult.m_imageDescriptor.m_size, format, *buffer);
Utils::PngFile::SaveSettings saveSettings;
saveSettings.m_compressionLevel = r_pngCompressionLevel;
@@ -507,7 +507,8 @@ namespace AZ
}
}
//Check if buffer view data changed from previous frame.
// Check if buffer view data changed from previous frame.
// Look into making 'm_meshBuffers != meshBuffers' faster by possibly building a crc and doing a crc check.
if (m_meshBuffers.size() != meshBuffers.size() || m_meshBuffers != meshBuffers)
{
m_meshBuffers = meshBuffers;
+1 -3
View File
@@ -109,13 +109,11 @@ namespace AZ
{
if (attachment->GetFirstScopeAttachment() == nullptr)
{
//We allow the rendering to continue even if an attachment is not used.
AZ_Error(
"FrameGraph", false,
"Invalid State: attachment '%s' was added but never used!",
attachment->GetId().GetCStr());
Clear();
return ResultCode::InvalidOperation;
}
}
}
@@ -111,13 +111,19 @@ namespace AZ
{
AZStd::lock_guard<AZStd::shared_mutex> lock(m_groupsToCompileMutex);
AZ_Assert(!shaderResourceGroup.IsQueuedForCompile(), "Attempting to compile an SRG that's already been queued for compile. Only compile an SRG once per frame.");
bool isQueuedForCompile = shaderResourceGroup.IsQueuedForCompile();
AZ_Warning(
"ShaderResourceGroupPool", !isQueuedForCompile,
"Attempting to compile an SRG that's already been queued for compile. Only compile an SRG once per frame.");
CalculateGroupDataDiff(shaderResourceGroup, groupData);
if (!isQueuedForCompile)
{
CalculateGroupDataDiff(shaderResourceGroup, groupData);
shaderResourceGroup.SetData(groupData);
shaderResourceGroup.SetData(groupData);
QueueForCompileNoLock(shaderResourceGroup);
QueueForCompileNoLock(shaderResourceGroup);
}
}
void ShaderResourceGroupPool::QueueForCompile(ShaderResourceGroup& group)
@@ -55,7 +55,7 @@ namespace AZ
RHI::Ptr<BufferMemory> bufferMemory;
const VkMemoryPropertyFlags flags = ConvertHeapMemoryLevel(m_descriptor.m_heapMemoryLevel) | m_descriptor.m_additionalMemoryPropertyFlags;
RHI::Ptr<Memory> memory = GetDevice().AllocateMemory(memoryRequirements.size, memoryRequirements.memoryTypeBits, flags);
RHI::Ptr<Memory> memory = GetDevice().AllocateMemory(memoryRequirements.size, memoryRequirements.memoryTypeBits, flags, m_descriptor.m_bindFlags);
if (memory)
{
@@ -822,7 +822,7 @@ namespace AZ
{
RHI::ConstPtr<ShaderResourceGroup> shaderResourceGroup;
const auto& srgBitset = pipelineLayout.GetAZSLBindingSlotsOfIndex(index);
AZStd::vector<const ShaderResourceGroup*> shaderResourceGroupList;
AZStd::fixed_vector<const ShaderResourceGroup*, RHI::Limits::Pipeline::ShaderResourceGroupCountMax> shaderResourceGroupList;
// Collect all the SRGs that are part of this descriptor set. They could be more than
// 1, so we would need to merge their values before committing the descriptor set.
for (uint32_t bindingSlot = 0; bindingSlot < srgBitset.size(); ++bindingSlot)
@@ -696,8 +696,7 @@ namespace AZ
usageFlags |=
VK_BUFFER_USAGE_INDEX_BUFFER_BIT |
VK_BUFFER_USAGE_VERTEX_BUFFER_BIT |
VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR |
VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT;
VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR;
}
if (RHI::CheckBitsAny(bindFlags, BindFlags::Constant))
@@ -742,12 +741,24 @@ namespace AZ
if (RHI::CheckBitsAny(bindFlags, BindFlags::RayTracingShaderTable))
{
usageFlags |= VK_BUFFER_USAGE_SHADER_BINDING_TABLE_BIT_KHR | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT;
usageFlags |= VK_BUFFER_USAGE_SHADER_BINDING_TABLE_BIT_KHR;
}
if (ShouldApplyDeviceAddressBit(bindFlags))
{
usageFlags |= VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT;
}
return usageFlags;
}
bool ShouldApplyDeviceAddressBit(RHI::BufferBindFlags bindFlags)
{
return RHI::CheckBitsAny(
bindFlags,
RHI::BufferBindFlags::InputAssembly | RHI::BufferBindFlags::DynamicInputAssembly | RHI::BufferBindFlags::RayTracingShaderTable);
}
VkPipelineStageFlags GetSupportedPipelineStages(RHI::PipelineStateType type)
{
// These stages don't need any special queue to be supported.
@@ -82,5 +82,6 @@ namespace AZ
VkImageUsageFlags ImageUsageFlagsOfFormatFeatureFlags(VkFormatFeatureFlags formatFeatureFlags);
VkAccessFlags GetSupportedAccessFlags(VkPipelineStageFlags pipelineStageFlags);
bool ShouldApplyDeviceAddressBit(RHI::BufferBindFlags bindFlags);
}
}
@@ -185,6 +185,9 @@ namespace AZ
VkPhysicalDeviceShaderFloat16Int8FeaturesKHR float16Int8 = {};
VkPhysicalDeviceSeparateDepthStencilLayoutsFeaturesKHR separateDepthStencil = {};
VkDeviceCreateInfo deviceInfo = {};
deviceInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
// If we are running Vulkan >= 1.2, then we must use VkPhysicalDeviceVulkan12Features instead
// of VkPhysicalDeviceShaderFloat16Int8FeaturesKHR or VkPhysicalDeviceSeparateDepthStencilLayoutsFeaturesKHR.
if (majorVersion >= 1 && minorVersion >= 2)
@@ -194,7 +197,14 @@ namespace AZ
vulkan12Features.shaderFloat16 = physicalDevice.GetPhysicalDeviceVulkan12Features().shaderFloat16;
vulkan12Features.shaderInt8 = physicalDevice.GetPhysicalDeviceVulkan12Features().shaderInt8;
vulkan12Features.separateDepthStencilLayouts = physicalDevice.GetPhysicalDeviceVulkan12Features().separateDepthStencilLayouts;
vulkan12Features.descriptorBindingPartiallyBound = physicalDevice.GetPhysicalDeviceVulkan12Features().separateDepthStencilLayouts;
vulkan12Features.descriptorIndexing = physicalDevice.GetPhysicalDeviceVulkan12Features().separateDepthStencilLayouts;
vulkan12Features.descriptorBindingVariableDescriptorCount = physicalDevice.GetPhysicalDeviceVulkan12Features().separateDepthStencilLayouts;
vulkan12Features.bufferDeviceAddress = physicalDevice.GetPhysicalDeviceVulkan12Features().bufferDeviceAddress;
vulkan12Features.bufferDeviceAddressMultiDevice = physicalDevice.GetPhysicalDeviceVulkan12Features().bufferDeviceAddressMultiDevice;
vulkan12Features.runtimeDescriptorArray = physicalDevice.GetPhysicalDeviceVulkan12Features().runtimeDescriptorArray;
robustness2.pNext = &vulkan12Features;
deviceInfo.pNext = &depthClipEnabled;
}
else
{
@@ -206,11 +216,11 @@ namespace AZ
float16Int8.pNext = &separateDepthStencil;
robustness2.pNext = &float16Int8;
deviceInfo.pNext = &descriptorIndexingFeatures;
}
VkDeviceCreateInfo deviceInfo = {};
deviceInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
deviceInfo.pNext = &descriptorIndexingFeatures;
deviceInfo.flags = 0;
deviceInfo.queueCreateInfoCount = static_cast<uint32_t>(queueCreationInfo.size());
deviceInfo.pQueueCreateInfos = queueCreationInfo.data();
@@ -740,7 +750,7 @@ namespace AZ
vkGetPhysicalDeviceQueueFamilyProperties(nativePhysicalDevice, &queueFamilyCount, m_queueFamilyProperties.data());
}
RHI::Ptr<Memory> Device::AllocateMemory(uint64_t sizeInBytes, const uint32_t memoryTypeMask, const VkMemoryPropertyFlags flags)
RHI::Ptr<Memory> Device::AllocateMemory(uint64_t sizeInBytes, const uint32_t memoryTypeMask, const VkMemoryPropertyFlags flags, const RHI::BufferBindFlags bufferBindFlags)
{
const auto& physicalDevice = static_cast<const PhysicalDevice&>(GetPhysicalDevice());
const VkPhysicalDeviceMemoryProperties& memProp = physicalDevice.GetMemoryProperties();
@@ -770,6 +780,7 @@ namespace AZ
RHI::CheckBitsAll(memoryTypesToUseMask, memoryTypeBit))
{
memoryDesc.m_memoryTypeIndex = memoryIndex;
memoryDesc.m_bufferBindFlags = bufferBindFlags;
auto result = memory->Init(*this, memoryDesc);
if (result == RHI::ResultCode::Success)
{
@@ -100,7 +100,11 @@ namespace AZ
RHI::Ptr<CommandList> AcquireCommandList(uint32_t familyQueueIndex, VkCommandBufferLevel level = VK_COMMAND_BUFFER_LEVEL_PRIMARY);
RHI::Ptr<CommandList> AcquireCommandList(RHI::HardwareQueueClass queueClass, VkCommandBufferLevel level = VK_COMMAND_BUFFER_LEVEL_PRIMARY);
RHI::Ptr<Memory> AllocateMemory(uint64_t sizeInBytes, const uint32_t memoryTypeMask, const VkMemoryPropertyFlags flags);
RHI::Ptr<Memory> AllocateMemory(
uint64_t sizeInBytes,
const uint32_t memoryTypeMask,
const VkMemoryPropertyFlags flags,
const RHI::BufferBindFlags bufferBindFlags = RHI::BufferBindFlags::None);
uint32_t GetCurrentFrameIndex() const;
@@ -59,7 +59,9 @@ namespace AZ
void FrameGraphExecuteGroupMerged::BeginInternal()
{
m_commandList = AcquireCommandList(VK_COMMAND_BUFFER_LEVEL_PRIMARY);
m_commandList->BeginCommandBuffer();
m_workRequest.m_commandList = m_commandList;
}
void FrameGraphExecuteGroupMerged::EndInternal()
@@ -41,9 +41,7 @@ namespace AZ
RETURN_RESULT_IF_UNSUCCESSFUL(result);
}
// Set the command list and renderpass contexts.
m_primaryCommandList = device.AcquireCommandList(m_hardwareQueueClass);
group->SetPrimaryCommandList(*m_primaryCommandList);
// Set the renderpass contexts.
group->SetRenderPasscontexts(m_renderPassContexts);
return RHI::ResultCode::Success;
@@ -54,7 +52,8 @@ namespace AZ
AZ_Assert(m_executeGroups.size() == 1, "Too many execute groups when initializing context");
FrameGraphExecuteGroupBase* group = static_cast<FrameGraphExecuteGroupBase*>(m_executeGroups.back());
AddWorkRequest(group->GetWorkRequest());
m_workRequest.m_commandList = m_primaryCommandList;
//Merged handler will only have one commandlist.
m_workRequest.m_commandList = group->GetCommandLists()[0];
}
}
}
@@ -31,7 +31,12 @@ namespace AZ
{
return static_cast<Device&>(Base::GetDevice());
}
FrameGraphExecuter::FrameGraphExecuter()
{
SetJobPolicy(RHI::JobPolicy::Parallel);
}
RHI::ResultCode FrameGraphExecuter::InitInternal(const RHI::FrameGraphExecuterDescriptor& descriptor)
{
const RHI::ConstPtr<RHI::PlatformLimitsDescriptor> rhiPlatformLimitsDescriptor = descriptor.m_platformLimitsDescriptor;
@@ -35,6 +35,8 @@ namespace AZ
Device& GetDevice() const;
private:
FrameGraphExecuter();
//////////////////////////////////////////////////////////////////////////
// RHI::FrameGraphExecuter
RHI::ResultCode InitInternal(const RHI::FrameGraphExecuterDescriptor& descriptor) override;
@@ -7,6 +7,7 @@
*/
#include <AzCore/std/parallel/lock.h>
#include <Atom/RHI.Reflect/Bits.h>
#include <Atom/RHI.Reflect/BufferDescriptor.h>
#include <RHI/Memory.h>
#include <RHI/Conversion.h>
#include <RHI/Device.h>
@@ -31,6 +32,15 @@ namespace AZ
allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
allocInfo.allocationSize = descriptor.m_sizeInBytes;
allocInfo.memoryTypeIndex = descriptor.m_memoryTypeIndex;
VkMemoryAllocateFlagsInfo memAllocInfo{};
if (ShouldApplyDeviceAddressBit(descriptor.m_bufferBindFlags))
{
memAllocInfo.flags |= VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_BIT;
}
memAllocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_FLAGS_INFO;
allocInfo.pNext = &memAllocInfo;
VkDeviceMemory deviceMemory;
VkResult vkResult = vkAllocateMemory(device.GetNativeDevice(), &allocInfo, nullptr, &deviceMemory);
AZ_Error(
@@ -37,6 +37,7 @@ namespace AZ
{
VkDeviceSize m_sizeInBytes = 0;
uint32_t m_memoryTypeIndex = 0;
RHI::BufferBindFlags m_bufferBindFlags = RHI::BufferBindFlags::None;
};
~Memory() = default;
@@ -38,7 +38,7 @@ namespace AZ
static RHI::Ptr<MergedShaderResourceGroupPool> Create();
using ShaderResourceGroupList = AZStd::vector<const ShaderResourceGroup*>;
using ShaderResourceGroupList = AZStd::fixed_vector<const ShaderResourceGroup*, RHI::Limits::Pipeline::ShaderResourceGroupCountMax>;
//! Finds or create a new instance of a MergedShaderResourceGroup.
//! @param shaderResourceGroupList The list of ShaderResourceGroups that are being merged.
MergedShaderResourceGroup* FindOrCreate(const ShaderResourceGroupList& shaderResourceGroupList);
@@ -115,77 +115,65 @@ namespace AZ
const RHI::ShaderResourceGroupLayout* layout = groupData.GetLayout();
if (groupData.IsResourceTypeEnabledForCompilation(static_cast<uint32_t>(RHI::ShaderResourceGroupData::ResourceTypeMask::BufferViewMask)))
for (uint32_t groupIndex = 0; groupIndex < static_cast<uint32_t>(layout->GetShaderInputListForBuffers().size()); ++groupIndex)
{
for (uint32_t groupIndex = 0; groupIndex < static_cast<uint32_t>(layout->GetShaderInputListForBuffers().size()); ++groupIndex)
{
const RHI::ShaderInputBufferIndex index(groupIndex);
auto bufViews = groupData.GetBufferViewArray(index);
uint32_t layoutIndex = m_descriptorSetLayout->GetLayoutIndexFromGroupIndex(groupIndex, DescriptorSetLayout::ResourceType::BufferView);
descriptorSet.UpdateBufferViews(layoutIndex, bufViews);
}
const RHI::ShaderInputBufferIndex index(groupIndex);
auto bufViews = groupData.GetBufferViewArray(index);
uint32_t layoutIndex = m_descriptorSetLayout->GetLayoutIndexFromGroupIndex(groupIndex, DescriptorSetLayout::ResourceType::BufferView);
descriptorSet.UpdateBufferViews(layoutIndex, bufViews);
}
if (groupData.IsResourceTypeEnabledForCompilation(static_cast<uint32_t>(RHI::ShaderResourceGroupData::ResourceTypeMask::ImageViewMask)))
auto const& shaderImageList = layout->GetShaderInputListForImages();
for (uint32_t groupIndex = 0; groupIndex < static_cast<uint32_t>(shaderImageList.size()); ++groupIndex)
{
auto const& shaderImageList = layout->GetShaderInputListForImages();
for (uint32_t groupIndex = 0; groupIndex < static_cast<uint32_t>(layout->GetShaderInputListForImages().size()); ++groupIndex)
{
const RHI::ShaderInputImageIndex index(groupIndex);
auto imgViews = groupData.GetImageViewArray(index);
uint32_t layoutIndex = m_descriptorSetLayout->GetLayoutIndexFromGroupIndex(groupIndex, DescriptorSetLayout::ResourceType::ImageView);
descriptorSet.UpdateImageViews(layoutIndex, imgViews, shaderImageList[groupIndex].m_type);
}
const RHI::ShaderInputImageIndex index(groupIndex);
auto imgViews = groupData.GetImageViewArray(index);
uint32_t layoutIndex =
m_descriptorSetLayout->GetLayoutIndexFromGroupIndex(groupIndex, DescriptorSetLayout::ResourceType::ImageView);
descriptorSet.UpdateImageViews(layoutIndex, imgViews, shaderImageList[groupIndex].m_type);
}
if (groupData.IsResourceTypeEnabledForCompilation(static_cast<uint32_t>(RHI::ShaderResourceGroupData::ResourceTypeMask::BufferViewUnboundedArrayMask)))
for (uint32_t groupIndex = 0; groupIndex < static_cast<uint32_t>(layout->GetShaderInputListForBufferUnboundedArrays().size()); ++groupIndex)
{
for (uint32_t groupIndex = 0; groupIndex < static_cast<uint32_t>(layout->GetShaderInputListForBufferUnboundedArrays().size()); ++groupIndex)
const RHI::ShaderInputBufferUnboundedArrayIndex index(groupIndex);
auto bufViews = groupData.GetBufferViewUnboundedArray(index);
if (bufViews.empty())
{
const RHI::ShaderInputBufferUnboundedArrayIndex index(groupIndex);
auto bufViews = groupData.GetBufferViewUnboundedArray(index);
if (bufViews.empty())
{
// skip empty unbounded arrays
continue;
}
uint32_t layoutIndex = m_descriptorSetLayout->GetLayoutIndexFromGroupIndex(groupIndex, DescriptorSetLayout::ResourceType::BufferViewUnboundedArray);
descriptorSet.UpdateBufferViews(layoutIndex, bufViews);
// skip empty unbounded arrays
continue;
}
uint32_t layoutIndex = m_descriptorSetLayout->GetLayoutIndexFromGroupIndex(groupIndex, DescriptorSetLayout::ResourceType::BufferViewUnboundedArray);
descriptorSet.UpdateBufferViews(layoutIndex, bufViews);
}
if (groupData.IsResourceTypeEnabledForCompilation(static_cast<uint32_t>(RHI::ShaderResourceGroupData::ResourceTypeMask::ImageViewUnboundedArrayMask)))
auto const& shaderImageUnboundeArrayList = layout->GetShaderInputListForImageUnboundedArrays();
for (uint32_t groupIndex = 0; groupIndex < static_cast<uint32_t>(shaderImageUnboundeArrayList.size()); ++groupIndex)
{
auto const& shaderImageUnboundeArrayList = layout->GetShaderInputListForImageUnboundedArrays();
for (uint32_t groupIndex = 0; groupIndex < static_cast<uint32_t>(layout->GetShaderInputListForImageUnboundedArrays().size()); ++groupIndex)
const RHI::ShaderInputImageUnboundedArrayIndex index(groupIndex);
auto imgViews = groupData.GetImageViewUnboundedArray(index);
if (imgViews.empty())
{
const RHI::ShaderInputImageUnboundedArrayIndex index(groupIndex);
auto imgViews = groupData.GetImageViewUnboundedArray(index);
if (imgViews.empty())
{
// skip empty unbounded arrays
continue;
}
uint32_t layoutIndex = m_descriptorSetLayout->GetLayoutIndexFromGroupIndex(groupIndex, DescriptorSetLayout::ResourceType::ImageViewUnboundedArray);
descriptorSet.UpdateImageViews(layoutIndex, imgViews, shaderImageUnboundeArrayList[groupIndex].m_type);
// skip empty unbounded arrays
continue;
}
uint32_t layoutIndex = m_descriptorSetLayout->GetLayoutIndexFromGroupIndex(groupIndex, DescriptorSetLayout::ResourceType::ImageViewUnboundedArray);
descriptorSet.UpdateImageViews(layoutIndex, imgViews, shaderImageUnboundeArrayList[groupIndex].m_type);
}
if (groupData.IsResourceTypeEnabledForCompilation(static_cast<uint32_t>(RHI::ShaderResourceGroupData::ResourceTypeMask::SamplerMask)))
for (uint32_t groupIndex = 0; groupIndex < static_cast<uint32_t>(layout->GetShaderInputListForSamplers().size()); ++groupIndex)
{
for (uint32_t groupIndex = 0; groupIndex < static_cast<uint32_t>(layout->GetShaderInputListForSamplers().size()); ++groupIndex)
{
const RHI::ShaderInputSamplerIndex index(groupIndex);
auto samplerArray = groupData.GetSamplerArray(index);
uint32_t layoutIndex = m_descriptorSetLayout->GetLayoutIndexFromGroupIndex(groupIndex, DescriptorSetLayout::ResourceType::Sampler);
descriptorSet.UpdateSamplers(layoutIndex, samplerArray);
}
const RHI::ShaderInputSamplerIndex index(groupIndex);
auto samplerArray = groupData.GetSamplerArray(index);
uint32_t layoutIndex =
m_descriptorSetLayout->GetLayoutIndexFromGroupIndex(groupIndex, DescriptorSetLayout::ResourceType::Sampler);
descriptorSet.UpdateSamplers(layoutIndex, samplerArray);
}
auto constantData = groupData.GetConstantData();
if (!constantData.empty() && groupData.IsResourceTypeEnabledForCompilation(static_cast<uint32_t>(RHI::ShaderResourceGroupData::ResourceTypeMask::ConstantDataMask)))
if (!constantData.empty())
{
descriptorSet.UpdateConstantData(constantData);
}
@@ -9,6 +9,7 @@
#pragma once
#include <AzCore/Memory/Memory.h>
#include <AzCore/Memory/SystemAllocator.h>
namespace AtomToolsFramework
{
@@ -0,0 +1,31 @@
/*
* 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 <AtomToolsFramework/PreviewRenderer/PreviewContent.h>
#include <AzCore/Memory/Memory.h>
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/std/functional.h>
#include <AzCore/std/smart_ptr/shared_ptr.h>
class QPixmap;
namespace AtomToolsFramework
{
//! PreviewRendererCaptureRequest describes the size, content, and behavior of a scene to be rendered to an image
struct PreviewRendererCaptureRequest final
{
AZ_CLASS_ALLOCATOR(PreviewRendererCaptureRequest, AZ::SystemAllocator, 0);
int m_size = 512;
AZStd::shared_ptr<PreviewContent> m_content;
AZStd::function<void()> m_captureFailedCallback;
AZStd::function<void(const QPixmap&)> m_captureCompleteCallback;
};
} // namespace AtomToolsFramework
@@ -0,0 +1,29 @@
/*
* 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 <Atom/RPI.Public/Base.h>
namespace AtomToolsFramework
{
struct PreviewRendererCaptureRequest;
//! Public interface for PreviewRenderer so that it can be used in other modules
class PreviewRendererInterface
{
public:
AZ_RTTI(PreviewRendererInterface, "{C5B5E3D0-0055-4C08-9B98-FDBBB5F05BED}");
virtual ~PreviewRendererInterface() = default;
virtual void AddCaptureRequest(const PreviewRendererCaptureRequest& captureRequest) = 0;
virtual AZ::RPI::ScenePtr GetScene() const = 0;
virtual AZ::RPI::ViewPtr GetView() const = 0;
virtual AZ::Uuid GetEntityContextId() const = 0;
};
} // namespace AtomToolsFramework
@@ -0,0 +1,24 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <AzCore/EBus/EBus.h>
namespace AtomToolsFramework
{
//! PreviewRendererSystemRequests provides an interface for PreviewRendererSystemComponent
class PreviewRendererSystemRequests : public AZ::EBusTraits
{
public:
// Only a single handler is allowed
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
};
using PreviewRendererSystemRequestBus = AZ::EBus<PreviewRendererSystemRequests>;
} // namespace AtomToolsFramework
@@ -123,7 +123,6 @@ namespace AtomToolsFramework
void OnTick(float deltaTime, AZ::ScriptTimePoint time) override;
// QWidget overrides ...
void resizeEvent(QResizeEvent *event) override;
bool event(QEvent* event) override;
void enterEvent(QEvent* event) override;
void leaveEvent(QEvent* event) override;
@@ -10,6 +10,7 @@
#include <AtomToolsFrameworkSystemComponent.h>
#include <Document/AtomToolsDocumentSystemComponent.h>
#include <Window/AtomToolsMainWindowSystemComponent.h>
#include <PreviewRenderer/PreviewRendererSystemComponent.h>
namespace AtomToolsFramework
{
@@ -19,6 +20,7 @@ namespace AtomToolsFramework
AtomToolsFrameworkSystemComponent::CreateDescriptor(),
AtomToolsDocumentSystemComponent::CreateDescriptor(),
AtomToolsMainWindowSystemComponent::CreateDescriptor(),
PreviewRendererSystemComponent::CreateDescriptor(),
});
}
@@ -28,6 +30,7 @@ namespace AtomToolsFramework
azrtti_typeid<AtomToolsFrameworkSystemComponent>(),
azrtti_typeid<AtomToolsDocumentSystemComponent>(),
azrtti_typeid<AtomToolsMainWindowSystemComponent>(),
azrtti_typeid<PreviewRendererSystemComponent>(),
};
}
}
@@ -15,11 +15,12 @@
#include <Atom/RPI.Public/View.h>
#include <Atom/RPI.Reflect/System/RenderPipelineDescriptor.h>
#include <Atom/RPI.Reflect/System/SceneDescriptor.h>
#include <AtomToolsFramework/PreviewRenderer/PreviewRenderer.h>
#include <AzCore/Interface/Interface.h>
#include <AzCore/Math/MatrixUtils.h>
#include <AzCore/Math/Transform.h>
#include <AzFramework/Scene/Scene.h>
#include <AzFramework/Scene/SceneSystemInterface.h>
#include <PreviewRenderer/PreviewRenderer.h>
#include <PreviewRenderer/PreviewRendererCaptureState.h>
#include <PreviewRenderer/PreviewRendererIdleState.h>
#include <PreviewRenderer/PreviewRendererLoadState.h>
@@ -80,6 +81,8 @@ namespace AtomToolsFramework
m_renderPipeline->SetDefaultView(m_view);
m_state.reset(new PreviewRendererIdleState(this));
AZ::Interface<PreviewRendererInterface>::Register(this);
}
PreviewRenderer::~PreviewRenderer()
@@ -96,9 +99,11 @@ namespace AtomToolsFramework
AZ::RPI::RPISystemInterface::Get()->UnregisterScene(m_scene);
m_frameworkScene->UnsetSubsystem(m_scene);
m_frameworkScene->UnsetSubsystem(m_entityContext.get());
AZ::Interface<PreviewRendererInterface>::Unregister(this);
}
void PreviewRenderer::AddCaptureRequest(const CaptureRequest& captureRequest)
void PreviewRenderer::AddCaptureRequest(const PreviewRendererCaptureRequest& captureRequest)
{
m_captureRequestQueue.push(captureRequest);
}
@@ -11,41 +11,31 @@
#include <Atom/RPI.Public/Base.h>
#include <Atom/RPI.Public/Pass/AttachmentReadback.h>
#include <AtomToolsFramework/PreviewRenderer/PreviewContent.h>
#include <AtomToolsFramework/PreviewRenderer/PreviewRendererState.h>
#include <AtomToolsFramework/PreviewRenderer/PreviewRendererCaptureRequest.h>
#include <AtomToolsFramework/PreviewRenderer/PreviewRendererInterface.h>
#include <AtomToolsFramework/PreviewRenderer/PreviewerFeatureProcessorProviderBus.h>
#include <AzFramework/Entity/GameEntityContextComponent.h>
namespace AzFramework
{
class Scene;
}
class QPixmap;
#include <PreviewRenderer/PreviewRendererState.h>
namespace AtomToolsFramework
{
//! Processes requests for setting up content that gets rendered to a texture and captured to an image
class PreviewRenderer final : public PreviewerFeatureProcessorProviderBus::Handler
class PreviewRenderer final
: public PreviewRendererInterface
, public PreviewerFeatureProcessorProviderBus::Handler
{
public:
AZ_CLASS_ALLOCATOR(PreviewRenderer, AZ::SystemAllocator, 0);
AZ_RTTI(PreviewRenderer, "{60FCB7AB-2A94-417A-8C5E-5B588D17F5D1}", PreviewRendererInterface);
PreviewRenderer(const AZStd::string& sceneName, const AZStd::string& pipelineName);
~PreviewRenderer();
~PreviewRenderer() override;
struct CaptureRequest final
{
int m_size = 512;
AZStd::shared_ptr<PreviewContent> m_content;
AZStd::function<void()> m_captureFailedCallback;
AZStd::function<void(const QPixmap&)> m_captureCompleteCallback;
};
void AddCaptureRequest(const PreviewRendererCaptureRequest& captureRequest) override;
void AddCaptureRequest(const CaptureRequest& captureRequest);
AZ::RPI::ScenePtr GetScene() const;
AZ::RPI::ViewPtr GetView() const;
AZ::Uuid GetEntityContextId() const;
AZ::RPI::ScenePtr GetScene() const override;
AZ::RPI::ViewPtr GetView() const override;
AZ::Uuid GetEntityContextId() const override;
void ProcessCaptureRequests();
void CancelCaptureRequest();
@@ -77,8 +67,8 @@ namespace AtomToolsFramework
AZStd::unique_ptr<AzFramework::EntityContext> m_entityContext;
//! Incoming requests are appended to this queue and processed one at a time in OnTick function.
AZStd::queue<CaptureRequest> m_captureRequestQueue;
CaptureRequest m_currentCaptureRequest;
AZStd::queue<PreviewRendererCaptureRequest> m_captureRequestQueue;
PreviewRendererCaptureRequest m_currentCaptureRequest;
AZStd::unique_ptr<PreviewRendererState> m_state;
};
@@ -6,7 +6,7 @@
*
*/
#include <AtomToolsFramework/PreviewRenderer/PreviewRenderer.h>
#include <PreviewRenderer/PreviewRenderer.h>
#include <PreviewRenderer/PreviewRendererCaptureState.h>
namespace AtomToolsFramework
@@ -9,8 +9,8 @@
#pragma once
#include <Atom/Feature/Utils/FrameCaptureBus.h>
#include <AtomToolsFramework/PreviewRenderer/PreviewRendererState.h>
#include <AzCore/Component/TickBus.h>
#include <PreviewRenderer/PreviewRendererState.h>
namespace AtomToolsFramework
{

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