Merge branch 'development' of https://github.com/o3de/o3de into cgalvan/CreatedCustomToolTemplate
This commit is contained in:
@@ -148,7 +148,7 @@ class TestAllComponentsIndepthTests(object):
|
||||
golden_image_path = os.path.join(golden_images_directory(), golden_image)
|
||||
golden_images.append(golden_image_path)
|
||||
|
||||
expected_lines = ["Light component tests completed."]
|
||||
expected_lines = ["spot_light Controller|Configuration|Shadows|Shadowmap size: SUCCESS"]
|
||||
unexpected_lines = [
|
||||
"Trace::Assert",
|
||||
"Trace::Error",
|
||||
|
||||
@@ -67,5 +67,9 @@ class TestAutomation(EditorTestSuite):
|
||||
class AtomEditorComponents_PostFXLayerAdded(EditorSharedTest):
|
||||
from Atom.tests import hydra_AtomEditorComponents_PostFXLayerAdded as test_module
|
||||
|
||||
@pytest.mark.test_case_id("C36525665")
|
||||
class AtomEditorComponents_PostFXShapeWeightModifierAdded(EditorSharedTest):
|
||||
from Atom.tests import hydra_AtomEditorComponents_PostFxShapeWeightModifierAdded as test_module
|
||||
|
||||
class ShaderAssetBuilder_RecompilesShaderAsChainOfDependenciesChanges(EditorSharedTest):
|
||||
from Atom.tests import hydra_ShaderAssetBuilder_RecompilesShaderAsChainOfDependenciesChanges as test_module
|
||||
|
||||
+207
@@ -0,0 +1,207 @@
|
||||
"""
|
||||
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
|
||||
"""
|
||||
|
||||
class Tests:
|
||||
creation_undo = (
|
||||
"UNDO Entity creation success",
|
||||
"UNDO Entity creation failed")
|
||||
creation_redo = (
|
||||
"REDO Entity creation success",
|
||||
"REDO Entity creation failed")
|
||||
postfx_shape_weight_creation = (
|
||||
"PostFx Shape Weight Modifier Entity successfully created",
|
||||
"PostFx Shape Weight Modifier Entity failed to be created")
|
||||
postfx_shape_weight_component = (
|
||||
"Entity has a PostFx Shape Weight Modifier component",
|
||||
"Entity failed to find PostFx Shape Weight Modifier component")
|
||||
postfx_shape_weight_disabled = (
|
||||
"PostFx Shape Weight Modifier component disabled",
|
||||
"PostFx Shape Weight Modifier component was not disabled.")
|
||||
postfx_layer_component = (
|
||||
"Entity has a PostFX Layer component",
|
||||
"Entity did not have an PostFX Layer component")
|
||||
tube_shape_component = (
|
||||
"Entity has a Tube Shape component",
|
||||
"Entity did not have a Tube Shape component")
|
||||
postfx_shape_weight_enabled = (
|
||||
"PostFx Shape Weight Modifier component enabled",
|
||||
"PostFx Shape Weight Modifier component was not enabled.")
|
||||
enter_game_mode = (
|
||||
"Entered game mode",
|
||||
"Failed to enter game mode")
|
||||
exit_game_mode = (
|
||||
"Exited game mode",
|
||||
"Couldn't exit game mode")
|
||||
is_visible = (
|
||||
"Entity is visible",
|
||||
"Entity was not visible")
|
||||
is_hidden = (
|
||||
"Entity is hidden",
|
||||
"Entity was not hidden")
|
||||
entity_deleted = (
|
||||
"Entity deleted",
|
||||
"Entity was not deleted")
|
||||
deletion_undo = (
|
||||
"UNDO deletion success",
|
||||
"UNDO deletion failed")
|
||||
deletion_redo = (
|
||||
"REDO deletion success",
|
||||
"REDO deletion failed")
|
||||
|
||||
|
||||
def AtomEditorComponents_postfx_shape_weight_AddedToEntity():
|
||||
"""
|
||||
Summary:
|
||||
Tests the PostFx Shape Weight Modifier component can be added to an entity and has the expected functionality.
|
||||
|
||||
Test setup:
|
||||
- Wait for Editor idle loop.
|
||||
- Open the "Base" level.
|
||||
|
||||
Expected Behavior:
|
||||
The component can be added, used in game mode, hidden/shown, deleted, and has accurate required components.
|
||||
Creation and deletion undo/redo should also work.
|
||||
|
||||
Test Steps:
|
||||
1) Create a PostFx Shape Weight Modifier entity with no components.
|
||||
2) Add a PostFx Shape Weight Modifier component to PostFx Shape Weight Modifier entity.
|
||||
3) UNDO the entity creation and component addition.
|
||||
4) REDO the entity creation and component addition.
|
||||
5) Verify PostFx Shape Weight Modifier component not enabled.
|
||||
6) Add PostFX Layer component since it is required by the PostFx Shape Weight Modifier component.
|
||||
7) Verify PostFx Shape Weight Modifier component is NOT enabled since it also requires a shape.
|
||||
8) Add a required shape looping over a list and checking if it enables PostFX Shape Weight Modifier.
|
||||
9) Undo to remove each added shape and verify PostFX Shape Weight Modifier is not enabled.
|
||||
10) Verify PostFx Shape Weight Modifier component is enabled by adding Spline and Tube Shape component.
|
||||
11) Enter/Exit game mode.
|
||||
12) Test IsHidden.
|
||||
13) Test IsVisible.
|
||||
14) Delete PostFx Shape Weight Modifier entity.
|
||||
15) UNDO deletion.
|
||||
16) REDO deletion.
|
||||
17) Look for errors.
|
||||
|
||||
:return: None
|
||||
"""
|
||||
|
||||
import azlmbr.legacy.general as general
|
||||
|
||||
from editor_python_test_tools.editor_entity_utils import EditorEntity
|
||||
from editor_python_test_tools.utils import Report, Tracer, TestHelper
|
||||
|
||||
with Tracer() as error_tracer:
|
||||
# Test setup begins.
|
||||
# Setup: Wait for Editor idle loop before executing Python hydra scripts then open "Base" level.
|
||||
TestHelper.init_idle()
|
||||
TestHelper.open_level("", "Base")
|
||||
|
||||
# Test steps begin.
|
||||
# 1. Create a PostFx Shape Weight Modifier entity with no components.
|
||||
postfx_shape_weight_name = "PostFX Shape Weight Modifier"
|
||||
postfx_shape_weight_entity = EditorEntity.create_editor_entity(postfx_shape_weight_name)
|
||||
Report.critical_result(Tests.postfx_shape_weight_creation, postfx_shape_weight_entity.exists())
|
||||
|
||||
# 2. Add a PostFx Shape Weight Modifier component to PostFx Shape Weight Modifier entity.
|
||||
postfx_shape_weight_component = postfx_shape_weight_entity.add_component(postfx_shape_weight_name)
|
||||
Report.critical_result(
|
||||
Tests.postfx_shape_weight_component,
|
||||
postfx_shape_weight_entity.has_component(postfx_shape_weight_name))
|
||||
|
||||
# 3. UNDO the entity creation and component addition.
|
||||
# -> UNDO component addition.
|
||||
general.undo()
|
||||
# -> UNDO naming entity.
|
||||
general.undo()
|
||||
# -> UNDO selecting entity.
|
||||
general.undo()
|
||||
# -> UNDO entity creation.
|
||||
general.undo()
|
||||
general.idle_wait_frames(1)
|
||||
Report.result(Tests.creation_undo, not postfx_shape_weight_entity.exists())
|
||||
|
||||
# 4. REDO the entity creation and component addition.
|
||||
# -> REDO entity creation.
|
||||
general.redo()
|
||||
# -> REDO selecting entity.
|
||||
general.redo()
|
||||
# -> REDO naming entity.
|
||||
general.redo()
|
||||
# -> REDO component addition.
|
||||
general.redo()
|
||||
general.idle_wait_frames(1)
|
||||
Report.result(Tests.creation_redo, postfx_shape_weight_entity.exists())
|
||||
|
||||
# 5. Verify PostFx Shape Weight Modifier component not enabled.
|
||||
Report.result(Tests.postfx_shape_weight_disabled, not postfx_shape_weight_component.is_enabled())
|
||||
|
||||
# 6. Add PostFX Layer component since it is required by the PostFx Shape Weight Modifier component.
|
||||
postfx_layer_name = "PostFX Layer"
|
||||
postfx_shape_weight_entity.add_component(postfx_layer_name)
|
||||
Report.result(Tests.postfx_layer_component, postfx_shape_weight_entity.has_component(postfx_layer_name))
|
||||
|
||||
# 7. Verify PostFx Shape Weight Modifier component is NOT enabled since it also requires a shape.
|
||||
Report.result(Tests.postfx_shape_weight_disabled, not postfx_shape_weight_component.is_enabled())
|
||||
|
||||
# 8. Add a required shape looping over a list and checking if it enables PostFX Shape Weight Modifier.
|
||||
for shape in ['Axis Aligned Box Shape', 'Box Shape', 'Capsule Shape', 'Compound Shape', 'Cylinder Shape',
|
||||
'Disk Shape', 'Polygon Prism Shape', 'Quad Shape', 'Sphere Shape', 'Vegetation Reference Shape']:
|
||||
postfx_shape_weight_entity.add_component(shape)
|
||||
test_shape = (
|
||||
f"Entity has a {shape} component",
|
||||
f"Entity did not have a {shape} component")
|
||||
Report.result(test_shape, postfx_shape_weight_entity.has_component(shape))
|
||||
|
||||
# Check if required shape allows PostFX Shape Weight Modifier to be enabled
|
||||
Report.result(Tests.postfx_shape_weight_enabled, postfx_shape_weight_component.is_enabled())
|
||||
|
||||
# 9. Undo to remove each added shape and verify PostFX Shape Weight Modifier is not enabled.
|
||||
general.undo()
|
||||
TestHelper.wait_for_condition(lambda: not postfx_shape_weight_entity.has_component(shape), 1.0)
|
||||
Report.result(Tests.postfx_shape_weight_disabled, not postfx_shape_weight_component.is_enabled())
|
||||
|
||||
# 10. Verify PostFx Shape Weight Modifier component is enabled by adding Spline and Tube Shape component.
|
||||
postfx_shape_weight_entity.add_components(['Spline', 'Tube Shape'])
|
||||
Report.result(Tests.tube_shape_component, postfx_shape_weight_entity.has_component('Tube Shape'))
|
||||
Report.result(Tests.postfx_shape_weight_enabled, postfx_shape_weight_component.is_enabled())
|
||||
|
||||
# 11. Enter/Exit game mode.
|
||||
TestHelper.enter_game_mode(Tests.enter_game_mode)
|
||||
general.idle_wait_frames(1)
|
||||
TestHelper.exit_game_mode(Tests.exit_game_mode)
|
||||
|
||||
# 12. Test IsHidden.
|
||||
postfx_shape_weight_entity.set_visibility_state(False)
|
||||
Report.result(Tests.is_hidden, postfx_shape_weight_entity.is_hidden() is True)
|
||||
|
||||
# 13. Test IsVisible.
|
||||
postfx_shape_weight_entity.set_visibility_state(True)
|
||||
general.idle_wait_frames(1)
|
||||
Report.result(Tests.is_visible, postfx_shape_weight_entity.is_visible() is True)
|
||||
|
||||
# 14. Delete PostFx Shape Weight Modifier entity.
|
||||
postfx_shape_weight_entity.delete()
|
||||
Report.result(Tests.entity_deleted, not postfx_shape_weight_entity.exists())
|
||||
|
||||
# 15. UNDO deletion.
|
||||
general.undo()
|
||||
Report.result(Tests.deletion_undo, postfx_shape_weight_entity.exists())
|
||||
|
||||
# 16. REDO deletion.
|
||||
general.redo()
|
||||
Report.result(Tests.deletion_redo, not postfx_shape_weight_entity.exists())
|
||||
|
||||
# 17. Look for errors or asserts.
|
||||
TestHelper.wait_for_condition(lambda: error_tracer.has_errors or error_tracer.has_asserts, 1.0)
|
||||
for error_info in error_tracer.errors:
|
||||
Report.info(f"Error: {error_info.filename} {error_info.function} | {error_info.message}")
|
||||
for assert_info in error_tracer.asserts:
|
||||
Report.info(f"Assert: {assert_info.filename} {assert_info.function} | {assert_info.message}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from editor_python_test_tools.utils import Report
|
||||
Report.start_test(AtomEditorComponents_postfx_shape_weight_AddedToEntity)
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"description": "",
|
||||
"materialType": "Materials/Types/Skin.materialtype",
|
||||
"parentMaterial": "",
|
||||
"propertyLayoutVersion": 3,
|
||||
"properties": {
|
||||
"wrinkleLayers": {
|
||||
"count": 3,
|
||||
"enable": true,
|
||||
"showBlendValues": true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:53e17ec8155911c8b42e85436130f600bd6dddd8931a8ccb1b2f8a9f8674cc85
|
||||
size 45104
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:0da56a05daa0ec1c476cfe25ca6d3b65267c98886cf33408f6e852fb325a8e2c
|
||||
size 198084
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:e3537fbe9205731a242251c525a67bbb5f3b8f5307537f1dc0c318b5b885ce52
|
||||
size 198112
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:bd794d5dd4b749c3275bfab79b9b5ae3f8e007d3e6741c0566c9c2d3931123bf
|
||||
size 198112
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:45ded862987a64061deffd8e4c9aa1dff4eec3bcff5f7b505679f1959e8ae137
|
||||
size 51440
|
||||
@@ -29,6 +29,10 @@ namespace AzFramework
|
||||
AZStd::vector<AZ::IO::Path> m_absoluteSourcePaths; //!< Where the gem's source path folder are located(as an absolute path)
|
||||
|
||||
static constexpr const char* GetGemAssetFolder() { return "Assets"; }
|
||||
static constexpr const char* GetGemRegistryFolder()
|
||||
{
|
||||
return "Registry";
|
||||
}
|
||||
};
|
||||
|
||||
//! Returns a list of GemInfo of all the gems that are active for the for the specified game project.
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include <AzCore/RTTI/BehaviorContext.h>
|
||||
#include <AzCore/Serialization/EditContext.h>
|
||||
#include <AzCore/Serialization/SerializeContext.h>
|
||||
#include <AzCore/Settings/SettingsRegistry.h>
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
namespace AzFramework
|
||||
@@ -190,6 +191,25 @@ namespace AzFramework
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
void InputSystemComponent::Activate()
|
||||
{
|
||||
const auto* settingsRegistry = AZ::SettingsRegistry::Get();
|
||||
if (settingsRegistry)
|
||||
{
|
||||
AZ::u64 value = 0;
|
||||
if (settingsRegistry->Get(value, "/O3DE/InputSystem/MouseMovementSampleRateHertz"))
|
||||
{
|
||||
m_mouseMovementSampleRateHertz = aznumeric_caster(value);
|
||||
}
|
||||
if (settingsRegistry->Get(value, "/O3DE/InputSystem/GamepadsEnabled"))
|
||||
{
|
||||
m_gamepadsEnabled = aznumeric_caster(value);
|
||||
}
|
||||
settingsRegistry->Get(m_keyboardEnabled, "/O3DE/InputSystem/KeyboardEnabled");
|
||||
settingsRegistry->Get(m_motionEnabled, "/O3DE/InputSystem/MotionEnabled");
|
||||
settingsRegistry->Get(m_mouseEnabled, "/O3DE/InputSystem/MouseEnabled");
|
||||
settingsRegistry->Get(m_touchEnabled, "/O3DE/InputSystem/TouchEnabled");
|
||||
settingsRegistry->Get(m_virtualKeyboardEnabled, "/O3DE/InputSystem/VirtualKeyboardEnabled");
|
||||
}
|
||||
|
||||
// Create all enabled input devices
|
||||
CreateEnabledInputDevices();
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ namespace AzFramework
|
||||
->Field("terminationTime", &SessionConfig::m_terminationTime)
|
||||
->Field("creatorId", &SessionConfig::m_creatorId)
|
||||
->Field("sessionProperties", &SessionConfig::m_sessionProperties)
|
||||
->Field("matchmakingData", &SessionConfig::m_matchmakingData)
|
||||
->Field("sessionId", &SessionConfig::m_sessionId)
|
||||
->Field("sessionName", &SessionConfig::m_sessionName)
|
||||
->Field("dnsName", &SessionConfig::m_dnsName)
|
||||
@@ -46,6 +47,8 @@ namespace AzFramework
|
||||
"CreatorId", "A unique identifier for a player or entity creating the session.")
|
||||
->DataElement(AZ::Edit::UIHandlers::Default, &AzFramework::SessionConfig::m_sessionProperties,
|
||||
"SessionProperties", "A collection of custom properties for a session.")
|
||||
->DataElement(AZ::Edit::UIHandlers::Default, &AzFramework::SessionConfig::m_matchmakingData,
|
||||
"MatchmakingData", "The matchmaking process information that was used to create the session.")
|
||||
->DataElement(AZ::Edit::UIHandlers::Default, &AzFramework::SessionConfig::m_sessionId,
|
||||
"SessionId", "A unique identifier for the session.")
|
||||
->DataElement(AZ::Edit::UIHandlers::Default, &AzFramework::SessionConfig::m_sessionName,
|
||||
|
||||
@@ -35,6 +35,9 @@ namespace AzFramework
|
||||
|
||||
// A collection of custom properties for a session.
|
||||
AZStd::unordered_map<AZStd::string, AZStd::string> m_sessionProperties;
|
||||
|
||||
// The matchmaking process information that was used to create the session.
|
||||
AZStd::string m_matchmakingData;
|
||||
|
||||
// A unique identifier for the session.
|
||||
AZStd::string m_sessionId;
|
||||
|
||||
@@ -41,6 +41,11 @@ namespace AzFramework
|
||||
// OnDestroySessionBegin is fired at the beginning of session termination
|
||||
// @return The result of all OnDestroySessionBegin notifications
|
||||
virtual bool OnDestroySessionBegin() = 0;
|
||||
|
||||
// OnUpdateSessionBegin is fired at the beginning of session update
|
||||
// @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;
|
||||
};
|
||||
using SessionNotificationBus = AZ::EBus<SessionNotifications>;
|
||||
} // namespace AzFramework
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <AzFramework/Windowing/NativeWindow.h>
|
||||
#include <AzFramework/XcbNativeWindow.h>
|
||||
#include <AzFramework/XcbConnectionManager.h>
|
||||
#include <AzFramework/XcbInterface.h>
|
||||
|
||||
#include <xcb/xcb.h>
|
||||
|
||||
|
||||
+22
-20
@@ -12,7 +12,6 @@
|
||||
|
||||
#include <xcb/xcb.h>
|
||||
|
||||
#include <AzCore/UserSettings/UserSettingsComponent.h>
|
||||
#include <AzFramework/XcbApplication.h>
|
||||
#include <AzFramework/XcbInputDeviceKeyboard.h>
|
||||
#include <AzFramework/Input/Buses/Notifications/InputTextNotificationBus.h>
|
||||
@@ -20,6 +19,7 @@
|
||||
#include "Matchers.h"
|
||||
#include "Actions.h"
|
||||
#include "XcbBaseTestFixture.h"
|
||||
#include "XcbTestApplication.h"
|
||||
|
||||
template<typename T>
|
||||
xcb_generic_event_t MakeEvent(T event)
|
||||
@@ -33,6 +33,7 @@ namespace AzFramework
|
||||
class XcbInputDeviceKeyboardTests
|
||||
: public XcbBaseTestFixture
|
||||
{
|
||||
public:
|
||||
void SetUp() override
|
||||
{
|
||||
using testing::Return;
|
||||
@@ -123,6 +124,15 @@ namespace AzFramework
|
||||
|
||||
static constexpr xcb_keycode_t s_keycodeForAKey{38};
|
||||
static constexpr xcb_keycode_t s_keycodeForShiftLKey{50};
|
||||
|
||||
XcbTestApplication m_application{
|
||||
/*enabledGamepadsCount=*/0,
|
||||
/*keyboardEnabled=*/true,
|
||||
/*motionEnabled=*/false,
|
||||
/*mouseEnabled=*/false,
|
||||
/*touchEnabled=*/false,
|
||||
/*virtualKeyboardEnabled=*/false
|
||||
};
|
||||
};
|
||||
|
||||
class InputTextNotificationListener
|
||||
@@ -195,27 +205,23 @@ namespace AzFramework
|
||||
EXPECT_CALL(m_interface, xkb_state_key_get_one_sym(&m_xkbState, s_keycodeForAKey))
|
||||
.Times(2);
|
||||
|
||||
Application application;
|
||||
application.Start({}, {});
|
||||
AZ::UserSettingsComponentRequestBus::Broadcast(&AZ::UserSettingsComponentRequests::DisableSaveOnFinalize);
|
||||
m_application.Start();
|
||||
|
||||
const InputChannel* inputChannel = InputChannelRequests::FindInputChannel(InputDeviceKeyboard::Key::AlphanumericA);
|
||||
ASSERT_TRUE(inputChannel);
|
||||
EXPECT_THAT(inputChannel->GetState(), Eq(InputChannel::State::Idle));
|
||||
|
||||
application.PumpSystemEventLoopUntilEmpty();
|
||||
application.TickSystem();
|
||||
application.Tick();
|
||||
m_application.PumpSystemEventLoopUntilEmpty();
|
||||
m_application.TickSystem();
|
||||
m_application.Tick();
|
||||
|
||||
EXPECT_THAT(inputChannel->GetState(), Eq(InputChannel::State::Began));
|
||||
|
||||
application.PumpSystemEventLoopUntilEmpty();
|
||||
application.TickSystem();
|
||||
application.Tick();
|
||||
m_application.PumpSystemEventLoopUntilEmpty();
|
||||
m_application.TickSystem();
|
||||
m_application.Tick();
|
||||
|
||||
EXPECT_THAT(inputChannel->GetState(), Eq(InputChannel::State::Ended));
|
||||
|
||||
application.Stop();
|
||||
}
|
||||
|
||||
TEST_F(XcbInputDeviceKeyboardTests, TextEnteredFromXcbKeyPressEvents)
|
||||
@@ -420,17 +426,13 @@ namespace AzFramework
|
||||
EXPECT_CALL(textListener, OnInputTextEvent(StrEq("a"), _)).Times(1);
|
||||
EXPECT_CALL(textListener, OnInputTextEvent(StrEq("A"), _)).Times(1);
|
||||
|
||||
Application application;
|
||||
application.Start({}, {});
|
||||
AZ::UserSettingsComponentRequestBus::Broadcast(&AZ::UserSettingsComponentRequests::DisableSaveOnFinalize);
|
||||
m_application.Start();
|
||||
|
||||
for (int i = 0; i < 4; ++i)
|
||||
{
|
||||
application.PumpSystemEventLoopUntilEmpty();
|
||||
application.TickSystem();
|
||||
application.Tick();
|
||||
m_application.PumpSystemEventLoopUntilEmpty();
|
||||
m_application.TickSystem();
|
||||
m_application.Tick();
|
||||
}
|
||||
|
||||
application.Stop();
|
||||
}
|
||||
} // namespace AzFramework
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AzCore/Settings/SettingsRegistry.h>
|
||||
#include <AzCore/UserSettings/UserSettingsComponent.h>
|
||||
#include <AzFramework/Application/Application.h>
|
||||
|
||||
namespace AzFramework
|
||||
{
|
||||
class XcbTestApplication
|
||||
: public Application
|
||||
{
|
||||
public:
|
||||
XcbTestApplication(AZ::u64 enabledGamepadsCount, bool keyboardEnabled, bool motionEnabled, bool mouseEnabled, bool touchEnabled, bool virtualKeyboardEnabled)
|
||||
{
|
||||
auto* settingsRegistry = AZ::SettingsRegistry::Get();
|
||||
settingsRegistry->Set("/O3DE/InputSystem/GamepadsEnabled", enabledGamepadsCount);
|
||||
settingsRegistry->Set("/O3DE/InputSystem/KeyboardEnabled", keyboardEnabled);
|
||||
settingsRegistry->Set("/O3DE/InputSystem/MotionEnabled", motionEnabled);
|
||||
settingsRegistry->Set("/O3DE/InputSystem/MouseEnabled", mouseEnabled);
|
||||
settingsRegistry->Set("/O3DE/InputSystem/TouchEnabled", touchEnabled);
|
||||
settingsRegistry->Set("/O3DE/InputSystem/VirtualKeyboardEnabled", virtualKeyboardEnabled);
|
||||
}
|
||||
|
||||
void Start(const Descriptor& descriptor = {}, const StartupParameters& startupParameters = {}) override
|
||||
{
|
||||
Application::Start(descriptor, startupParameters);
|
||||
AZ::UserSettingsComponentRequestBus::Broadcast(&AZ::UserSettingsComponentRequests::DisableSaveOnFinalize);
|
||||
}
|
||||
};
|
||||
} // namespace AzFramework
|
||||
@@ -17,4 +17,5 @@ set(FILES
|
||||
XcbBaseTestFixture.cpp
|
||||
XcbBaseTestFixture.h
|
||||
XcbInputDeviceKeyboardTests.cpp
|
||||
XcbTestApplication.h
|
||||
)
|
||||
|
||||
@@ -12,6 +12,7 @@ set(FILES
|
||||
../../Utilities/QtWindowUtilities_linux.cpp
|
||||
../../Utilities/ScreenGrabber_linux.cpp
|
||||
../../../Platform/Linux/AzQtComponents/Components/StyledDockWidget_Linux.cpp
|
||||
../../../Platform/Linux/AzQtComponents/Utilities/DesktopUtilities_Linux.cpp
|
||||
../../../Platform/Linux/AzQtComponents/AzQtComponents_Traits_Linux.h
|
||||
../../../Platform/Linux/AzQtComponents/AzQtComponents_Traits_Platform.h
|
||||
)
|
||||
|
||||
@@ -12,6 +12,7 @@ set(FILES
|
||||
../../Utilities/QtWindowUtilities_mac.mm
|
||||
../../Utilities/ScreenGrabber_mac.mm
|
||||
../../../Platform/Mac/AzQtComponents/Components/StyledDockWidget_Mac.cpp
|
||||
../../../Platform/Mac/AzQtComponents/Utilities/DesktopUtilities_Mac.cpp
|
||||
../../../Platform/Mac/AzQtComponents/AzQtComponents_Traits_Mac.h
|
||||
../../../Platform/Mac/AzQtComponents/AzQtComponents_Traits_Platform.h
|
||||
)
|
||||
|
||||
+1
@@ -9,6 +9,7 @@
|
||||
set(FILES
|
||||
../../natvis/qt.natvis
|
||||
../../../Platform/Windows/AzQtComponents/Utilities/HandleDpiAwareness_Windows.cpp
|
||||
../../../Platform/Windows/AzQtComponents/Utilities/DesktopUtilities_Windows.cpp
|
||||
../../Utilities/MouseHider_win.cpp
|
||||
../../Utilities/QtWindowUtilities_win.cpp
|
||||
../../Utilities/ScreenGrabber_win.cpp
|
||||
|
||||
@@ -271,7 +271,6 @@ set(FILES
|
||||
Utilities/ColorUtilities.h
|
||||
Utilities/Conversions.h
|
||||
Utilities/Conversions.cpp
|
||||
Utilities/DesktopUtilities.cpp
|
||||
Utilities/DesktopUtilities.h
|
||||
Utilities/HandleDpiAwareness.cpp
|
||||
Utilities/HandleDpiAwareness.h
|
||||
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#include <AzQtComponents/Utilities/DesktopUtilities.h>
|
||||
|
||||
#include <QDir>
|
||||
#include <QProcess>
|
||||
|
||||
namespace AzQtComponents
|
||||
{
|
||||
void ShowFileOnDesktop(const QString& path)
|
||||
{
|
||||
const char* defaultNautilusPath = "/usr/bin/nautilus";
|
||||
const char* defaultXdgOpenPath = "/usr/bin/xdg-open";
|
||||
|
||||
// Determine if Nautilus (for Gnome Desktops) is available because it supports opening the file manager
|
||||
// and selecting a specific file
|
||||
bool nautilusAvailable = QFileInfo(defaultNautilusPath).exists();
|
||||
|
||||
QFileInfo pathInfo(path);
|
||||
if (pathInfo.isDir())
|
||||
{
|
||||
QProcess::startDetached(defaultXdgOpenPath, { path });
|
||||
}
|
||||
else
|
||||
{
|
||||
if (nautilusAvailable)
|
||||
{
|
||||
QProcess::startDetached(defaultNautilusPath, { "--select", path });
|
||||
}
|
||||
else
|
||||
{
|
||||
QDir parentDir { pathInfo.dir() };
|
||||
QProcess::startDetached(defaultXdgOpenPath, { parentDir.path() });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QString fileBrowserActionName()
|
||||
{
|
||||
const char* exploreActionName = "Open in file browser";
|
||||
return QObject::tr(exploreActionName);
|
||||
}
|
||||
}
|
||||
-23
@@ -15,21 +15,6 @@ namespace AzQtComponents
|
||||
{
|
||||
void ShowFileOnDesktop(const QString& path)
|
||||
{
|
||||
#if defined(AZ_PLATFORM_WINDOWS)
|
||||
|
||||
// Launch explorer at the path provided
|
||||
QStringList args;
|
||||
if (!QFileInfo(path).isDir())
|
||||
{
|
||||
// Folders are just opened, files are selected
|
||||
args << "/select,";
|
||||
}
|
||||
args << QDir::toNativeSeparators(path);
|
||||
|
||||
QProcess::startDetached("explorer", args);
|
||||
|
||||
#else
|
||||
|
||||
if (QFileInfo(path).isDir())
|
||||
{
|
||||
QProcess::startDetached("/usr/bin/osascript", { "-e",
|
||||
@@ -43,19 +28,11 @@ namespace AzQtComponents
|
||||
|
||||
QProcess::startDetached("/usr/bin/osascript", { "-e",
|
||||
QStringLiteral("tell application \"Finder\" to activate") });
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
QString fileBrowserActionName()
|
||||
{
|
||||
#ifdef AZ_PLATFORM_WINDOWS
|
||||
const char* exploreActionName = "Open in Explorer";
|
||||
#elif defined(AZ_PLATFORM_MAC)
|
||||
const char* exploreActionName = "Open in Finder";
|
||||
#else
|
||||
const char* exploreActionName = "Open in file browser";
|
||||
#endif
|
||||
return QObject::tr(exploreActionName);
|
||||
}
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#include <AzQtComponents/Utilities/DesktopUtilities.h>
|
||||
|
||||
#include <QDir>
|
||||
#include <QProcess>
|
||||
|
||||
namespace AzQtComponents
|
||||
{
|
||||
void ShowFileOnDesktop(const QString& path)
|
||||
{
|
||||
// Launch explorer at the path provided
|
||||
QStringList args;
|
||||
if (!QFileInfo(path).isDir())
|
||||
{
|
||||
// Folders are just opened, files are selected
|
||||
args << "/select,";
|
||||
}
|
||||
args << QDir::toNativeSeparators(path);
|
||||
|
||||
QProcess::startDetached("explorer", args);
|
||||
}
|
||||
|
||||
QString fileBrowserActionName()
|
||||
{
|
||||
const char* exploreActionName = "Open in Explorer";
|
||||
return QObject::tr(exploreActionName);
|
||||
}
|
||||
}
|
||||
+8
-6
@@ -590,20 +590,22 @@ TEST_F(PlatformConfigurationUnitTests, Test_GemHandling)
|
||||
|
||||
AssetUtilities::ResetAssetRoot();
|
||||
|
||||
ASSERT_EQ(2, config.GetScanFolderCount());
|
||||
ASSERT_EQ(4, config.GetScanFolderCount());
|
||||
EXPECT_FALSE(config.GetScanFolderAt(0).IsRoot());
|
||||
EXPECT_TRUE(config.GetScanFolderAt(0).RecurseSubFolders());
|
||||
// the first one is a game gem, so its order should be above 1 but below 100.
|
||||
EXPECT_GE(config.GetScanFolderAt(0).GetOrder(), 100);
|
||||
EXPECT_EQ(0, config.GetScanFolderAt(0).ScanPath().compare(expectedScanFolder, Qt::CaseInsensitive));
|
||||
|
||||
// for each gem, there are currently 1 scan folder, the gem assets folder, with no output prefix
|
||||
// for each gem, there are currently 2 scan folders:
|
||||
// The Gem's 'Assets' folder
|
||||
// The Gem's 'Registry' folder
|
||||
|
||||
expectedScanFolder = tempPath.absoluteFilePath("Gems/LmbrCentral/v2/Assets");
|
||||
EXPECT_FALSE(config.GetScanFolderAt(1).IsRoot() );
|
||||
EXPECT_TRUE(config.GetScanFolderAt(1).RecurseSubFolders());
|
||||
EXPECT_GT(config.GetScanFolderAt(1).GetOrder(), config.GetScanFolderAt(0).GetOrder());
|
||||
EXPECT_EQ(0, config.GetScanFolderAt(1).ScanPath().compare(expectedScanFolder, Qt::CaseInsensitive));
|
||||
EXPECT_FALSE(config.GetScanFolderAt(2).IsRoot() );
|
||||
EXPECT_TRUE(config.GetScanFolderAt(2).RecurseSubFolders());
|
||||
EXPECT_GT(config.GetScanFolderAt(2).GetOrder(), config.GetScanFolderAt(0).GetOrder());
|
||||
EXPECT_EQ(0, config.GetScanFolderAt(2).ScanPath().compare(expectedScanFolder, Qt::CaseInsensitive));
|
||||
}
|
||||
|
||||
TEST_F(PlatformConfigurationUnitTests, Test_MetaFileTypes)
|
||||
|
||||
@@ -1582,6 +1582,24 @@ namespace AssetProcessor
|
||||
gemOrder,
|
||||
/*scanFolderId*/ 0,
|
||||
/*canSaveNewAssets*/ true)); // Users can create assets like slices in Gem asset folders.
|
||||
|
||||
// Now add another scan folder on Gem/GemName/Registry...
|
||||
gemFolder = gemDir.absoluteFilePath(AzFramework::GemInfo::GetGemRegistryFolder());
|
||||
gemFolder = AssetUtilities::NormalizeDirectoryPath(gemFolder);
|
||||
|
||||
assetBrowserDisplayName = AzFramework::GemInfo::GetGemRegistryFolder();
|
||||
portableKey = QString("gemregistry-%1").arg(gemNameAsUuid);
|
||||
gemOrder++;
|
||||
|
||||
AZ_TracePrintf(AssetProcessor::DebugChannel, "Adding GEM registry folder for monitoring / scanning: %s.\n", gemFolder.toUtf8().data());
|
||||
AddScanFolder(ScanFolderInfo(
|
||||
gemFolder,
|
||||
assetBrowserDisplayName,
|
||||
portableKey,
|
||||
isRoot,
|
||||
isRecursive,
|
||||
platforms,
|
||||
gemOrder));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,10 +15,11 @@ ly_add_target(
|
||||
awsgamelift_client_files.cmake
|
||||
INCLUDE_DIRECTORIES
|
||||
PUBLIC
|
||||
../AWSGameLiftCommon/Include
|
||||
Include
|
||||
PRIVATE
|
||||
Source
|
||||
../AWSGameLiftCommon/Source
|
||||
Source
|
||||
COMPILE_DEFINITIONS
|
||||
PRIVATE
|
||||
${awsgameliftclient_compile_definition}
|
||||
@@ -78,10 +79,11 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED)
|
||||
awsgamelift_client_tests_files.cmake
|
||||
INCLUDE_DIRECTORIES
|
||||
PRIVATE
|
||||
../AWSGameLiftCommon/Include
|
||||
../AWSGameLiftCommon/Source
|
||||
Include
|
||||
Tests
|
||||
Source
|
||||
../AWSGameLiftCommon/Source
|
||||
BUILD_DEPENDENCIES
|
||||
PRIVATE
|
||||
AZ::AzCore
|
||||
|
||||
+3
-27
@@ -10,36 +10,12 @@
|
||||
|
||||
#include <AzCore/std/containers/unordered_map.h>
|
||||
#include <AzCore/std/string/string.h>
|
||||
|
||||
#include <AzFramework/Matchmaking/MatchmakingRequests.h>
|
||||
|
||||
#include <AWSGameLiftPlayer.h>
|
||||
|
||||
namespace AWSGameLift
|
||||
{
|
||||
//! AWSGameLiftPlayerInformation
|
||||
//! Information on each player to be matched
|
||||
//! This information must include a player ID, and may contain player attributes and latency data to be used in the matchmaking process
|
||||
//! After a successful match, Player objects contain the name of the team the player is assigned to
|
||||
struct AWSGameLiftPlayerInformation
|
||||
{
|
||||
AZ_RTTI(AWSGameLiftPlayerInformation, "{B62C118E-C55D-4903-8ECB-E58E8CA613C4}");
|
||||
static void Reflect(AZ::ReflectContext* context);
|
||||
|
||||
AWSGameLiftPlayerInformation() = default;
|
||||
virtual ~AWSGameLiftPlayerInformation() = default;
|
||||
|
||||
// A map of region names to latencies in millseconds, that indicates
|
||||
// the amount of latency that a player experiences when connected to AWS Regions
|
||||
AZStd::unordered_map<AZStd::string, int> m_latencyInMs;
|
||||
// A collection of key:value pairs containing player information for use in matchmaking
|
||||
// Player attribute keys must match the playerAttributes used in a matchmaking rule set
|
||||
// Example: {"skill": "{\"N\": \"23\"}", "gameMode": "{\"S\": \"deathmatch\"}"}
|
||||
AZStd::unordered_map<AZStd::string, AZStd::string> m_playerAttributes;
|
||||
// A unique identifier for a player
|
||||
AZStd::string m_playerId;
|
||||
// Name of the team that the player is assigned to in a match
|
||||
AZStd::string m_team;
|
||||
};
|
||||
|
||||
//! AWSGameLiftStartMatchmakingRequest
|
||||
//! GameLift start matchmaking request which corresponds to Amazon GameLift
|
||||
//! Uses FlexMatch to create a game match for a group of players based on custom matchmaking rules
|
||||
@@ -57,6 +33,6 @@ namespace AWSGameLift
|
||||
// Name of the matchmaking configuration to use for this request
|
||||
AZStd::string m_configurationName;
|
||||
// Information on each player to be matched
|
||||
AZStd::vector<AWSGameLiftPlayerInformation> m_players;
|
||||
AZStd::vector<AWSGameLiftPlayer> m_players;
|
||||
};
|
||||
} // namespace AWSGameLift
|
||||
|
||||
@@ -210,6 +210,7 @@ namespace AWSGameLift
|
||||
->Property("SessionId", BehaviorValueProperty(&AzFramework::SessionConfig::m_sessionId))
|
||||
->Property("SessionName", BehaviorValueProperty(&AzFramework::SessionConfig::m_sessionName))
|
||||
->Property("SessionProperties", BehaviorValueProperty(&AzFramework::SessionConfig::m_sessionProperties))
|
||||
->Property("MatchmakingData", BehaviorValueProperty(&AzFramework::SessionConfig::m_matchmakingData))
|
||||
->Property("Status", BehaviorValueProperty(&AzFramework::SessionConfig::m_status))
|
||||
->Property("StatusReason", BehaviorValueProperty(&AzFramework::SessionConfig::m_statusReason))
|
||||
->Property("TerminationTime", BehaviorValueProperty(&AzFramework::SessionConfig::m_terminationTime))
|
||||
|
||||
+1
@@ -105,6 +105,7 @@ namespace AWSGameLift
|
||||
session.m_status = AWSGameLiftSessionStatusNames[(int)gameSession.GetStatus()];
|
||||
session.m_statusReason = AWSGameLiftSessionStatusReasons[(int)gameSession.GetStatusReason()];
|
||||
session.m_terminationTime = gameSession.GetTerminationTime().Millis();
|
||||
session.m_matchmakingData = gameSession.GetMatchmakerData().c_str();
|
||||
// TODO: Update the AWS Native SDK to get the new game session attributes.
|
||||
//session.m_dnsName = gameSession.GetDnsName();
|
||||
|
||||
|
||||
+3
-2
@@ -10,6 +10,7 @@
|
||||
|
||||
#include <Activity/AWSGameLiftActivityUtils.h>
|
||||
#include <Activity/AWSGameLiftStartMatchmakingActivity.h>
|
||||
#include <AWSGameLiftPlayer.h>
|
||||
#include <AWSGameLiftSessionConstants.h>
|
||||
|
||||
#include <aws/core/utils/Outcome.h>
|
||||
@@ -29,7 +30,7 @@ namespace AWSGameLift
|
||||
}
|
||||
|
||||
Aws::Vector<Aws::GameLift::Model::Player> players;
|
||||
for (const AWSGameLiftPlayerInformation& playerInfo : startMatchmakingRequest.m_players)
|
||||
for (const AWSGameLiftPlayer& playerInfo : startMatchmakingRequest.m_players)
|
||||
{
|
||||
Aws::GameLift::Model::Player player;
|
||||
if (!playerInfo.m_playerId.empty())
|
||||
@@ -109,7 +110,7 @@ namespace AWSGameLift
|
||||
|
||||
if (isValid)
|
||||
{
|
||||
for (const AWSGameLiftPlayerInformation& playerInfo : gameliftStartMatchmakingRequest->m_players)
|
||||
for (const AWSGameLiftPlayer& playerInfo : gameliftStartMatchmakingRequest->m_players)
|
||||
{
|
||||
isValid &= !playerInfo.m_playerId.empty();
|
||||
isValid &= AWSGameLiftActivityUtils::ValidatePlayerAttributes(playerInfo.m_playerAttributes);
|
||||
|
||||
+1
-44
@@ -14,53 +14,10 @@
|
||||
|
||||
namespace AWSGameLift
|
||||
{
|
||||
void AWSGameLiftPlayerInformation::Reflect(AZ::ReflectContext* context)
|
||||
{
|
||||
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
|
||||
{
|
||||
serializeContext->Class<AWSGameLiftPlayerInformation>()
|
||||
->Version(0)
|
||||
->Field("latencyInMs", &AWSGameLiftPlayerInformation::m_latencyInMs)
|
||||
->Field("playerAttributes", &AWSGameLiftPlayerInformation::m_playerAttributes)
|
||||
->Field("playerId", &AWSGameLiftPlayerInformation::m_playerId)
|
||||
->Field("team", &AWSGameLiftPlayerInformation::m_team);
|
||||
|
||||
if (AZ::EditContext* editContext = serializeContext->GetEditContext())
|
||||
{
|
||||
editContext->Class<AWSGameLiftPlayerInformation>("AWSGameLiftPlayerInformation", "")
|
||||
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
|
||||
->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
|
||||
->DataElement(
|
||||
AZ::Edit::UIHandlers::Default, &AWSGameLiftPlayerInformation::m_latencyInMs, "LatencyInMs",
|
||||
"A set of values, expressed in milliseconds, that indicates the amount of latency that"
|
||||
"a player experiences when connected to AWS Regions")
|
||||
->DataElement(
|
||||
AZ::Edit::UIHandlers::Default, &AWSGameLiftPlayerInformation::m_playerAttributes, "PlayerAttributes",
|
||||
"A collection of key:value pairs containing player information for use in matchmaking")
|
||||
->DataElement(
|
||||
AZ::Edit::UIHandlers::Default, &AWSGameLiftPlayerInformation::m_playerId, "PlayerId",
|
||||
"A unique identifier for a player")
|
||||
->DataElement(
|
||||
AZ::Edit::UIHandlers::Default, &AWSGameLiftPlayerInformation::m_team, "Team",
|
||||
"Name of the team that the player is assigned to in a match");
|
||||
}
|
||||
}
|
||||
|
||||
if (AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
|
||||
{
|
||||
behaviorContext->Class<AWSGameLiftPlayerInformation>("AWSGameLiftPlayerInformation")
|
||||
->Attribute(AZ::Script::Attributes::Storage, AZ::Script::Attributes::StorageType::Value)
|
||||
->Property("LatencyInMs", BehaviorValueProperty(&AWSGameLiftPlayerInformation::m_latencyInMs))
|
||||
->Property("PlayerAttributes", BehaviorValueProperty(&AWSGameLiftPlayerInformation::m_playerAttributes))
|
||||
->Property("PlayerId", BehaviorValueProperty(&AWSGameLiftPlayerInformation::m_playerId))
|
||||
->Property("Team", BehaviorValueProperty(&AWSGameLiftPlayerInformation::m_team));
|
||||
}
|
||||
}
|
||||
|
||||
void AWSGameLiftStartMatchmakingRequest::Reflect(AZ::ReflectContext* context)
|
||||
{
|
||||
AzFramework::StartMatchmakingRequest::Reflect(context);
|
||||
AWSGameLiftPlayerInformation::Reflect(context);
|
||||
AWSGameLiftPlayer::Reflect(context);
|
||||
|
||||
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
|
||||
{
|
||||
|
||||
@@ -208,6 +208,7 @@ protected:
|
||||
sessionConfig.m_terminationTime = 0;
|
||||
sessionConfig.m_creatorId = "dummyCreatorId";
|
||||
sessionConfig.m_sessionProperties["dummyKey"] = "dummyValue";
|
||||
sessionConfig.m_matchmakingData = "dummyMatchmakingData";
|
||||
sessionConfig.m_sessionId = "dummyGameSessionId";
|
||||
sessionConfig.m_sessionName = "dummyGameSessionName";
|
||||
sessionConfig.m_ipAddress = "dummyIpAddress";
|
||||
@@ -232,7 +233,7 @@ protected:
|
||||
request.m_configurationName = "dummyConfiguration";
|
||||
request.m_ticketId = DummyMatchmakingTicketId;
|
||||
|
||||
AWSGameLiftPlayerInformation player;
|
||||
AWSGameLiftPlayer player;
|
||||
player.m_playerAttributes["dummy"] = "{\"N\": \"1\"}";
|
||||
player.m_playerId = DummyPlayerId;
|
||||
player.m_latencyInMs["us-east-1"] = 10;
|
||||
@@ -813,7 +814,7 @@ TEST_F(AWSGameLiftClientManagerTest, StartMatchmaking_CallWithInvalidRequest_Get
|
||||
{
|
||||
AWSGameLiftStartMatchmakingRequest request;
|
||||
request.m_configurationName = "dummyConfiguration";
|
||||
AWSGameLiftPlayerInformation player;
|
||||
AWSGameLiftPlayer player;
|
||||
player.m_playerAttributes["dummy"] = "{\"A\": \"1\"}";
|
||||
request.m_players.emplace_back(player);
|
||||
|
||||
@@ -855,7 +856,7 @@ TEST_F(AWSGameLiftClientManagerTest, StartMatchmakingAsync_CallWithInvalidReques
|
||||
{
|
||||
AWSGameLiftStartMatchmakingRequest request;
|
||||
request.m_configurationName = "dummyConfiguration";
|
||||
AWSGameLiftPlayerInformation player;
|
||||
AWSGameLiftPlayer player;
|
||||
player.m_playerAttributes["dummy"] = "{\"A\": \"1\"}";
|
||||
request.m_players.emplace_back(player);
|
||||
|
||||
|
||||
+7
-6
@@ -8,6 +8,7 @@
|
||||
|
||||
#include <Activity/AWSGameLiftStartMatchmakingActivity.h>
|
||||
#include <AWSGameLiftClientFixture.h>
|
||||
#include <AWSGameLiftPlayer.h>
|
||||
|
||||
#include <aws/gamelift/model/StartMatchmakingRequest.h>
|
||||
|
||||
@@ -21,7 +22,7 @@ TEST_F(AWSGameLiftStartMatchmakingActivityTest, BuildAWSGameLiftStartMatchmaking
|
||||
request.m_configurationName = "dummyConfiguration";
|
||||
request.m_ticketId = "dummyTicketId";
|
||||
|
||||
AWSGameLiftPlayerInformation player;
|
||||
AWSGameLiftPlayer player;
|
||||
player.m_playerAttributes["dummy"] = "{\"S\": \"test\"}";
|
||||
player.m_playerId = "dummyPlayerId";
|
||||
player.m_team = "dummyTeam";
|
||||
@@ -58,7 +59,7 @@ TEST_F(AWSGameLiftStartMatchmakingActivityTest, ValidateStartMatchmakingRequest_
|
||||
AWSGameLiftStartMatchmakingRequest request;
|
||||
request.m_ticketId = "dummyTicketId";
|
||||
|
||||
AWSGameLiftPlayerInformation player;
|
||||
AWSGameLiftPlayer player;
|
||||
player.m_playerAttributes["dummy"] = "{\"S\": \"test\"}";
|
||||
player.m_playerId = "dummyPlayerId";
|
||||
player.m_team = "dummyTeam";
|
||||
@@ -89,7 +90,7 @@ TEST_F(AWSGameLiftStartMatchmakingActivityTest, ValidateStartMatchmakingRequest_
|
||||
request.m_configurationName = "dummyConfiguration";
|
||||
request.m_ticketId = "dummyTicketId";
|
||||
|
||||
AWSGameLiftPlayerInformation player;
|
||||
AWSGameLiftPlayer player;
|
||||
player.m_playerAttributes["dummy"] = "{\"S\": \"test\"}";
|
||||
player.m_team = "dummyTeam";
|
||||
player.m_latencyInMs["us-east-1"] = 10;
|
||||
@@ -107,7 +108,7 @@ TEST_F(AWSGameLiftStartMatchmakingActivityTest, ValidateStartMatchmakingRequest_
|
||||
request.m_configurationName = "dummyConfiguration";
|
||||
request.m_ticketId = "dummyTicketId";
|
||||
|
||||
AWSGameLiftPlayerInformation player;
|
||||
AWSGameLiftPlayer player;
|
||||
player.m_playerAttributes["dummy"] = "{\"A\": \"test\"}";
|
||||
player.m_playerId = "dummyPlayerId";
|
||||
player.m_team = "dummyTeam";
|
||||
@@ -125,7 +126,7 @@ TEST_F(AWSGameLiftStartMatchmakingActivityTest, ValidateStartMatchmakingRequest_
|
||||
AWSGameLiftStartMatchmakingRequest request;
|
||||
request.m_configurationName = "dummyConfiguration";
|
||||
|
||||
AWSGameLiftPlayerInformation player;
|
||||
AWSGameLiftPlayer player;
|
||||
player.m_playerAttributes["dummy"] = "{\"S\": \"test\"}";
|
||||
player.m_playerId = "dummyPlayerId";
|
||||
player.m_team = "dummyTeam";
|
||||
@@ -142,7 +143,7 @@ TEST_F(AWSGameLiftStartMatchmakingActivityTest, ValidateStartMatchmakingRequest_
|
||||
request.m_ticketId = "dummyTicketId";
|
||||
request.m_configurationName = "dummyConfiguration";
|
||||
|
||||
AWSGameLiftPlayerInformation player;
|
||||
AWSGameLiftPlayer player;
|
||||
player.m_playerAttributes["dummy"] = "{\"S\": \"test\"}";
|
||||
player.m_playerId = "dummyPlayerId";
|
||||
player.m_team = "dummyTeam";
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
#
|
||||
|
||||
set(FILES
|
||||
../AWSGameLiftCommon/Include/AWSGameLiftPlayer.h
|
||||
../AWSGameLiftCommon/Source/AWSGameLiftPlayer.cpp
|
||||
../AWSGameLiftCommon/Source/AWSGameLiftSessionConstants.h
|
||||
Include/Request/AWSGameLiftAcceptMatchRequest.h
|
||||
Include/Request/AWSGameLiftCreateSessionOnQueueRequest.h
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* 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/BehaviorContext.h>
|
||||
#include <AzCore/std/containers/unordered_map.h>
|
||||
#include <AzCore/std/string/string.h>
|
||||
|
||||
namespace AWSGameLift
|
||||
{
|
||||
//! AWSGameLiftPlayer
|
||||
//! Information on each player to be matched
|
||||
//! This information must include a player ID, and may contain player attributes and latency data to be used in the matchmaking process
|
||||
//! After a successful match, Player objects contain the name of the team the player is assigned to
|
||||
struct AWSGameLiftPlayer
|
||||
{
|
||||
AZ_RTTI(AWSGameLiftPlayer, "{B62C118E-C55D-4903-8ECB-E58E8CA613C4}");
|
||||
static void Reflect(AZ::ReflectContext* context);
|
||||
|
||||
AWSGameLiftPlayer() = default;
|
||||
virtual ~AWSGameLiftPlayer() = default;
|
||||
|
||||
// A map of region names to latencies in millseconds, that indicates
|
||||
// the amount of latency that a player experiences when connected to AWS Regions
|
||||
AZStd::unordered_map<AZStd::string, int> m_latencyInMs;
|
||||
|
||||
// A collection of key:value pairs containing player information for use in matchmaking
|
||||
// Player attribute keys must match the playerAttributes used in a matchmaking rule set
|
||||
// Example: {"skill": "{\"N\": 23}", "gameMode": "{\"S\": \"deathmatch\"}"}
|
||||
AZStd::unordered_map<AZStd::string, AZStd::string> m_playerAttributes;
|
||||
|
||||
// A unique identifier for a player
|
||||
AZStd::string m_playerId;
|
||||
|
||||
// Name of the team that the player is assigned to in a match
|
||||
AZStd::string m_team;
|
||||
};
|
||||
} // namespace AWSGameLift
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* 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/Serialization/EditContext.h>
|
||||
#include <AzCore/Serialization/SerializeContext.h>
|
||||
|
||||
#include <AWSGameLiftPlayer.h>
|
||||
|
||||
namespace AWSGameLift
|
||||
{
|
||||
void AWSGameLiftPlayer::Reflect(AZ::ReflectContext* context)
|
||||
{
|
||||
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
|
||||
{
|
||||
serializeContext->Class<AWSGameLiftPlayer>()
|
||||
->Version(0)
|
||||
->Field("latencyInMs", &AWSGameLiftPlayer::m_latencyInMs)
|
||||
->Field("playerAttributes", &AWSGameLiftPlayer::m_playerAttributes)
|
||||
->Field("playerId", &AWSGameLiftPlayer::m_playerId)
|
||||
->Field("team", &AWSGameLiftPlayer::m_team);
|
||||
|
||||
if (AZ::EditContext* editContext = serializeContext->GetEditContext())
|
||||
{
|
||||
editContext->Class<AWSGameLiftPlayer>("AWSGameLiftPlayer", "")
|
||||
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
|
||||
->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
|
||||
->DataElement(
|
||||
AZ::Edit::UIHandlers::Default, &AWSGameLiftPlayer::m_latencyInMs, "LatencyInMs",
|
||||
"A set of values, expressed in milliseconds, that indicates the amount of latency that"
|
||||
"a player experiences when connected to AWS Regions")
|
||||
->DataElement(
|
||||
AZ::Edit::UIHandlers::Default, &AWSGameLiftPlayer::m_playerAttributes, "PlayerAttributes",
|
||||
"A collection of key:value pairs containing player information for use in matchmaking")
|
||||
->DataElement(
|
||||
AZ::Edit::UIHandlers::Default, &AWSGameLiftPlayer::m_playerId, "PlayerId",
|
||||
"A unique identifier for a player")
|
||||
->DataElement(
|
||||
AZ::Edit::UIHandlers::Default, &AWSGameLiftPlayer::m_team, "Team",
|
||||
"Name of the team that the player is assigned to in a match");
|
||||
}
|
||||
}
|
||||
|
||||
if (AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
|
||||
{
|
||||
behaviorContext->Class<AWSGameLiftPlayer>("AWSGameLiftPlayer")
|
||||
->Attribute(AZ::Script::Attributes::Storage, AZ::Script::Attributes::StorageType::Value)
|
||||
->Property("LatencyInMs", BehaviorValueProperty(&AWSGameLiftPlayer::m_latencyInMs))
|
||||
->Property("PlayerAttributes", BehaviorValueProperty(&AWSGameLiftPlayer::m_playerAttributes))
|
||||
->Property("PlayerId", BehaviorValueProperty(&AWSGameLiftPlayer::m_playerId))
|
||||
->Property("Team", BehaviorValueProperty(&AWSGameLiftPlayer::m_team));
|
||||
}
|
||||
}
|
||||
} // namespace AWSGameLift
|
||||
@@ -17,6 +17,7 @@ ly_add_target(
|
||||
awsgamelift_server_files.cmake
|
||||
INCLUDE_DIRECTORIES
|
||||
PUBLIC
|
||||
../AWSGameLiftCommon/Include
|
||||
Include
|
||||
PRIVATE
|
||||
../AWSGameLiftCommon/Source
|
||||
@@ -54,6 +55,8 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED)
|
||||
awsgamelift_server_tests_files.cmake
|
||||
INCLUDE_DIRECTORIES
|
||||
PRIVATE
|
||||
../AWSGameLiftCommon/Include
|
||||
../AWSGameLiftCommon/Source
|
||||
Tests
|
||||
Source
|
||||
BUILD_DEPENDENCIES
|
||||
|
||||
+18
-3
@@ -9,9 +9,11 @@
|
||||
#pragma once
|
||||
|
||||
#include <AzCore/EBus/EBus.h>
|
||||
#include <AzCore/RTTI/BehaviorContext.h>
|
||||
#include <AzCore/RTTI/RTTI.h>
|
||||
#include <AzCore/std/containers/vector.h>
|
||||
#include <AzCore/std/string/string.h>
|
||||
#include <AzFramework/Session/ISessionRequests.h>
|
||||
|
||||
#include <AWSGameLiftPlayer.h>
|
||||
|
||||
namespace AWSGameLift
|
||||
{
|
||||
@@ -26,8 +28,21 @@ namespace AWSGameLift
|
||||
virtual ~IAWSGameLiftServerRequests() = default;
|
||||
|
||||
//! Notify GameLift that the server process is ready to host a game session.
|
||||
//! @return Whether the ProcessReady notification is sent to GameLift.
|
||||
//! @return True if the ProcessReady notification is sent to GameLift successfully, false otherwise
|
||||
virtual bool NotifyGameLiftProcessReady() = 0;
|
||||
|
||||
//! Sends a request to find new players for open slots in a game session created with FlexMatch.
|
||||
//! @param ticketId Unique identifier for match backfill request ticket
|
||||
//! @param players A set of data representing all players who are currently in the game session,
|
||||
//! if not provided, system will use lazy loaded game session data which is not guaranteed to
|
||||
//! be accurate (no latency data either)
|
||||
//! @return True if StartMatchBackfill succeeds, false otherwise
|
||||
virtual bool StartMatchBackfill(const AZStd::string& ticketId, const AZStd::vector<AWSGameLiftPlayer>& players) = 0;
|
||||
|
||||
//! Cancels an active match backfill request that was created with StartMatchBackfill
|
||||
//! @param ticketId Unique identifier of the backfill request ticket to be canceled
|
||||
//! @return True if StopMatchBackfill succeeds, false otherwise
|
||||
virtual bool StopMatchBackfill(const AZStd::string& ticketId) = 0;
|
||||
};
|
||||
|
||||
// IAWSGameLiftServerRequests EBus wrapper for scripting
|
||||
|
||||
@@ -17,6 +17,9 @@
|
||||
#include <AzCore/IO/SystemFile.h>
|
||||
#include <AzCore/Jobs/JobFunction.h>
|
||||
#include <AzCore/Jobs/JobManagerBus.h>
|
||||
#include <AzCore/JSON/error/en.h>
|
||||
#include <AzCore/JSON/stringbuffer.h>
|
||||
#include <AzCore/JSON/writer.h>
|
||||
#include <AzCore/std/bind/bind.h>
|
||||
#include <AzFramework/Session/SessionNotifications.h>
|
||||
|
||||
@@ -112,6 +115,7 @@ namespace AWSGameLift
|
||||
{
|
||||
propertiesOutput = propertiesOutput.substr(0, propertiesOutput.size() - 1); // Trim last comma to fit array format
|
||||
}
|
||||
sessionConfig.m_matchmakingData = gameSession.GetMatchmakerData().c_str();
|
||||
sessionConfig.m_sessionId = gameSession.GetGameSessionId().c_str();
|
||||
sessionConfig.m_ipAddress = gameSession.GetIpAddress().c_str();
|
||||
sessionConfig.m_maxPlayer = gameSession.GetMaximumPlayerSessionCount();
|
||||
@@ -133,6 +137,276 @@ namespace AWSGameLift
|
||||
return sessionConfig;
|
||||
}
|
||||
|
||||
bool AWSGameLiftServerManager::BuildServerMatchBackfillPlayer(
|
||||
const AWSGameLiftPlayer& player, Aws::GameLift::Server::Model::Player& outBackfillPlayer)
|
||||
{
|
||||
outBackfillPlayer.SetPlayerId(player.m_playerId.c_str());
|
||||
outBackfillPlayer.SetTeam(player.m_team.c_str());
|
||||
for (auto latencyPair : player.m_latencyInMs)
|
||||
{
|
||||
outBackfillPlayer.AddLatencyInMs(latencyPair.first.c_str(), latencyPair.second);
|
||||
}
|
||||
|
||||
for (auto attributePair : player.m_playerAttributes)
|
||||
{
|
||||
Aws::GameLift::Server::Model::AttributeValue playerAttribute;
|
||||
rapidjson::Document attributeDocument;
|
||||
rapidjson::ParseResult parseResult = attributeDocument.Parse(attributePair.second.c_str());
|
||||
// player attribute json content should always be a single member object
|
||||
if (parseResult && attributeDocument.IsObject() && attributeDocument.MemberCount() == 1)
|
||||
{
|
||||
if ((attributeDocument.HasMember(AWSGameLiftMatchmakingPlayerAttributeSTypeName) ||
|
||||
attributeDocument.HasMember(AWSGameLiftMatchmakingPlayerAttributeSServerTypeName)) &&
|
||||
attributeDocument.MemberBegin()->value.IsString())
|
||||
{
|
||||
playerAttribute = Aws::GameLift::Server::Model::AttributeValue(
|
||||
attributeDocument.MemberBegin()->value.GetString());
|
||||
}
|
||||
else if ((attributeDocument.HasMember(AWSGameLiftMatchmakingPlayerAttributeNTypeName) ||
|
||||
attributeDocument.HasMember(AWSGameLiftMatchmakingPlayerAttributeNServerTypeName)) &&
|
||||
attributeDocument.MemberBegin()->value.IsNumber())
|
||||
{
|
||||
playerAttribute = Aws::GameLift::Server::Model::AttributeValue(
|
||||
attributeDocument.MemberBegin()->value.GetDouble());
|
||||
}
|
||||
else if ((attributeDocument.HasMember(AWSGameLiftMatchmakingPlayerAttributeSDMTypeName) ||
|
||||
attributeDocument.HasMember(AWSGameLiftMatchmakingPlayerAttributeSDMServerTypeName)) &&
|
||||
attributeDocument.MemberBegin()->value.IsObject())
|
||||
{
|
||||
playerAttribute = Aws::GameLift::Server::Model::AttributeValue::ConstructStringDoubleMap();
|
||||
for (auto iter = attributeDocument.MemberBegin()->value.MemberBegin();
|
||||
iter != attributeDocument.MemberBegin()->value.MemberEnd(); iter++)
|
||||
{
|
||||
if (iter->name.IsString() && iter->value.IsNumber())
|
||||
{
|
||||
playerAttribute.AddStringAndDouble(iter->name.GetString(), iter->value.GetDouble());
|
||||
}
|
||||
else
|
||||
{
|
||||
AZ_Error(AWSGameLiftServerManagerName, false, AWSGameLiftMatchmakingPlayerAttributeInvalidErrorMessage,
|
||||
player.m_playerId.c_str(), "String double map key must be string type and value must be number type");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if ((attributeDocument.HasMember(AWSGameLiftMatchmakingPlayerAttributeSLTypeName) ||
|
||||
attributeDocument.HasMember(AWSGameLiftMatchmakingPlayerAttributeSLServerTypeName)) &&
|
||||
attributeDocument.MemberBegin()->value.IsArray())
|
||||
{
|
||||
playerAttribute = Aws::GameLift::Server::Model::AttributeValue::ConstructStringList();
|
||||
for (auto iter = attributeDocument.MemberBegin()->value.Begin();
|
||||
iter != attributeDocument.MemberBegin()->value.End(); iter++)
|
||||
{
|
||||
if (iter->IsString())
|
||||
{
|
||||
playerAttribute.AddString(iter->GetString());
|
||||
}
|
||||
else
|
||||
{
|
||||
AZ_Error(AWSGameLiftServerManagerName, false, AWSGameLiftMatchmakingPlayerAttributeInvalidErrorMessage,
|
||||
player.m_playerId.c_str(), "String list element must be string type");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AZ_Error(AWSGameLiftServerManagerName, false, AWSGameLiftMatchmakingPlayerAttributeInvalidErrorMessage,
|
||||
player.m_playerId.c_str(), "S, N, SDM or SLM is expected as attribute type.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AZ_Error(AWSGameLiftServerManagerName, false, AWSGameLiftMatchmakingPlayerAttributeInvalidErrorMessage,
|
||||
player.m_playerId.c_str(), rapidjson::GetParseError_En(parseResult.Code()));
|
||||
return false;
|
||||
}
|
||||
outBackfillPlayer.AddPlayerAttribute(attributePair.first.c_str(), playerAttribute);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
AZStd::vector<AWSGameLiftPlayer> AWSGameLiftServerManager::GetActiveServerMatchBackfillPlayers()
|
||||
{
|
||||
AZStd::vector<AWSGameLiftPlayer> activePlayers;
|
||||
// Keep processing only when game session has matchmaking data
|
||||
if (IsMatchmakingDataValid())
|
||||
{
|
||||
auto activePlayerSessions = GetActivePlayerSessions();
|
||||
for (auto playerSession : activePlayerSessions)
|
||||
{
|
||||
AWSGameLiftPlayer player;
|
||||
if (BuildActiveServerMatchBackfillPlayer(playerSession.GetPlayerId().c_str(), player))
|
||||
{
|
||||
activePlayers.push_back(player);
|
||||
}
|
||||
}
|
||||
}
|
||||
return activePlayers;
|
||||
}
|
||||
|
||||
bool AWSGameLiftServerManager::IsMatchmakingDataValid()
|
||||
{
|
||||
return m_matchmakingData.IsObject() &&
|
||||
m_matchmakingData.HasMember(AWSGameLiftMatchmakingConfigurationKeyName) &&
|
||||
m_matchmakingData.HasMember(AWSGameLiftMatchmakingTeamsKeyName);
|
||||
}
|
||||
|
||||
AZStd::vector<Aws::GameLift::Server::Model::PlayerSession> AWSGameLiftServerManager::GetActivePlayerSessions()
|
||||
{
|
||||
Aws::GameLift::Server::Model::DescribePlayerSessionsRequest describeRequest;
|
||||
describeRequest.SetGameSessionId(m_gameSession.GetGameSessionId());
|
||||
describeRequest.SetPlayerSessionStatusFilter(
|
||||
Aws::GameLift::Server::Model::PlayerSessionStatusMapper::GetNameForPlayerSessionStatus(
|
||||
Aws::GameLift::Server::Model::PlayerSessionStatus::ACTIVE));
|
||||
int maxPlayerSession = m_gameSession.GetMaximumPlayerSessionCount();
|
||||
|
||||
AZStd::vector<Aws::GameLift::Server::Model::PlayerSession> activePlayerSessions;
|
||||
if (maxPlayerSession <= AWSGameLiftDescribePlayerSessionsPageSize)
|
||||
{
|
||||
describeRequest.SetLimit(maxPlayerSession);
|
||||
auto outcome = m_gameLiftServerSDKWrapper->DescribePlayerSessions(describeRequest);
|
||||
if (outcome.IsSuccess())
|
||||
{
|
||||
for (auto playerSession : outcome.GetResult().GetPlayerSessions())
|
||||
{
|
||||
activePlayerSessions.push_back(playerSession);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AZ_Error(AWSGameLiftServerManagerName, false, AWSGameLiftDescribePlayerSessionsErrorMessage,
|
||||
outcome.GetError().GetErrorMessage().c_str());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
describeRequest.SetLimit(AWSGameLiftDescribePlayerSessionsPageSize);
|
||||
while (true)
|
||||
{
|
||||
auto outcome = m_gameLiftServerSDKWrapper->DescribePlayerSessions(describeRequest);
|
||||
if (outcome.IsSuccess())
|
||||
{
|
||||
for (auto playerSession : outcome.GetResult().GetPlayerSessions())
|
||||
{
|
||||
activePlayerSessions.push_back(playerSession);
|
||||
}
|
||||
if (outcome.GetResult().GetNextToken().empty())
|
||||
{
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
describeRequest.SetNextToken(outcome.GetResult().GetNextToken());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
activePlayerSessions.clear();
|
||||
AZ_Error(AWSGameLiftServerManagerName, false, AWSGameLiftDescribePlayerSessionsErrorMessage,
|
||||
outcome.GetError().GetErrorMessage().c_str());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return activePlayerSessions;
|
||||
}
|
||||
|
||||
bool AWSGameLiftServerManager::BuildActiveServerMatchBackfillPlayer(const AZStd::string& playerId, AWSGameLiftPlayer& outPlayer)
|
||||
{
|
||||
// As data is from GameLift service, assume it is always in correct format
|
||||
rapidjson::Value& teams = m_matchmakingData[AWSGameLiftMatchmakingTeamsKeyName];
|
||||
|
||||
// Iterate through teams to find target player
|
||||
for (rapidjson::SizeType teamIndex = 0; teamIndex < teams.Size(); ++teamIndex)
|
||||
{
|
||||
rapidjson::Value& players = teams[teamIndex][AWSGameLiftMatchmakingPlayersKeyName];
|
||||
|
||||
// Iterate through players under the team to find target player
|
||||
for (rapidjson::SizeType playerIndex = 0; playerIndex < players.Size(); ++playerIndex)
|
||||
{
|
||||
if (std::strcmp(players[playerIndex][AWSGameLiftMatchmakingPlayerIdKeyName].GetString(), playerId.c_str()) == 0)
|
||||
{
|
||||
outPlayer.m_playerId = playerId;
|
||||
outPlayer.m_team = teams[teamIndex][AWSGameLiftMatchmakingTeamNameKeyName].GetString();
|
||||
// Get player attributes if target player has
|
||||
if (players[playerIndex].HasMember(AWSGameLiftMatchmakingPlayerAttributesKeyName))
|
||||
{
|
||||
BuildServerMatchBackfillPlayerAttributes(
|
||||
players[playerIndex][AWSGameLiftMatchmakingPlayerAttributesKeyName], outPlayer);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void AWSGameLiftServerManager::BuildServerMatchBackfillPlayerAttributes(
|
||||
const rapidjson::Value& playerAttributes, AWSGameLiftPlayer& outPlayer)
|
||||
{
|
||||
for (auto iter = playerAttributes.MemberBegin(); iter != playerAttributes.MemberEnd(); iter++)
|
||||
{
|
||||
AZStd::string attributeName = iter->name.GetString();
|
||||
|
||||
rapidjson::StringBuffer jsonStringBuffer;
|
||||
rapidjson::Writer<rapidjson::StringBuffer> writer(jsonStringBuffer);
|
||||
iter->value[AWSGameLiftMatchmakingPlayerAttributeValueKeyName].Accept(writer);
|
||||
AZStd::string attributeType = iter->value[AWSGameLiftMatchmakingPlayerAttributeTypeKeyName].GetString();
|
||||
AZStd::string attributeValue = AZStd::string::format("{\"%s\": %s}",
|
||||
attributeType.c_str(), jsonStringBuffer.GetString());
|
||||
|
||||
outPlayer.m_playerAttributes.emplace(attributeName, attributeValue);
|
||||
}
|
||||
}
|
||||
|
||||
bool AWSGameLiftServerManager::BuildStartMatchBackfillRequest(
|
||||
const AZStd::string& ticketId,
|
||||
const AZStd::vector<AWSGameLiftPlayer>& players,
|
||||
Aws::GameLift::Server::Model::StartMatchBackfillRequest& outRequest)
|
||||
{
|
||||
outRequest.SetGameSessionArn(m_gameSession.GetGameSessionId());
|
||||
outRequest.SetMatchmakingConfigurationArn(m_matchmakingData[AWSGameLiftMatchmakingConfigurationKeyName].GetString());
|
||||
if (!ticketId.empty())
|
||||
{
|
||||
outRequest.SetTicketId(ticketId.c_str());
|
||||
}
|
||||
|
||||
AZStd::vector<AWSGameLiftPlayer> requestPlayers(players);
|
||||
if (players.size() == 0)
|
||||
{
|
||||
requestPlayers = GetActiveServerMatchBackfillPlayers();
|
||||
}
|
||||
for (auto player : requestPlayers)
|
||||
{
|
||||
Aws::GameLift::Server::Model::Player backfillPlayer;
|
||||
if (BuildServerMatchBackfillPlayer(player, backfillPlayer))
|
||||
{
|
||||
outRequest.AddPlayer(backfillPlayer);
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void AWSGameLiftServerManager::BuildStopMatchBackfillRequest(
|
||||
const AZStd::string& ticketId, Aws::GameLift::Server::Model::StopMatchBackfillRequest& outRequest)
|
||||
{
|
||||
outRequest.SetGameSessionArn(m_gameSession.GetGameSessionId());
|
||||
outRequest.SetMatchmakingConfigurationArn(m_matchmakingData[AWSGameLiftMatchmakingConfigurationKeyName].GetString());
|
||||
if (!ticketId.empty())
|
||||
{
|
||||
outRequest.SetTicketId(ticketId.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
AZ::IO::Path AWSGameLiftServerManager::GetExternalSessionCertificate()
|
||||
{
|
||||
// TODO: Add support to get TLS cert file path
|
||||
@@ -238,7 +512,7 @@ namespace AWSGameLift
|
||||
|
||||
Aws::GameLift::Server::ProcessParameters processReadyParameter = Aws::GameLift::Server::ProcessParameters(
|
||||
AZStd::bind(&AWSGameLiftServerManager::OnStartGameSession, this, AZStd::placeholders::_1),
|
||||
AZStd::bind(&AWSGameLiftServerManager::OnUpdateGameSession, this),
|
||||
AZStd::bind(&AWSGameLiftServerManager::OnUpdateGameSession, this, AZStd::placeholders::_1),
|
||||
AZStd::bind(&AWSGameLiftServerManager::OnProcessTerminate, this),
|
||||
AZStd::bind(&AWSGameLiftServerManager::OnHealthCheck, this), desc.m_port,
|
||||
Aws::GameLift::Server::LogParameters(logPaths));
|
||||
@@ -260,6 +534,7 @@ namespace AWSGameLift
|
||||
|
||||
void AWSGameLiftServerManager::OnStartGameSession(const Aws::GameLift::Server::Model::GameSession& gameSession)
|
||||
{
|
||||
UpdateGameSessionData(gameSession);
|
||||
AzFramework::SessionConfig sessionConfig = BuildSessionConfig(gameSession);
|
||||
|
||||
bool createSessionResult = true;
|
||||
@@ -311,10 +586,19 @@ namespace AWSGameLift
|
||||
return m_serverSDKInitialized && healthCheckResult;
|
||||
}
|
||||
|
||||
void AWSGameLiftServerManager::OnUpdateGameSession()
|
||||
void AWSGameLiftServerManager::OnUpdateGameSession(const Aws::GameLift::Server::Model::UpdateGameSession& updateGameSession)
|
||||
{
|
||||
// TODO: Perform game-specific tasks to prep for newly matched players
|
||||
return;
|
||||
Aws::GameLift::Server::Model::UpdateReason updateReason = updateGameSession.GetUpdateReason();
|
||||
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());
|
||||
}
|
||||
|
||||
bool AWSGameLiftServerManager::RemoveConnectedPlayer(uint32_t playerConnectionId, AZStd::string& outPlayerSessionId)
|
||||
@@ -340,6 +624,92 @@ namespace AWSGameLift
|
||||
m_gameLiftServerSDKWrapper = AZStd::move(gameLiftServerSDKWrapper);
|
||||
}
|
||||
|
||||
bool AWSGameLiftServerManager::StartMatchBackfill(const AZStd::string& ticketId, const AZStd::vector<AWSGameLiftPlayer>& players)
|
||||
{
|
||||
if (!m_serverSDKInitialized)
|
||||
{
|
||||
AZ_Error(AWSGameLiftServerManagerName, false, AWSGameLiftServerSDKNotInitErrorMessage);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!IsMatchmakingDataValid())
|
||||
{
|
||||
AZ_Error(AWSGameLiftServerManagerName, false, AWSGameLiftMatchmakingDataMissingErrorMessage);
|
||||
return false;
|
||||
}
|
||||
|
||||
Aws::GameLift::Server::Model::StartMatchBackfillRequest request;
|
||||
if (!BuildStartMatchBackfillRequest(ticketId, players, request))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
AZ_TracePrintf(AWSGameLiftServerManagerName, "Starting match backfill %s ...", ticketId.c_str());
|
||||
auto outcome = m_gameLiftServerSDKWrapper->StartMatchBackfill(request);
|
||||
if (!outcome.IsSuccess())
|
||||
{
|
||||
AZ_Error(AWSGameLiftServerManagerName, false, AWSGameLiftStartMatchBackfillErrorMessage,
|
||||
outcome.GetError().GetErrorMessage().c_str());
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
AZ_TracePrintf(AWSGameLiftServerManagerName, "StartMatchBackfill request against Amazon GameLift service is complete.");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool AWSGameLiftServerManager::StopMatchBackfill(const AZStd::string& ticketId)
|
||||
{
|
||||
if (!m_serverSDKInitialized)
|
||||
{
|
||||
AZ_Error(AWSGameLiftServerManagerName, false, AWSGameLiftServerSDKNotInitErrorMessage);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!IsMatchmakingDataValid())
|
||||
{
|
||||
AZ_Error(AWSGameLiftServerManagerName, false, AWSGameLiftMatchmakingDataMissingErrorMessage);
|
||||
return false;
|
||||
}
|
||||
|
||||
Aws::GameLift::Server::Model::StopMatchBackfillRequest request;
|
||||
BuildStopMatchBackfillRequest(ticketId, request);
|
||||
|
||||
AZ_TracePrintf(AWSGameLiftServerManagerName, "Stopping match backfill %s ...", ticketId.c_str());
|
||||
auto outcome = m_gameLiftServerSDKWrapper->StopMatchBackfill(request);
|
||||
if (!outcome.IsSuccess())
|
||||
{
|
||||
AZ_Error(AWSGameLiftServerManagerName, false, AWSGameLiftStopMatchBackfillErrorMessage,
|
||||
outcome.GetError().GetErrorMessage().c_str());
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
AZ_TracePrintf(AWSGameLiftServerManagerName, "StopMatchBackfill request against Amazon GameLift service is complete.");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void AWSGameLiftServerManager::UpdateGameSessionData(const Aws::GameLift::Server::Model::GameSession& gameSession)
|
||||
{
|
||||
AZ_TracePrintf(AWSGameLiftServerManagerName, "Lazy loading game session and matchmaking data from Amazon GameLift service ...");
|
||||
m_gameSession = Aws::GameLift::Server::Model::GameSession(gameSession);
|
||||
if (m_gameSession.GetMatchmakerData().empty())
|
||||
{
|
||||
m_matchmakingData.Parse("{}");
|
||||
}
|
||||
else
|
||||
{
|
||||
rapidjson::ParseResult parseResult = m_matchmakingData.Parse(m_gameSession.GetMatchmakerData().c_str());
|
||||
if (!parseResult)
|
||||
{
|
||||
AZ_Error(AWSGameLiftServerManagerName, false,
|
||||
AWSGameLiftMatchmakingDataInvalidErrorMessage, rapidjson::GetParseError_En(parseResult.Code()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool AWSGameLiftServerManager::ValidatePlayerJoinSession(const AzFramework::PlayerConnectionConfig& playerConnectionConfig)
|
||||
{
|
||||
uint32_t playerConnectionId = playerConnectionConfig.m_playerConnectionId;
|
||||
|
||||
@@ -11,11 +11,15 @@
|
||||
#include <aws/gamelift/server/GameLiftServerAPI.h>
|
||||
#include <aws/gamelift/server/model/GameSession.h>
|
||||
|
||||
#include <AzCore/JSON/rapidjson.h>
|
||||
#include <AzCore/JSON/document.h>
|
||||
#include <AzCore/std/containers/vector.h>
|
||||
#include <AzCore/std/string/string.h>
|
||||
#include <AzCore/std/smart_ptr/unique_ptr.h>
|
||||
#include <AzFramework/Session/ISessionHandlingRequests.h>
|
||||
#include <AzFramework/Session/SessionConfig.h>
|
||||
|
||||
#include <AWSGameLiftPlayer.h>
|
||||
#include <Request/IAWSGameLiftServerRequests.h>
|
||||
|
||||
namespace AWSGameLift
|
||||
@@ -66,6 +70,36 @@ namespace AWSGameLift
|
||||
"Invalid player connection config, player connection id: %d, player session id: %s";
|
||||
static constexpr const char AWSGameLiftServerRemovePlayerSessionErrorMessage[] =
|
||||
"Failed to notify GameLift that the player with the player session id %s has disconnected from the server process. ErrorMessage: %s";
|
||||
static constexpr const char AWSGameLiftMatchmakingDataInvalidErrorMessage[] =
|
||||
"Failed to parse GameLift matchmaking data. ErrorMessage: %s";
|
||||
static constexpr const char AWSGameLiftMatchmakingDataMissingErrorMessage[] =
|
||||
"GameLift matchmaking data is missing or invalid to parse.";
|
||||
static constexpr const char AWSGameLiftMatchmakingPlayerAttributeInvalidErrorMessage[] =
|
||||
"Failed to build player %s attributes. ErrorMessage: %s";
|
||||
static constexpr const char AWSGameLiftDescribePlayerSessionsErrorMessage[] =
|
||||
"Failed to describe player sessions. ErrorMessage: %s";
|
||||
static constexpr const char AWSGameLiftStartMatchBackfillErrorMessage[] =
|
||||
"Failed to start match backfill. ErrorMessage: %s";
|
||||
static constexpr const char AWSGameLiftStopMatchBackfillErrorMessage[] =
|
||||
"Failed to stop match backfill. ErrorMessage: %s";
|
||||
|
||||
static constexpr const char AWSGameLiftMatchmakingConfigurationKeyName[] = "matchmakingConfigurationArn";
|
||||
static constexpr const char AWSGameLiftMatchmakingTeamsKeyName[] = "teams";
|
||||
static constexpr const char AWSGameLiftMatchmakingTeamNameKeyName[] = "name";
|
||||
static constexpr const char AWSGameLiftMatchmakingPlayersKeyName[] = "players";
|
||||
static constexpr const char AWSGameLiftMatchmakingPlayerIdKeyName[] = "playerId";
|
||||
static constexpr const char AWSGameLiftMatchmakingPlayerAttributesKeyName[] = "attributes";
|
||||
static constexpr const char AWSGameLiftMatchmakingPlayerAttributeTypeKeyName[] = "attributeType";
|
||||
static constexpr const char AWSGameLiftMatchmakingPlayerAttributeValueKeyName[] = "valueAttribute";
|
||||
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 AWSGameLiftMatchmakingPlayerAttributeSLTypeName[] = "SL";
|
||||
static constexpr const char AWSGameLiftMatchmakingPlayerAttributeSLServerTypeName[] = "STRING_LIST";
|
||||
static constexpr const char AWSGameLiftMatchmakingPlayerAttributeSDMTypeName[] = "SDM";
|
||||
static constexpr const char AWSGameLiftMatchmakingPlayerAttributeSDMServerTypeName[] = "STRING_DOUBLE_MAP";
|
||||
static constexpr const uint16_t AWSGameLiftDescribePlayerSessionsPageSize = 30;
|
||||
|
||||
AWSGameLiftServerManager();
|
||||
virtual ~AWSGameLiftServerManager();
|
||||
@@ -78,6 +112,8 @@ namespace AWSGameLift
|
||||
|
||||
// AWSGameLiftServerRequestBus interface implementation
|
||||
bool NotifyGameLiftProcessReady() override;
|
||||
bool StartMatchBackfill(const AZStd::string& ticketId, const AZStd::vector<AWSGameLiftPlayer>& players) override;
|
||||
bool StopMatchBackfill(const AZStd::string& ticketId) override;
|
||||
|
||||
// ISessionHandlingProviderRequests interface implementation
|
||||
void HandleDestroySession() override;
|
||||
@@ -92,18 +128,48 @@ namespace AWSGameLift
|
||||
//! Add connected player session id.
|
||||
bool AddConnectedPlayer(const AzFramework::PlayerConnectionConfig& playerConnectionConfig);
|
||||
|
||||
//! Get active server player data from lazy loaded game session for server match backfill
|
||||
AZStd::vector<AWSGameLiftPlayer> GetActiveServerMatchBackfillPlayers();
|
||||
|
||||
//! Update local game session data to latest one
|
||||
void UpdateGameSessionData(const Aws::GameLift::Server::Model::GameSession& gameSession);
|
||||
|
||||
private:
|
||||
//! Build the serverProcessDesc with appropriate server port number and log paths.
|
||||
GameLiftServerProcessDesc BuildGameLiftServerProcessDesc();
|
||||
|
||||
//! Build active server player data from lazy loaded game session based on player id
|
||||
bool BuildActiveServerMatchBackfillPlayer(const AZStd::string& playerId, AWSGameLiftPlayer& outPlayer);
|
||||
|
||||
//! Build server player attribute data from lazy load matchmaking data
|
||||
void BuildServerMatchBackfillPlayerAttributes(const rapidjson::Value& playerAttributes, AWSGameLiftPlayer& outPlayer);
|
||||
|
||||
//! Build server player data for server match backfill
|
||||
bool BuildServerMatchBackfillPlayer(const AWSGameLiftPlayer& player, Aws::GameLift::Server::Model::Player& outBackfillPlayer);
|
||||
|
||||
//! Build start match backfill request for StartMatchBackfill operation
|
||||
bool BuildStartMatchBackfillRequest(
|
||||
const AZStd::string& ticketId,
|
||||
const AZStd::vector<AWSGameLiftPlayer>& players,
|
||||
Aws::GameLift::Server::Model::StartMatchBackfillRequest& outRequest);
|
||||
|
||||
//! Build stop match backfill request for StopMatchBackfill operation
|
||||
void BuildStopMatchBackfillRequest(const AZStd::string& ticketId, Aws::GameLift::Server::Model::StopMatchBackfillRequest& outRequest);
|
||||
|
||||
//! Build session config by using AWS GameLift Server GameSession Model.
|
||||
AzFramework::SessionConfig BuildSessionConfig(const Aws::GameLift::Server::Model::GameSession& gameSession);
|
||||
|
||||
//! Check whether matchmaking data is in proper format
|
||||
bool IsMatchmakingDataValid();
|
||||
|
||||
//! Fetch active player sessions in game session.
|
||||
AZStd::vector<Aws::GameLift::Server::Model::PlayerSession> GetActivePlayerSessions();
|
||||
|
||||
//! Callback function that the GameLift service invokes to activate a new game session.
|
||||
void OnStartGameSession(const Aws::GameLift::Server::Model::GameSession& gameSession);
|
||||
|
||||
//! Callback function that the GameLift service invokes to pass an updated game session object to the server process.
|
||||
void OnUpdateGameSession();
|
||||
void OnUpdateGameSession(const Aws::GameLift::Server::Model::UpdateGameSession& updateGameSession);
|
||||
|
||||
//! Callback function that the server process or GameLift service invokes to force the server process to shut down.
|
||||
void OnProcessTerminate();
|
||||
@@ -125,5 +191,12 @@ namespace AWSGameLift
|
||||
using PlayerConnectionId = uint32_t;
|
||||
using PlayerSessionId = AZStd::string;
|
||||
AZStd::unordered_map<PlayerConnectionId, PlayerSessionId> m_connectedPlayers;
|
||||
|
||||
// Lazy loaded game session and matchmaking data
|
||||
Aws::GameLift::Server::Model::GameSession m_gameSession;
|
||||
// Matchmaking data contains a unique match ID, it identifies the matchmaker that created the match
|
||||
// and describes the teams, team assignments, and players.
|
||||
// Reference https://docs.aws.amazon.com/gamelift/latest/flexmatchguide/match-server.html#match-server-data
|
||||
rapidjson::Document m_matchmakingData;
|
||||
};
|
||||
} // namespace AWSGameLift
|
||||
|
||||
@@ -22,6 +22,12 @@ namespace AWSGameLift
|
||||
return Aws::GameLift::Server::ActivateGameSession();
|
||||
}
|
||||
|
||||
Aws::GameLift::DescribePlayerSessionsOutcome GameLiftServerSDKWrapper::DescribePlayerSessions(
|
||||
const Aws::GameLift::Server::Model::DescribePlayerSessionsRequest& describePlayerSessionsRequest)
|
||||
{
|
||||
return Aws::GameLift::Server::DescribePlayerSessions(describePlayerSessionsRequest);
|
||||
}
|
||||
|
||||
Aws::GameLift::Server::InitSDKOutcome GameLiftServerSDKWrapper::InitSDK()
|
||||
{
|
||||
return Aws::GameLift::Server::InitSDK();
|
||||
@@ -69,4 +75,17 @@ namespace AWSGameLift
|
||||
{
|
||||
return Aws::GameLift::Server::RemovePlayerSession(playerSessionId.c_str());
|
||||
}
|
||||
|
||||
Aws::GameLift::StartMatchBackfillOutcome GameLiftServerSDKWrapper::StartMatchBackfill(
|
||||
const Aws::GameLift::Server::Model::StartMatchBackfillRequest& startMatchBackfillRequest)
|
||||
{
|
||||
return Aws::GameLift::Server::StartMatchBackfill(startMatchBackfillRequest);
|
||||
}
|
||||
|
||||
Aws::GameLift::GenericOutcome GameLiftServerSDKWrapper::StopMatchBackfill(
|
||||
const Aws::GameLift::Server::Model::StopMatchBackfillRequest& stopMatchBackfillRequest)
|
||||
{
|
||||
return Aws::GameLift::Server::StopMatchBackfill(stopMatchBackfillRequest);
|
||||
}
|
||||
|
||||
} // namespace AWSGameLift
|
||||
|
||||
@@ -33,6 +33,14 @@ namespace AWSGameLift
|
||||
//! @return Returns a generic outcome consisting of success or failure with an error message.
|
||||
virtual Aws::GameLift::GenericOutcome ActivateGameSession();
|
||||
|
||||
//! Retrieves player session data, including settings, session metadata, and player data.
|
||||
//! Use this action to get information for a single player session,
|
||||
//! for all player sessions in a game session, or for all player sessions associated with a single player ID.
|
||||
//! @param describePlayerSessionsRequest The request object describing which player sessions to retrieve.
|
||||
//! @return If successful, returns a DescribePlayerSessionsOutcome object containing a set of player session objects that fit the request parameters.
|
||||
virtual Aws::GameLift::DescribePlayerSessionsOutcome DescribePlayerSessions(
|
||||
const Aws::GameLift::Server::Model::DescribePlayerSessionsRequest& describePlayerSessionsRequest);
|
||||
|
||||
//! Initializes the GameLift SDK.
|
||||
//! Should be called when the server starts, before any GameLift-dependent initialization happens.
|
||||
//! @return If successful, returns an InitSdkOutcome object indicating that the server process is ready to call ProcessReady().
|
||||
@@ -56,5 +64,16 @@ namespace AWSGameLift
|
||||
//! @param playerSessionId Unique ID issued by the Amazon GameLift service in response to a call to the AWS SDK Amazon GameLift API action CreatePlayerSession.
|
||||
//! @return Returns a generic outcome consisting of success or failure with an error message.
|
||||
virtual Aws::GameLift::GenericOutcome RemovePlayerSession(const AZStd::string& playerSessionId);
|
||||
|
||||
//! Sends a request to find new players for open slots in a game session created with FlexMatch.
|
||||
//! When the match has been successfully, backfilled updated matchmaker data will be sent to the OnUpdateGameSession callback.
|
||||
//! @param startMatchBackfillRequest This data type is used to send a matchmaking backfill request.
|
||||
//! @return Returns a StartMatchBackfillOutcome object with the match backfill ticket or failure with an error message.
|
||||
virtual Aws::GameLift::StartMatchBackfillOutcome StartMatchBackfill(const Aws::GameLift::Server::Model::StartMatchBackfillRequest& startMatchBackfillRequest);
|
||||
|
||||
//! Cancels an active match backfill request that was created with StartMatchBackfill
|
||||
//! @param stopMatchBackfillRequest This data type is used to cancel a matchmaking backfill request.
|
||||
//! @return Returns a generic outcome consisting of success or failure with an error message.
|
||||
virtual Aws::GameLift::GenericOutcome StopMatchBackfill(const Aws::GameLift::Server::Model::StopMatchBackfillRequest& stopMatchBackfillRequest);
|
||||
};
|
||||
} // namespace AWSGameLift
|
||||
|
||||
@@ -16,6 +16,136 @@
|
||||
|
||||
namespace UnitTest
|
||||
{
|
||||
static constexpr const char TEST_SERVER_MATCHMAKING_DATA[] =
|
||||
R"({
|
||||
"matchId":"testmatchid",
|
||||
"matchmakingConfigurationArn":"testmatchconfig",
|
||||
"teams":[
|
||||
{"name":"testteam",
|
||||
"players":[
|
||||
{"playerId":"testplayer",
|
||||
"attributes":{
|
||||
"skills":{
|
||||
"attributeType":"STRING_DOUBLE_MAP",
|
||||
"valueAttribute":{"test1":10.0,"test2":20.0,"test3":30.0,"test4":40.0}
|
||||
},
|
||||
"mode":{
|
||||
"attributeType":"STRING",
|
||||
"valueAttribute":"testmode"
|
||||
},
|
||||
"level":{
|
||||
"attributeType":"NUMBER",
|
||||
"valueAttribute":10.0
|
||||
},
|
||||
"items":{
|
||||
"attributeType":"STRING_LIST",
|
||||
"valueAttribute":["test1","test2","test3"]
|
||||
}
|
||||
}}
|
||||
]}
|
||||
]
|
||||
})";
|
||||
|
||||
Aws::GameLift::Server::Model::StartMatchBackfillRequest GetTestStartMatchBackfillRequest()
|
||||
{
|
||||
Aws::GameLift::Server::Model::StartMatchBackfillRequest request;
|
||||
request.SetMatchmakingConfigurationArn("testmatchconfig");
|
||||
Aws::GameLift::Server::Model::Player player;
|
||||
player.SetPlayerId("testplayer");
|
||||
player.SetTeam("testteam");
|
||||
player.AddPlayerAttribute("mode", Aws::GameLift::Server::Model::AttributeValue("testmode"));
|
||||
player.AddPlayerAttribute("level", Aws::GameLift::Server::Model::AttributeValue(10.0));
|
||||
auto sdmValue = Aws::GameLift::Server::Model::AttributeValue::ConstructStringDoubleMap();
|
||||
sdmValue.AddStringAndDouble("test1", 10.0);
|
||||
player.AddPlayerAttribute("skills", sdmValue);
|
||||
auto slValue = Aws::GameLift::Server::Model::AttributeValue::ConstructStringList();
|
||||
slValue.AddString("test1");
|
||||
player.AddPlayerAttribute("items", slValue);
|
||||
player.AddLatencyInMs("testregion", 10);
|
||||
request.AddPlayer(player);
|
||||
request.SetTicketId("testticket");
|
||||
return request;
|
||||
}
|
||||
|
||||
AWSGameLiftPlayer GetTestGameLiftPlayer()
|
||||
{
|
||||
AWSGameLiftPlayer player;
|
||||
player.m_team = "testteam";
|
||||
player.m_playerId = "testplayer";
|
||||
player.m_playerAttributes.emplace("mode", "{\"S\": \"testmode\"}");
|
||||
player.m_playerAttributes.emplace("level", "{\"N\": 10.0}");
|
||||
player.m_playerAttributes.emplace("skills", "{\"SDM\": {\"test1\":10.0}}");
|
||||
player.m_playerAttributes.emplace("items", "{\"SL\": [\"test1\"]}");
|
||||
player.m_latencyInMs.emplace("testregion", 10);
|
||||
return player;
|
||||
}
|
||||
|
||||
MATCHER_P(StartMatchBackfillRequestMatcher, expectedRequest, "")
|
||||
{
|
||||
// Custome matcher for checking the SearchSessionsResponse type argument.
|
||||
AZ_UNUSED(result_listener);
|
||||
if (strcmp(arg.GetGameSessionArn().c_str(), expectedRequest.GetGameSessionArn().c_str()) != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (strcmp(arg.GetMatchmakingConfigurationArn().c_str(), expectedRequest.GetMatchmakingConfigurationArn().c_str()) != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (strcmp(arg.GetTicketId().c_str(), expectedRequest.GetTicketId().c_str()) != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (arg.GetPlayers().size() != expectedRequest.GetPlayers().size())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
for (int playerIndex = 0; playerIndex < expectedRequest.GetPlayers().size(); playerIndex++)
|
||||
{
|
||||
auto actualPlayerAttributes = arg.GetPlayers()[playerIndex].GetPlayerAttributes();
|
||||
auto expectedPlayerAttributes = expectedRequest.GetPlayers()[playerIndex].GetPlayerAttributes();
|
||||
if (actualPlayerAttributes.size() != expectedPlayerAttributes.size())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
for (auto attributePair : expectedPlayerAttributes)
|
||||
{
|
||||
if (actualPlayerAttributes.find(attributePair.first) == actualPlayerAttributes.end())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!(attributePair.second.GetType() == actualPlayerAttributes[attributePair.first].GetType() &&
|
||||
(attributePair.second.GetS() == actualPlayerAttributes[attributePair.first].GetS() ||
|
||||
attributePair.second.GetN() == actualPlayerAttributes[attributePair.first].GetN() ||
|
||||
attributePair.second.GetSL() == actualPlayerAttributes[attributePair.first].GetSL() ||
|
||||
attributePair.second.GetSDM() == actualPlayerAttributes[attributePair.first].GetSDM())))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
auto actualLatencies = arg.GetPlayers()[playerIndex].GetLatencyInMs();
|
||||
auto expectedLatencies = expectedRequest.GetPlayers()[playerIndex].GetLatencyInMs();
|
||||
if (actualLatencies.size() != expectedLatencies.size())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
for (auto latencyPair : expectedLatencies)
|
||||
{
|
||||
if (actualLatencies.find(latencyPair.first) == actualLatencies.end())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (latencyPair.second != actualLatencies[latencyPair.first])
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
class SessionNotificationsHandlerMock
|
||||
: public AzFramework::SessionNotificationBus::Handler
|
||||
{
|
||||
@@ -33,6 +163,7 @@ namespace UnitTest
|
||||
MOCK_METHOD0(OnSessionHealthCheck, bool());
|
||||
MOCK_METHOD1(OnCreateSessionBegin, bool(const AzFramework::SessionConfig&));
|
||||
MOCK_METHOD0(OnDestroySessionBegin, bool());
|
||||
MOCK_METHOD2(OnUpdateSessionBegin, void(const AzFramework::SessionConfig&, const AZStd::string&));
|
||||
};
|
||||
|
||||
class GameLiftServerManagerTest
|
||||
@@ -228,6 +359,64 @@ namespace UnitTest
|
||||
AZ_TEST_STOP_TRACE_SUPPRESSION(1);
|
||||
}
|
||||
|
||||
TEST_F(GameLiftServerManagerTest, OnUpdateGameSession_TriggerWithUnknownReason_OnUpdateSessionBeginGetCalledOnce)
|
||||
{
|
||||
m_serverManager->InitializeGameLiftServerSDK();
|
||||
m_serverManager->NotifyGameLiftProcessReady();
|
||||
SessionNotificationsHandlerMock handlerMock;
|
||||
EXPECT_CALL(handlerMock, OnUpdateSessionBegin(testing::_, testing::_)).Times(1);
|
||||
|
||||
m_serverManager->m_gameLiftServerSDKWrapperMockPtr->m_onUpdateGameSessionFunc(
|
||||
Aws::GameLift::Server::Model::UpdateGameSession(
|
||||
Aws::GameLift::Server::Model::GameSession(),
|
||||
Aws::GameLift::Server::Model::UpdateReason::UNKNOWN,
|
||||
"testticket"));
|
||||
}
|
||||
|
||||
TEST_F(GameLiftServerManagerTest, OnUpdateGameSession_TriggerWithEmptyMatchmakingData_OnUpdateSessionBeginGetCalledOnce)
|
||||
{
|
||||
m_serverManager->InitializeGameLiftServerSDK();
|
||||
m_serverManager->NotifyGameLiftProcessReady();
|
||||
SessionNotificationsHandlerMock handlerMock;
|
||||
EXPECT_CALL(handlerMock, OnUpdateSessionBegin(testing::_, testing::_)).Times(1);
|
||||
|
||||
m_serverManager->m_gameLiftServerSDKWrapperMockPtr->m_onUpdateGameSessionFunc(
|
||||
Aws::GameLift::Server::Model::UpdateGameSession(
|
||||
Aws::GameLift::Server::Model::GameSession(),
|
||||
Aws::GameLift::Server::Model::UpdateReason::MATCHMAKING_DATA_UPDATED,
|
||||
"testticket"));
|
||||
}
|
||||
|
||||
TEST_F(GameLiftServerManagerTest, OnUpdateGameSession_TriggerWithValidMatchmakingData_OnUpdateSessionBeginGetCalledOnce)
|
||||
{
|
||||
m_serverManager->InitializeGameLiftServerSDK();
|
||||
m_serverManager->NotifyGameLiftProcessReady();
|
||||
SessionNotificationsHandlerMock handlerMock;
|
||||
EXPECT_CALL(handlerMock, OnUpdateSessionBegin(testing::_, testing::_)).Times(1);
|
||||
|
||||
Aws::GameLift::Server::Model::GameSession gameSession;
|
||||
gameSession.SetMatchmakerData(TEST_SERVER_MATCHMAKING_DATA);
|
||||
m_serverManager->m_gameLiftServerSDKWrapperMockPtr->m_onUpdateGameSessionFunc(
|
||||
Aws::GameLift::Server::Model::UpdateGameSession(
|
||||
gameSession, Aws::GameLift::Server::Model::UpdateReason::MATCHMAKING_DATA_UPDATED, "testticket"));
|
||||
}
|
||||
|
||||
TEST_F(GameLiftServerManagerTest, OnUpdateGameSession_TriggerWithInvalidMatchmakingData_OnUpdateSessionBeginGetCalledOnce)
|
||||
{
|
||||
m_serverManager->InitializeGameLiftServerSDK();
|
||||
m_serverManager->NotifyGameLiftProcessReady();
|
||||
SessionNotificationsHandlerMock handlerMock;
|
||||
EXPECT_CALL(handlerMock, OnUpdateSessionBegin(testing::_, testing::_)).Times(1);
|
||||
|
||||
Aws::GameLift::Server::Model::GameSession gameSession;
|
||||
gameSession.SetMatchmakerData("{invalid}");
|
||||
AZ_TEST_START_TRACE_SUPPRESSION;
|
||||
m_serverManager->m_gameLiftServerSDKWrapperMockPtr->m_onUpdateGameSessionFunc(
|
||||
Aws::GameLift::Server::Model::UpdateGameSession(
|
||||
gameSession, Aws::GameLift::Server::Model::UpdateReason::MATCHMAKING_DATA_UPDATED, "testticket"));
|
||||
AZ_TEST_STOP_TRACE_SUPPRESSION(1);
|
||||
}
|
||||
|
||||
TEST_F(GameLiftServerManagerTest, ValidatePlayerJoinSession_CallWithInvalidConnectionConfig_GetFalseResultAndExpectedErrorLog)
|
||||
{
|
||||
AZ_TEST_START_TRACE_SUPPRESSION;
|
||||
@@ -425,4 +614,331 @@ namespace UnitTest
|
||||
}
|
||||
AZ_TEST_STOP_TRACE_SUPPRESSION(testThreadNumber - 1); // The player is only disconnected once.
|
||||
}
|
||||
|
||||
TEST_F(GameLiftServerManagerTest, UpdateGameSessionData_CallWithInvalidMatchmakingData_GetExpectedError)
|
||||
{
|
||||
AZ_TEST_START_TRACE_SUPPRESSION;
|
||||
m_serverManager->SetupTestMatchmakingData("{invalid}");
|
||||
AZ_TEST_STOP_TRACE_SUPPRESSION(1);
|
||||
}
|
||||
|
||||
TEST_F(GameLiftServerManagerTest, GetActiveServerMatchBackfillPlayers_CallWithInvalidMatchmakingData_GetEmptyResult)
|
||||
{
|
||||
AZ_TEST_START_TRACE_SUPPRESSION;
|
||||
m_serverManager->SetupTestMatchmakingData("{invalid}");
|
||||
AZ_TEST_STOP_TRACE_SUPPRESSION(1);
|
||||
|
||||
auto actualResult = m_serverManager->GetTestServerMatchBackfillPlayers();
|
||||
EXPECT_TRUE(actualResult.empty());
|
||||
}
|
||||
|
||||
TEST_F(GameLiftServerManagerTest, GetActiveServerMatchBackfillPlayers_CallWithEmptyMatchmakingData_GetEmptyResult)
|
||||
{
|
||||
m_serverManager->SetupTestMatchmakingData("");
|
||||
|
||||
auto actualResult = m_serverManager->GetTestServerMatchBackfillPlayers();
|
||||
EXPECT_TRUE(actualResult.empty());
|
||||
}
|
||||
|
||||
TEST_F(GameLiftServerManagerTest, GetActiveServerMatchBackfillPlayers_CallButDescribePlayerError_GetEmptyResult)
|
||||
{
|
||||
m_serverManager->SetupTestMatchmakingData(TEST_SERVER_MATCHMAKING_DATA);
|
||||
|
||||
Aws::GameLift::GameLiftError error;
|
||||
Aws::GameLift::DescribePlayerSessionsOutcome errorOutcome(error);
|
||||
EXPECT_CALL(*(m_serverManager->m_gameLiftServerSDKWrapperMockPtr), DescribePlayerSessions(testing::_))
|
||||
.Times(1)
|
||||
.WillOnce(Return(errorOutcome));
|
||||
|
||||
AZ_TEST_START_TRACE_SUPPRESSION;
|
||||
auto actualResult = m_serverManager->GetTestServerMatchBackfillPlayers();
|
||||
AZ_TEST_STOP_TRACE_SUPPRESSION(1);
|
||||
EXPECT_TRUE(actualResult.empty());
|
||||
}
|
||||
|
||||
TEST_F(GameLiftServerManagerTest, GetActiveServerMatchBackfillPlayers_CallButNoActivePlayer_GetEmptyResult)
|
||||
{
|
||||
m_serverManager->SetupTestMatchmakingData(TEST_SERVER_MATCHMAKING_DATA);
|
||||
|
||||
Aws::GameLift::Server::Model::DescribePlayerSessionsResult result;
|
||||
Aws::GameLift::DescribePlayerSessionsOutcome successOutcome(result);
|
||||
EXPECT_CALL(*(m_serverManager->m_gameLiftServerSDKWrapperMockPtr), DescribePlayerSessions(testing::_))
|
||||
.Times(1)
|
||||
.WillOnce(Return(successOutcome));
|
||||
|
||||
auto actualResult = m_serverManager->GetTestServerMatchBackfillPlayers();
|
||||
EXPECT_TRUE(actualResult.empty());
|
||||
}
|
||||
|
||||
TEST_F(GameLiftServerManagerTest, GetActiveServerMatchBackfillPlayers_CallWithValidMatchmakingData_GetExpectedResult)
|
||||
{
|
||||
m_serverManager->SetupTestMatchmakingData(TEST_SERVER_MATCHMAKING_DATA);
|
||||
|
||||
Aws::GameLift::Server::Model::PlayerSession playerSession;
|
||||
playerSession.SetPlayerId("testplayer");
|
||||
Aws::GameLift::Server::Model::DescribePlayerSessionsResult result;
|
||||
result.AddPlayerSessions(playerSession);
|
||||
Aws::GameLift::DescribePlayerSessionsOutcome successOutcome(result);
|
||||
EXPECT_CALL(*(m_serverManager->m_gameLiftServerSDKWrapperMockPtr), DescribePlayerSessions(testing::_))
|
||||
.Times(1)
|
||||
.WillOnce(Return(successOutcome));
|
||||
|
||||
auto actualResult = m_serverManager->GetTestServerMatchBackfillPlayers();
|
||||
EXPECT_TRUE(actualResult.size() == 1);
|
||||
EXPECT_TRUE(actualResult[0].m_team == "testteam");
|
||||
EXPECT_TRUE(actualResult[0].m_playerId == "testplayer");
|
||||
EXPECT_TRUE(actualResult[0].m_playerAttributes.size() == 4);
|
||||
}
|
||||
|
||||
TEST_F(GameLiftServerManagerTest, GetActiveServerMatchBackfillPlayers_CallWithMultiDescribePlayerButError_GetEmptyResult)
|
||||
{
|
||||
m_serverManager->SetupTestMatchmakingData(TEST_SERVER_MATCHMAKING_DATA, 50);
|
||||
|
||||
Aws::GameLift::GameLiftError error;
|
||||
Aws::GameLift::DescribePlayerSessionsOutcome errorOutcome(error);
|
||||
EXPECT_CALL(*(m_serverManager->m_gameLiftServerSDKWrapperMockPtr), DescribePlayerSessions(testing::_))
|
||||
.Times(1)
|
||||
.WillOnce(Return(errorOutcome));
|
||||
|
||||
AZ_TEST_START_TRACE_SUPPRESSION;
|
||||
auto actualResult = m_serverManager->GetTestServerMatchBackfillPlayers();
|
||||
AZ_TEST_STOP_TRACE_SUPPRESSION(1);
|
||||
EXPECT_TRUE(actualResult.empty());
|
||||
}
|
||||
|
||||
TEST_F(GameLiftServerManagerTest, GetActiveServerMatchBackfillPlayers_CallWithMultiDescribePlayer_GetExpectedResult)
|
||||
{
|
||||
m_serverManager->SetupTestMatchmakingData(TEST_SERVER_MATCHMAKING_DATA, 50);
|
||||
|
||||
Aws::GameLift::Server::Model::PlayerSession playerSession1;
|
||||
playerSession1.SetPlayerId("testplayer");
|
||||
Aws::GameLift::Server::Model::DescribePlayerSessionsResult result1;
|
||||
result1.AddPlayerSessions(playerSession1);
|
||||
result1.SetNextToken("testtoken");
|
||||
Aws::GameLift::DescribePlayerSessionsOutcome successOutcome1(result1);
|
||||
|
||||
Aws::GameLift::Server::Model::PlayerSession playerSession2;
|
||||
playerSession2.SetPlayerId("playernotinmatch");
|
||||
Aws::GameLift::Server::Model::DescribePlayerSessionsResult result2;
|
||||
result2.AddPlayerSessions(playerSession2);
|
||||
Aws::GameLift::DescribePlayerSessionsOutcome successOutcome2(result2);
|
||||
EXPECT_CALL(*(m_serverManager->m_gameLiftServerSDKWrapperMockPtr), DescribePlayerSessions(testing::_))
|
||||
.WillOnce(Return(successOutcome1))
|
||||
.WillOnce(Return(successOutcome2));
|
||||
|
||||
auto actualResult = m_serverManager->GetTestServerMatchBackfillPlayers();
|
||||
EXPECT_TRUE(actualResult.size() == 1);
|
||||
EXPECT_TRUE(actualResult[0].m_team == "testteam");
|
||||
EXPECT_TRUE(actualResult[0].m_playerId == "testplayer");
|
||||
EXPECT_TRUE(actualResult[0].m_playerAttributes.size() == 4);
|
||||
}
|
||||
|
||||
TEST_F(GameLiftServerManagerTest, StartMatchBackfill_SDKNotInitialized_GetExpectedError)
|
||||
{
|
||||
AZ_TEST_START_TRACE_SUPPRESSION;
|
||||
auto actualResult = m_serverManager->StartMatchBackfill("testticket", {});
|
||||
AZ_TEST_STOP_TRACE_SUPPRESSION(1);
|
||||
EXPECT_FALSE(actualResult);
|
||||
}
|
||||
|
||||
TEST_F(GameLiftServerManagerTest, StartMatchBackfill_CallWithEmptyMatchmakingData_GetExpectedError)
|
||||
{
|
||||
m_serverManager->InitializeGameLiftServerSDK();
|
||||
m_serverManager->SetupTestMatchmakingData("");
|
||||
|
||||
AZ_TEST_START_TRACE_SUPPRESSION;
|
||||
auto actualResult = m_serverManager->StartMatchBackfill("testticket", {});
|
||||
AZ_TEST_STOP_TRACE_SUPPRESSION(1);
|
||||
EXPECT_FALSE(actualResult);
|
||||
}
|
||||
|
||||
TEST_F(GameLiftServerManagerTest, StartMatchBackfill_CallWithInvalidPlayerAttribute_GetExpectedError)
|
||||
{
|
||||
m_serverManager->InitializeGameLiftServerSDK();
|
||||
m_serverManager->SetupTestMatchmakingData(TEST_SERVER_MATCHMAKING_DATA);
|
||||
|
||||
AWSGameLiftPlayer testPlayer = GetTestGameLiftPlayer();
|
||||
testPlayer.m_playerAttributes.clear();
|
||||
testPlayer.m_playerAttributes.emplace("invalidattribute", "{invalid}");
|
||||
|
||||
AZ_TEST_START_TRACE_SUPPRESSION;
|
||||
auto actualResult = m_serverManager->StartMatchBackfill("testticket", { testPlayer });
|
||||
AZ_TEST_STOP_TRACE_SUPPRESSION(1);
|
||||
EXPECT_FALSE(actualResult);
|
||||
}
|
||||
|
||||
TEST_F(GameLiftServerManagerTest, StartMatchBackfill_CallWithWrongPlayerAttributeType_GetExpectedError)
|
||||
{
|
||||
m_serverManager->InitializeGameLiftServerSDK();
|
||||
m_serverManager->SetupTestMatchmakingData(TEST_SERVER_MATCHMAKING_DATA);
|
||||
|
||||
AWSGameLiftPlayer testPlayer = GetTestGameLiftPlayer();
|
||||
testPlayer.m_playerAttributes.clear();
|
||||
testPlayer.m_playerAttributes.emplace("invalidattribute", "{\"SDM\": [\"test1\"]}");
|
||||
|
||||
AZ_TEST_START_TRACE_SUPPRESSION;
|
||||
auto actualResult = m_serverManager->StartMatchBackfill("testticket", { testPlayer });
|
||||
AZ_TEST_STOP_TRACE_SUPPRESSION(1);
|
||||
EXPECT_FALSE(actualResult);
|
||||
}
|
||||
|
||||
TEST_F(GameLiftServerManagerTest, StartMatchBackfill_CallWithUnexpectedPlayerAttributeType_GetExpectedError)
|
||||
{
|
||||
m_serverManager->InitializeGameLiftServerSDK();
|
||||
m_serverManager->SetupTestMatchmakingData(TEST_SERVER_MATCHMAKING_DATA);
|
||||
|
||||
AWSGameLiftPlayer testPlayer = GetTestGameLiftPlayer();
|
||||
testPlayer.m_playerAttributes.clear();
|
||||
testPlayer.m_playerAttributes.emplace("invalidattribute", "{\"UNEXPECTED\": [\"test1\"]}");
|
||||
|
||||
AZ_TEST_START_TRACE_SUPPRESSION;
|
||||
auto actualResult = m_serverManager->StartMatchBackfill("testticket", { testPlayer });
|
||||
AZ_TEST_STOP_TRACE_SUPPRESSION(1);
|
||||
EXPECT_FALSE(actualResult);
|
||||
}
|
||||
|
||||
TEST_F(GameLiftServerManagerTest, StartMatchBackfill_CallWithWrongSLPlayerAttributeValue_GetExpectedError)
|
||||
{
|
||||
m_serverManager->InitializeGameLiftServerSDK();
|
||||
m_serverManager->SetupTestMatchmakingData(TEST_SERVER_MATCHMAKING_DATA);
|
||||
|
||||
AWSGameLiftPlayer testPlayer = GetTestGameLiftPlayer();
|
||||
testPlayer.m_playerAttributes.clear();
|
||||
testPlayer.m_playerAttributes.emplace("invalidattribute", "{\"SL\": [10.0]}");
|
||||
|
||||
AZ_TEST_START_TRACE_SUPPRESSION;
|
||||
auto actualResult = m_serverManager->StartMatchBackfill("testticket", { testPlayer });
|
||||
AZ_TEST_STOP_TRACE_SUPPRESSION(1);
|
||||
EXPECT_FALSE(actualResult);
|
||||
}
|
||||
|
||||
TEST_F(GameLiftServerManagerTest, StartMatchBackfill_CallWithWrongSDMPlayerAttributeValue_GetExpectedError)
|
||||
{
|
||||
m_serverManager->InitializeGameLiftServerSDK();
|
||||
m_serverManager->SetupTestMatchmakingData(TEST_SERVER_MATCHMAKING_DATA);
|
||||
|
||||
AWSGameLiftPlayer testPlayer = GetTestGameLiftPlayer();
|
||||
testPlayer.m_playerAttributes.clear();
|
||||
testPlayer.m_playerAttributes.emplace("invalidattribute", "{\"SDM\": {10.0: \"test1\"}}");
|
||||
|
||||
AZ_TEST_START_TRACE_SUPPRESSION;
|
||||
auto actualResult = m_serverManager->StartMatchBackfill("testticket", { testPlayer });
|
||||
AZ_TEST_STOP_TRACE_SUPPRESSION(1);
|
||||
EXPECT_FALSE(actualResult);
|
||||
}
|
||||
|
||||
TEST_F(GameLiftServerManagerTest, StartMatchBackfill_CallWithValidPlayersData_GetExpectedResult)
|
||||
{
|
||||
m_serverManager->InitializeGameLiftServerSDK();
|
||||
m_serverManager->SetupTestMatchmakingData(TEST_SERVER_MATCHMAKING_DATA);
|
||||
|
||||
Aws::GameLift::Server::Model::StartMatchBackfillResult backfillResult;
|
||||
Aws::GameLift::StartMatchBackfillOutcome backfillSuccessOutcome(backfillResult);
|
||||
Aws::GameLift::Server::Model::StartMatchBackfillRequest request = GetTestStartMatchBackfillRequest();
|
||||
|
||||
EXPECT_CALL(*(m_serverManager->m_gameLiftServerSDKWrapperMockPtr), StartMatchBackfill(StartMatchBackfillRequestMatcher(request)))
|
||||
.Times(1)
|
||||
.WillOnce(Return(backfillSuccessOutcome));
|
||||
|
||||
AWSGameLiftPlayer testPlayer = GetTestGameLiftPlayer();
|
||||
auto actualResult = m_serverManager->StartMatchBackfill("testticket", {testPlayer});
|
||||
EXPECT_TRUE(actualResult);
|
||||
}
|
||||
|
||||
TEST_F(GameLiftServerManagerTest, StartMatchBackfill_CallWithoutGivingPlayersData_GetExpectedResult)
|
||||
{
|
||||
m_serverManager->InitializeGameLiftServerSDK();
|
||||
m_serverManager->SetupTestMatchmakingData(TEST_SERVER_MATCHMAKING_DATA);
|
||||
|
||||
Aws::GameLift::Server::Model::PlayerSession playerSession;
|
||||
playerSession.SetPlayerId("testplayer");
|
||||
Aws::GameLift::Server::Model::DescribePlayerSessionsResult result;
|
||||
result.AddPlayerSessions(playerSession);
|
||||
Aws::GameLift::DescribePlayerSessionsOutcome successOutcome(result);
|
||||
EXPECT_CALL(*(m_serverManager->m_gameLiftServerSDKWrapperMockPtr), DescribePlayerSessions(testing::_))
|
||||
.Times(1)
|
||||
.WillOnce(Return(successOutcome));
|
||||
|
||||
Aws::GameLift::Server::Model::StartMatchBackfillResult backfillResult;
|
||||
Aws::GameLift::StartMatchBackfillOutcome backfillSuccessOutcome(backfillResult);
|
||||
EXPECT_CALL(*(m_serverManager->m_gameLiftServerSDKWrapperMockPtr), StartMatchBackfill(testing::_))
|
||||
.Times(1)
|
||||
.WillOnce(Return(backfillSuccessOutcome));
|
||||
|
||||
auto actualResult = m_serverManager->StartMatchBackfill("testticket", {});
|
||||
EXPECT_TRUE(actualResult);
|
||||
}
|
||||
|
||||
TEST_F(GameLiftServerManagerTest, StartMatchBackfill_CallButStartBackfillFail_GetExpectedError)
|
||||
{
|
||||
m_serverManager->InitializeGameLiftServerSDK();
|
||||
m_serverManager->SetupTestMatchmakingData(TEST_SERVER_MATCHMAKING_DATA);
|
||||
|
||||
Aws::GameLift::Server::Model::PlayerSession playerSession;
|
||||
playerSession.SetPlayerId("testplayer");
|
||||
Aws::GameLift::Server::Model::DescribePlayerSessionsResult result;
|
||||
result.AddPlayerSessions(playerSession);
|
||||
Aws::GameLift::DescribePlayerSessionsOutcome successOutcome(result);
|
||||
EXPECT_CALL(*(m_serverManager->m_gameLiftServerSDKWrapperMockPtr), DescribePlayerSessions(testing::_))
|
||||
.Times(1)
|
||||
.WillOnce(Return(successOutcome));
|
||||
|
||||
Aws::GameLift::GameLiftError error;
|
||||
Aws::GameLift::StartMatchBackfillOutcome errorOutcome(error);
|
||||
EXPECT_CALL(*(m_serverManager->m_gameLiftServerSDKWrapperMockPtr), StartMatchBackfill(testing::_))
|
||||
.Times(1)
|
||||
.WillOnce(Return(errorOutcome));
|
||||
|
||||
AZ_TEST_START_TRACE_SUPPRESSION;
|
||||
auto actualResult = m_serverManager->StartMatchBackfill("testticket", {});
|
||||
AZ_TEST_STOP_TRACE_SUPPRESSION(1);
|
||||
EXPECT_FALSE(actualResult);
|
||||
}
|
||||
|
||||
TEST_F(GameLiftServerManagerTest, StopMatchBackfill_SDKNotInitialized_GetExpectedError)
|
||||
{
|
||||
AZ_TEST_START_TRACE_SUPPRESSION;
|
||||
auto actualResult = m_serverManager->StopMatchBackfill("testticket");
|
||||
AZ_TEST_STOP_TRACE_SUPPRESSION(1);
|
||||
EXPECT_FALSE(actualResult);
|
||||
}
|
||||
|
||||
TEST_F(GameLiftServerManagerTest, StopMatchBackfill_CallWithEmptyMatchmakingData_GetExpectedError)
|
||||
{
|
||||
m_serverManager->InitializeGameLiftServerSDK();
|
||||
m_serverManager->SetupTestMatchmakingData("");
|
||||
|
||||
AZ_TEST_START_TRACE_SUPPRESSION;
|
||||
auto actualResult = m_serverManager->StopMatchBackfill("testticket");
|
||||
AZ_TEST_STOP_TRACE_SUPPRESSION(1);
|
||||
EXPECT_FALSE(actualResult);
|
||||
}
|
||||
|
||||
TEST_F(GameLiftServerManagerTest, StopMatchBackfill_CallAndSuccessOutcome_GetExpectedResult)
|
||||
{
|
||||
m_serverManager->InitializeGameLiftServerSDK();
|
||||
m_serverManager->SetupTestMatchmakingData(TEST_SERVER_MATCHMAKING_DATA);
|
||||
|
||||
EXPECT_CALL(*(m_serverManager->m_gameLiftServerSDKWrapperMockPtr), StopMatchBackfill(testing::_))
|
||||
.Times(1)
|
||||
.WillOnce(Return(Aws::GameLift::GenericOutcome(nullptr)));
|
||||
|
||||
auto actualResult = m_serverManager->StopMatchBackfill("testticket");
|
||||
EXPECT_TRUE(actualResult);
|
||||
}
|
||||
|
||||
TEST_F(GameLiftServerManagerTest, StopMatchBackfill_CallButErrorOutcome_GetExpectedError)
|
||||
{
|
||||
m_serverManager->InitializeGameLiftServerSDK();
|
||||
m_serverManager->SetupTestMatchmakingData(TEST_SERVER_MATCHMAKING_DATA);
|
||||
|
||||
EXPECT_CALL(*(m_serverManager->m_gameLiftServerSDKWrapperMockPtr), StopMatchBackfill(testing::_))
|
||||
.Times(1)
|
||||
.WillOnce(Return(Aws::GameLift::GenericOutcome()));
|
||||
|
||||
AZ_TEST_START_TRACE_SUPPRESSION;
|
||||
auto actualResult = m_serverManager->StopMatchBackfill("testticket");
|
||||
AZ_TEST_STOP_TRACE_SUPPRESSION(1);
|
||||
EXPECT_FALSE(actualResult);
|
||||
}
|
||||
} // namespace UnitTest
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AWSGameLiftPlayer.h>
|
||||
#include <AWSGameLiftServerSystemComponent.h>
|
||||
#include <AWSGameLiftServerManager.h>
|
||||
#include <GameLiftServerSDKWrapper.h>
|
||||
@@ -40,17 +41,25 @@ namespace UnitTest
|
||||
|
||||
MOCK_METHOD1(AcceptPlayerSession, GenericOutcome(const std::string&));
|
||||
MOCK_METHOD0(ActivateGameSession, GenericOutcome());
|
||||
MOCK_METHOD1(DescribePlayerSessions, DescribePlayerSessionsOutcome(
|
||||
const Aws::GameLift::Server::Model::DescribePlayerSessionsRequest&));
|
||||
MOCK_METHOD0(InitSDK, Server::InitSDKOutcome());
|
||||
MOCK_METHOD1(ProcessReady, GenericOutcome(const Server::ProcessParameters& processParameters));
|
||||
MOCK_METHOD0(ProcessEnding, GenericOutcome());
|
||||
MOCK_METHOD1(RemovePlayerSession, GenericOutcome(const AZStd::string& playerSessionId));
|
||||
MOCK_METHOD0(GetTerminationTime, AZStd::string());
|
||||
MOCK_METHOD1(StartMatchBackfill, StartMatchBackfillOutcome(
|
||||
const Aws::GameLift::Server::Model::StartMatchBackfillRequest&));
|
||||
MOCK_METHOD1(StopMatchBackfill, GenericOutcome(
|
||||
const Aws::GameLift::Server::Model::StopMatchBackfillRequest&));
|
||||
|
||||
|
||||
GenericOutcome ProcessReadyMock(const Server::ProcessParameters& processParameters)
|
||||
{
|
||||
m_healthCheckFunc = processParameters.getOnHealthCheck();
|
||||
m_onStartGameSessionFunc = processParameters.getOnStartGameSession();
|
||||
m_onProcessTerminateFunc = processParameters.getOnProcessTerminate();
|
||||
m_onUpdateGameSessionFunc = processParameters.getOnUpdateGameSession();
|
||||
|
||||
GenericOutcome successOutcome(nullptr);
|
||||
return successOutcome;
|
||||
@@ -59,6 +68,7 @@ namespace UnitTest
|
||||
AZStd::function<bool()> m_healthCheckFunc;
|
||||
AZStd::function<void()> m_onProcessTerminateFunc;
|
||||
AZStd::function<void(Aws::GameLift::Server::Model::GameSession)> m_onStartGameSessionFunc;
|
||||
AZStd::function<void(Aws::GameLift::Server::Model::UpdateGameSession)> m_onUpdateGameSessionFunc;
|
||||
};
|
||||
|
||||
class AWSGameLiftServerManagerMock
|
||||
@@ -78,12 +88,25 @@ namespace UnitTest
|
||||
m_gameLiftServerSDKWrapperMockPtr = nullptr;
|
||||
}
|
||||
|
||||
void SetupTestMatchmakingData(const AZStd::string& matchmakingData, int maxPlayer = 10)
|
||||
{
|
||||
m_testGameSession.SetMatchmakerData(matchmakingData.c_str());
|
||||
m_testGameSession.SetMaximumPlayerSessionCount(maxPlayer);
|
||||
UpdateGameSessionData(m_testGameSession);
|
||||
}
|
||||
|
||||
bool AddConnectedTestPlayer(const AzFramework::PlayerConnectionConfig& playerConnectionConfig)
|
||||
{
|
||||
return AddConnectedPlayer(playerConnectionConfig);
|
||||
}
|
||||
|
||||
AZStd::vector<AWSGameLiftPlayer> GetTestServerMatchBackfillPlayers()
|
||||
{
|
||||
return GetActiveServerMatchBackfillPlayers();
|
||||
}
|
||||
|
||||
NiceMock<GameLiftServerSDKWrapperMock>* m_gameLiftServerSDKWrapperMockPtr;
|
||||
Aws::GameLift::Server::Model::GameSession m_testGameSession;
|
||||
};
|
||||
|
||||
class AWSGameLiftServerSystemComponentMock
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
#
|
||||
|
||||
set(FILES
|
||||
../AWSGameLiftCommon/Include/AWSGameLiftPlayer.h
|
||||
../AWSGameLiftCommon/Source/AWSGameLiftPlayer.cpp
|
||||
../AWSGameLiftCommon/Source/AWSGameLiftSessionConstants.h
|
||||
Include/Request/IAWSGameLiftServerRequests.h
|
||||
Source/AWSGameLiftServerManager.cpp
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
#include "Skin_Common.azsli"
|
||||
|
||||
// SRGs
|
||||
#include <Atom/Features/PBR/DefaultObjectSrg.azsli>
|
||||
#include <Atom/Features/Skin/SkinObjectSrg.azsli>
|
||||
#include <Atom/Features/PBR/ForwardPassSrg.azsli>
|
||||
|
||||
// Pass Output
|
||||
|
||||
@@ -973,15 +973,15 @@
|
||||
"tag": "ForwardPass"
|
||||
},
|
||||
{
|
||||
"file": "Shaders/Shadow/Shadowmap.shader",
|
||||
"file": "Shaders/Shadow/ShadowmapSkin.shader",
|
||||
"tag": "Shadowmap"
|
||||
},
|
||||
{
|
||||
"file": "Shaders/Depth/DepthPass.shader",
|
||||
"file": "Shaders/Depth/DepthPassSkin.shader",
|
||||
"tag": "DepthPass"
|
||||
},
|
||||
{
|
||||
"file": "Shaders/MotionVector/MeshMotionVector.shader",
|
||||
"file": "Shaders/MotionVector/MeshMotionVectorSkin.shader",
|
||||
"tag": "MeshMotionVector"
|
||||
}
|
||||
],
|
||||
|
||||
@@ -27,16 +27,6 @@ ShaderResourceGroup ObjectSrg : SRG_PerObject
|
||||
return SceneSrg::GetObjectToWorldInverseTransposeMatrix(m_objectId);
|
||||
}
|
||||
|
||||
//[GFX TODO][ATOM-15280] Move wrinkle mask data from the default object srg into something specific to the Skin shader
|
||||
uint m_wrinkle_mask_count;
|
||||
float4 m_wrinkle_mask_weights[4];
|
||||
Texture2D m_wrinkle_masks[16];
|
||||
|
||||
float GetWrinkleMaskWeight(uint index)
|
||||
{
|
||||
return m_wrinkle_mask_weights[index / 4][index % 4];
|
||||
}
|
||||
|
||||
//! Reflection Probe (smallest probe volume that overlaps the object position)
|
||||
struct ReflectionProbeData
|
||||
{
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* 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 <scenesrg.srgi>
|
||||
|
||||
ShaderResourceGroup ObjectSrg : SRG_PerObject
|
||||
{
|
||||
uint m_objectId;
|
||||
|
||||
//! Returns the matrix for transforming points from Object Space to World Space.
|
||||
float4x4 GetWorldMatrix()
|
||||
{
|
||||
return SceneSrg::GetObjectToWorldMatrix(m_objectId);
|
||||
}
|
||||
|
||||
//! Returns the inverse-transpose of the world matrix.
|
||||
//! Commonly used to transform normals while supporting non-uniform scale.
|
||||
float3x3 GetWorldMatrixInverseTranspose()
|
||||
{
|
||||
return SceneSrg::GetObjectToWorldInverseTransposeMatrix(m_objectId);
|
||||
}
|
||||
|
||||
uint m_wrinkle_mask_count;
|
||||
float4 m_wrinkle_mask_weights[4];
|
||||
Texture2D m_wrinkle_masks[16];
|
||||
|
||||
float GetWrinkleMaskWeight(uint index)
|
||||
{
|
||||
return m_wrinkle_mask_weights[index / 4][index % 4];
|
||||
}
|
||||
|
||||
//! Reflection Probe (smallest probe volume that overlaps the object position)
|
||||
struct ReflectionProbeData
|
||||
{
|
||||
row_major float3x4 m_modelToWorld;
|
||||
row_major float3x4 m_modelToWorldInverse; // does not include extents
|
||||
float3 m_outerObbHalfLengths;
|
||||
float3 m_innerObbHalfLengths;
|
||||
float m_padding;
|
||||
bool m_useReflectionProbe;
|
||||
bool m_useParallaxCorrection;
|
||||
};
|
||||
|
||||
ReflectionProbeData m_reflectionProbeData;
|
||||
TextureCube m_reflectionProbeCubeMap;
|
||||
|
||||
float4x4 GetReflectionProbeWorldMatrix()
|
||||
{
|
||||
float4x4 modelToWorld = float4x4(
|
||||
float4(1, 0, 0, 0),
|
||||
float4(0, 1, 0, 0),
|
||||
float4(0, 0, 1, 0),
|
||||
float4(0, 0, 0, 1));
|
||||
|
||||
modelToWorld[0] = m_reflectionProbeData.m_modelToWorld[0];
|
||||
modelToWorld[1] = m_reflectionProbeData.m_modelToWorld[1];
|
||||
modelToWorld[2] = m_reflectionProbeData.m_modelToWorld[2];
|
||||
return modelToWorld;
|
||||
}
|
||||
|
||||
float4x4 GetReflectionProbeWorldMatrixInverse()
|
||||
{
|
||||
float4x4 modelToWorldInverse = float4x4(
|
||||
float4(1, 0, 0, 0),
|
||||
float4(0, 1, 0, 0),
|
||||
float4(0, 0, 1, 0),
|
||||
float4(0, 0, 0, 1));
|
||||
|
||||
modelToWorldInverse[0] = m_reflectionProbeData.m_modelToWorldInverse[0];
|
||||
modelToWorldInverse[1] = m_reflectionProbeData.m_modelToWorldInverse[1];
|
||||
modelToWorldInverse[2] = m_reflectionProbeData.m_modelToWorldInverse[2];
|
||||
return modelToWorldInverse;
|
||||
}
|
||||
}
|
||||
@@ -6,30 +6,7 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#include <viewsrg.srgi>
|
||||
#include <Atom/Features/PBR/DefaultObjectSrg.azsli>
|
||||
#include <DepthPassCommon.azsli>
|
||||
|
||||
struct VSInput
|
||||
{
|
||||
float3 m_position : POSITION;
|
||||
};
|
||||
|
||||
struct VSDepthOutput
|
||||
{
|
||||
float4 m_position : SV_Position;
|
||||
};
|
||||
|
||||
VSDepthOutput DepthPassVS(VSInput IN)
|
||||
{
|
||||
VSDepthOutput OUT;
|
||||
|
||||
float4x4 objectToWorld = ObjectSrg::GetWorldMatrix();
|
||||
float4 worldPosition = mul(objectToWorld, float4(IN.m_position, 1.0));
|
||||
OUT.m_position = mul(ViewSrg::m_viewProjectionMatrix, worldPosition);
|
||||
|
||||
return OUT;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// Use the depth pass shader with the default object srg
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* 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 <viewsrg.srgi>
|
||||
|
||||
struct VSInput
|
||||
{
|
||||
float3 m_position : POSITION;
|
||||
};
|
||||
|
||||
struct VSDepthOutput
|
||||
{
|
||||
float4 m_position : SV_Position;
|
||||
};
|
||||
|
||||
VSDepthOutput DepthPassVS(VSInput IN)
|
||||
{
|
||||
VSDepthOutput OUT;
|
||||
|
||||
float4x4 objectToWorld = ObjectSrg::GetWorldMatrix();
|
||||
float4 worldPosition = mul(objectToWorld, float4(IN.m_position, 1.0));
|
||||
OUT.m_position = mul(ViewSrg::m_viewProjectionMatrix, worldPosition);
|
||||
|
||||
return OUT;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
* 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 <Atom/Features/Skin/SkinObjectSrg.azsli>
|
||||
#include <DepthPassCommon.azsli>
|
||||
|
||||
// Use the depth pass shader with the skin object srg
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"Source" : "DepthPassSkin",
|
||||
|
||||
"DepthStencilState" : {
|
||||
"Depth" : { "Enable" : true, "CompareFunc" : "GreaterEqual" }
|
||||
},
|
||||
|
||||
"CompilerHints" : {
|
||||
"DisableOptimizations" : false
|
||||
},
|
||||
|
||||
"ProgramSettings" :
|
||||
{
|
||||
"EntryPoints":
|
||||
[
|
||||
{
|
||||
"name": "DepthPassVS",
|
||||
"type" : "Vertex"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
"DrawList" : "depth"
|
||||
}
|
||||
@@ -6,77 +6,7 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#include <scenesrg.srgi>
|
||||
#include <viewsrg.srgi>
|
||||
|
||||
#include <Atom/Features/PBR/DefaultObjectSrg.azsli>
|
||||
#include <Atom/RPI/ShaderResourceGroups/DefaultDrawSrg.azsli>
|
||||
#include <MeshMotionVectorCommon.azsli>
|
||||
|
||||
struct VSInput
|
||||
{
|
||||
float3 m_position : POSITION;
|
||||
|
||||
// This gets set automatically by the system at runtime only if it's available.
|
||||
// There is a soft naming convention that associates this with o_prevPosition_isBound, which will be set to true whenever m_optional_prevPosition is available.
|
||||
// (search "m_optional_" in ShaderVariantAssetBuilder for details on the naming convention).
|
||||
// [GFX TODO][ATOM-14475]: Come up with a more elegant way to associate the isBound flag with the input stream.
|
||||
// Vertex position of last frame to capture small scale motion due to vertex animation
|
||||
float3 m_optional_prevPosition : POSITIONT;
|
||||
};
|
||||
|
||||
struct VSOutput
|
||||
{
|
||||
float4 m_position : SV_Position;
|
||||
float3 m_worldPos : TEXCOORD0;
|
||||
float3 m_worldPosPrev: TEXCOORD1;
|
||||
};
|
||||
|
||||
struct PSOutput
|
||||
{
|
||||
float2 m_motion : SV_Target0;
|
||||
};
|
||||
|
||||
// Indicates whether the vertex input struct's "m_optional_prevPosition" is bound. If false, it is not safe to read from m_optional_prevPosition.
|
||||
// This option gets set automatically by the system at runtime; there is a soft naming convention that associates it with m_optional_prevPosition.
|
||||
// (search "m_optional_" in ShaderVariantAssetBuilder for details on the naming convention).
|
||||
// [GFX TODO][ATOM-14475]: Come up with a more elegant way to associate the isBound flag with the input stream.
|
||||
option bool o_prevPosition_isBound;
|
||||
|
||||
VSOutput MainVS(VSInput IN)
|
||||
{
|
||||
VSOutput OUT;
|
||||
|
||||
OUT.m_worldPos = mul(SceneSrg::GetObjectToWorldMatrix(ObjectSrg::m_objectId), float4(IN.m_position, 1.0)).xyz;
|
||||
OUT.m_position = mul(ViewSrg::m_viewProjectionMatrix, float4(OUT.m_worldPos, 1.0));
|
||||
|
||||
if (o_prevPosition_isBound)
|
||||
{
|
||||
OUT.m_worldPosPrev = mul(SceneSrg::GetObjectToWorldMatrixPrev(ObjectSrg::m_objectId), float4(IN.m_optional_prevPosition, 1.0)).xyz;
|
||||
}
|
||||
else
|
||||
{
|
||||
OUT.m_worldPosPrev = mul(SceneSrg::GetObjectToWorldMatrixPrev(ObjectSrg::m_objectId), float4(IN.m_position, 1.0)).xyz;
|
||||
}
|
||||
|
||||
return OUT;
|
||||
}
|
||||
|
||||
PSOutput MainPS(VSOutput IN)
|
||||
{
|
||||
PSOutput OUT;
|
||||
|
||||
// Current clip position
|
||||
float4 clipPos = mul(ViewSrg::m_viewProjectionMatrix, float4(IN.m_worldPos, 1.0));
|
||||
|
||||
// Reprojected last frame's clip position, for skinned mesh it also implies last key frame
|
||||
float4 clipPosPrev = mul(ViewSrg::m_viewProjectionPrevMatrix, float4(IN.m_worldPosPrev, 1.0));
|
||||
|
||||
float2 motion = (clipPos.xy / clipPos.w - clipPosPrev.xy / clipPosPrev.w) * 0.5;
|
||||
|
||||
OUT.m_motion = motion;
|
||||
|
||||
// Flip y to line up with uv coordinates
|
||||
OUT.m_motion.y = -OUT.m_motion.y;
|
||||
|
||||
return OUT;
|
||||
}
|
||||
// Use the mesh motion vector with the default object srg
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* 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 <scenesrg.srgi>
|
||||
#include <viewsrg.srgi>
|
||||
|
||||
#include <Atom/RPI/ShaderResourceGroups/DefaultDrawSrg.azsli>
|
||||
|
||||
struct VSInput
|
||||
{
|
||||
float3 m_position : POSITION;
|
||||
|
||||
// This gets set automatically by the system at runtime only if it's available.
|
||||
// There is a soft naming convention that associates this with o_prevPosition_isBound, which will be set to true whenever m_optional_prevPosition is available.
|
||||
// (search "m_optional_" in ShaderVariantAssetBuilder for details on the naming convention).
|
||||
// [GFX TODO][ATOM-14475]: Come up with a more elegant way to associate the isBound flag with the input stream.
|
||||
// Vertex position of last frame to capture small scale motion due to vertex animation
|
||||
float3 m_optional_prevPosition : POSITIONT;
|
||||
};
|
||||
|
||||
struct VSOutput
|
||||
{
|
||||
float4 m_position : SV_Position;
|
||||
float3 m_worldPos : TEXCOORD0;
|
||||
float3 m_worldPosPrev: TEXCOORD1;
|
||||
};
|
||||
|
||||
struct PSOutput
|
||||
{
|
||||
float2 m_motion : SV_Target0;
|
||||
};
|
||||
|
||||
// Indicates whether the vertex input struct's "m_optional_prevPosition" is bound. If false, it is not safe to read from m_optional_prevPosition.
|
||||
// This option gets set automatically by the system at runtime; there is a soft naming convention that associates it with m_optional_prevPosition.
|
||||
// (search "m_optional_" in ShaderVariantAssetBuilder for details on the naming convention).
|
||||
// [GFX TODO][ATOM-14475]: Come up with a more elegant way to associate the isBound flag with the input stream.
|
||||
option bool o_prevPosition_isBound;
|
||||
|
||||
VSOutput MainVS(VSInput IN)
|
||||
{
|
||||
VSOutput OUT;
|
||||
|
||||
OUT.m_worldPos = mul(SceneSrg::GetObjectToWorldMatrix(ObjectSrg::m_objectId), float4(IN.m_position, 1.0)).xyz;
|
||||
OUT.m_position = mul(ViewSrg::m_viewProjectionMatrix, float4(OUT.m_worldPos, 1.0));
|
||||
|
||||
if (o_prevPosition_isBound)
|
||||
{
|
||||
OUT.m_worldPosPrev = mul(SceneSrg::GetObjectToWorldMatrixPrev(ObjectSrg::m_objectId), float4(IN.m_optional_prevPosition, 1.0)).xyz;
|
||||
}
|
||||
else
|
||||
{
|
||||
OUT.m_worldPosPrev = mul(SceneSrg::GetObjectToWorldMatrixPrev(ObjectSrg::m_objectId), float4(IN.m_position, 1.0)).xyz;
|
||||
}
|
||||
|
||||
return OUT;
|
||||
}
|
||||
|
||||
PSOutput MainPS(VSOutput IN)
|
||||
{
|
||||
PSOutput OUT;
|
||||
|
||||
// Current clip position
|
||||
float4 clipPos = mul(ViewSrg::m_viewProjectionMatrix, float4(IN.m_worldPos, 1.0));
|
||||
|
||||
// Reprojected last frame's clip position, for skinned mesh it also implies last key frame
|
||||
float4 clipPosPrev = mul(ViewSrg::m_viewProjectionPrevMatrix, float4(IN.m_worldPosPrev, 1.0));
|
||||
|
||||
float2 motion = (clipPos.xy / clipPos.w - clipPosPrev.xy / clipPosPrev.w) * 0.5;
|
||||
|
||||
OUT.m_motion = motion;
|
||||
|
||||
// Flip y to line up with uv coordinates
|
||||
OUT.m_motion.y = -OUT.m_motion.y;
|
||||
|
||||
return OUT;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
* 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 <Atom/Features/Skin/SkinObjectSrg.azsli>
|
||||
#include <MeshMotionVectorCommon.azsli>
|
||||
|
||||
// Use the mesh motion vector with the skin object srg
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"Source" : "MeshMotionVectorSkin",
|
||||
|
||||
"DepthStencilState" : {
|
||||
"Depth" : { "Enable" : true, "CompareFunc" : "GreaterEqual" }
|
||||
},
|
||||
|
||||
"DrawList" : "motion",
|
||||
|
||||
"ProgramSettings":
|
||||
{
|
||||
"EntryPoints":
|
||||
[
|
||||
{
|
||||
"name": "MainVS",
|
||||
"type": "Vertex"
|
||||
},
|
||||
{
|
||||
"name": "MainPS",
|
||||
"type": "Fragment"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -6,27 +6,7 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#include <scenesrg.srgi>
|
||||
#include <viewsrg.srgi>
|
||||
#include <Atom/Features/PBR/DefaultObjectSrg.azsli>
|
||||
#include <ShadowmapCommon.azsli>
|
||||
|
||||
struct VertexInput
|
||||
{
|
||||
float3 m_position : POSITION;
|
||||
};
|
||||
|
||||
struct VertexOutput
|
||||
{
|
||||
float4 m_position : SV_Position;
|
||||
};
|
||||
|
||||
VertexOutput MainVS(VertexInput input)
|
||||
{
|
||||
const float4x4 worldMatrix = ObjectSrg::GetWorldMatrix();
|
||||
VertexOutput output;
|
||||
|
||||
const float3 worldPosition = mul(worldMatrix, float4(input.m_position, 1.0)).xyz;
|
||||
output.m_position = mul(ViewSrg::m_viewProjectionMatrix, float4(worldPosition, 1.0));
|
||||
|
||||
return output;
|
||||
}
|
||||
// Use the shadowmap shader with the default object srg
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <scenesrg.srgi>
|
||||
#include <viewsrg.srgi>
|
||||
|
||||
struct VertexInput
|
||||
{
|
||||
float3 m_position : POSITION;
|
||||
};
|
||||
|
||||
struct VertexOutput
|
||||
{
|
||||
float4 m_position : SV_Position;
|
||||
};
|
||||
|
||||
VertexOutput MainVS(VertexInput input)
|
||||
{
|
||||
const float4x4 worldMatrix = ObjectSrg::GetWorldMatrix();
|
||||
VertexOutput output;
|
||||
|
||||
const float3 worldPosition = mul(worldMatrix, float4(input.m_position, 1.0)).xyz;
|
||||
output.m_position = mul(ViewSrg::m_viewProjectionMatrix, float4(worldPosition, 1.0));
|
||||
|
||||
return output;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
* 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 <Atom/Features/Skin/SkinObjectSrg.azsli>
|
||||
#include <ShadowmapCommon.azsli>
|
||||
|
||||
// Use the shadowmap shader with the skin object srg
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"Source" : "ShadowmapSkin",
|
||||
|
||||
"DepthStencilState" : {
|
||||
"Depth" : { "Enable" : true, "CompareFunc" : "LessEqual" }
|
||||
},
|
||||
|
||||
"DrawList" : "shadow",
|
||||
|
||||
"RasterState" :
|
||||
{
|
||||
"depthBias" : "10",
|
||||
"depthBiasSlopeScale" : "4"
|
||||
},
|
||||
|
||||
"ProgramSettings":
|
||||
{
|
||||
"EntryPoints":
|
||||
[
|
||||
{
|
||||
"name": "MainVS",
|
||||
"type": "Vertex"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -30,6 +30,7 @@ namespace AZ::Render
|
||||
void Clear();
|
||||
IndexType GetFreeSlotIndex();
|
||||
void RemoveIndex(IndexType index);
|
||||
void RemoveData(DataType* data);
|
||||
|
||||
DataType& GetData(IndexType index);
|
||||
const DataType& GetData(IndexType index) const;
|
||||
@@ -42,6 +43,7 @@ namespace AZ::Render
|
||||
const AZStd::vector<IndexType>& GetIndexVector() const;
|
||||
|
||||
IndexType GetRawIndex(IndexType index) const;
|
||||
IndexType GetIndexForData(const DataType* data) const;
|
||||
|
||||
private:
|
||||
constexpr static size_t InitialReservedSize = 128;
|
||||
|
||||
@@ -83,6 +83,16 @@ namespace AZ::Render
|
||||
m_indices.at(index) = m_firstFreeSlot;
|
||||
m_firstFreeSlot = index;
|
||||
}
|
||||
|
||||
template<typename DataType, typename IndexType>
|
||||
inline void IndexedDataVector<DataType, IndexType>::RemoveData(DataType* data)
|
||||
{
|
||||
IndexType indexForData = GetIndexForData(data);
|
||||
if (indexForData != NoFreeSlot)
|
||||
{
|
||||
RemoveIndex(indexForData);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename DataType, typename IndexType>
|
||||
inline DataType& IndexedDataVector<DataType, IndexType>::GetData(IndexType index)
|
||||
@@ -131,4 +141,14 @@ namespace AZ::Render
|
||||
{
|
||||
return m_indices.at(index);
|
||||
}
|
||||
|
||||
template<typename DataType, typename IndexType>
|
||||
IndexType IndexedDataVector<DataType, IndexType>::GetIndexForData(const DataType* data) const
|
||||
{
|
||||
if (data >= &m_data.front() && data <= &m_data.back())
|
||||
{
|
||||
return m_dataToIndices.at(data - &m_data.front());
|
||||
}
|
||||
return NoFreeSlot;
|
||||
}
|
||||
} // namespace AZ::Render
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:81b5fa1f978888c3be8a40fce20455668df2723a77587aeb7039f8bf74bdd0e3
|
||||
size 119
|
||||
@@ -0,0 +1,5 @@
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="12" height="12" fill="white"/>
|
||||
<rect width="12" height="12" rx="1" fill="#4A90E2"/>
|
||||
<path d="M2.86112 7.45504L5.5 7.50007L1.11005 3.27505C0.963317 3.12572 0.963317 2.88361 1.11005 2.73429L2.70413 1.112C2.85086 0.962668 3.08876 0.962668 3.23549 1.112L7.5 5.50005L7.4487 2.73429C7.44894 2.52328 7.61709 2.35235 7.82443 2.35235L9.62427 2.35241C9.83178 2.35241 10 2.5236 10 2.73479V9.61762C10 9.8288 9.83178 10 9.62427 10H2.86112C2.65361 10 2.48539 9.8288 2.48539 9.61762L2.48559 7.83742C2.48559 7.62631 2.65369 7.45515 2.86112 7.45504Z" fill="#444444"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 667 B |
@@ -2,5 +2,7 @@
|
||||
<qresource>
|
||||
<file>Icons/group_closed.png</file>
|
||||
<file>Icons/group_open.png</file>
|
||||
<file>Icons/blank.png</file>
|
||||
<file>Icons/changed_property.svg</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
||||
@@ -1,15 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 53.2 (72643) - https://sketchapp.com -->
|
||||
<title>icon / Environmental / Sky Highlight</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs>
|
||||
<path d="M10.875,5.34332825 L10.875,2.625 C10.875,2.00367966 11.3786797,1.5 12,1.5 C12.6213203,1.5 13.125,2.00367966 13.125,2.625 L13.125,5.34332825 C12.7591405,5.28194865 12.3833012,5.25 12,5.25 C11.6166988,5.25 11.2408595,5.28194865 10.875,5.34332825 L10.875,5.34332825 Z M15.9111012,6.49790859 L17.8336309,4.5753788 C18.2729708,4.13603897 18.9852814,4.13603897 19.4246212,4.5753788 C19.863961,5.01471863 19.863961,5.72702923 19.4246212,6.16636906 L17.5020914,8.08889885 C17.0642544,7.47406324 16.5259368,6.93574556 15.9111012,6.49790859 L15.9111012,6.49790859 Z M18.6566718,10.875 L21.375,10.875 C21.9963203,10.875 22.5,11.3786797 22.5,12 C22.5,12.6213203 21.9963203,13.125 21.375,13.125 L18.6566718,13.125 C18.7180514,12.7591405 18.75,12.3833012 18.75,12 C18.75,11.6166988 18.7180514,11.2408595 18.6566718,10.875 L18.6566718,10.875 Z M17.5020914,15.9111012 L19.4246212,17.8336309 C19.863961,18.2729708 19.863961,18.9852814 19.4246212,19.4246212 C18.9852814,19.863961 18.2729708,19.863961 17.8336309,19.4246212 L15.9111012,17.5020914 C16.5259368,17.0642544 17.0642544,16.5259368 17.5020914,15.9111012 L17.5020914,15.9111012 Z M13.125,18.6566718 L13.125,21.375 C13.125,21.9963203 12.6213203,22.5 12,22.5 C11.3786797,22.5 10.875,21.9963203 10.875,21.375 L10.875,18.6566718 C11.2408595,18.7180514 11.6166988,18.75 12,18.75 C12.3833012,18.75 12.7591405,18.7180514 13.125,18.6566718 L13.125,18.6566718 Z M8.08889885,17.5020914 L6.16636906,19.4246212 C5.72702923,19.863961 5.01471863,19.863961 4.5753788,19.4246212 C4.13603897,18.9852814 4.13603897,18.2729708 4.5753788,17.8336309 L6.49790859,15.9111012 C6.93574556,16.5259368 7.47406324,17.0642544 8.08889885,17.5020914 L8.08889885,17.5020914 Z M5.34332825,13.125 L2.625,13.125 C2.00367966,13.125 1.5,12.6213203 1.5,12 C1.5,11.3786797 2.00367966,10.875 2.625,10.875 L5.34332825,10.875 C5.28194865,11.2408595 5.25,11.6166988 5.25,12 C5.25,12.3833012 5.28194865,12.7591405 5.34332825,13.125 L5.34332825,13.125 Z M6.49790859,8.08889885 L4.5753788,6.16636906 C4.13603897,5.72702923 4.13603897,5.01471863 4.5753788,4.5753788 C5.01471863,4.13603897 5.72702923,4.13603897 6.16636906,4.5753788 L8.08889885,6.49790859 C7.47406324,6.93574556 6.93574556,7.47406324 6.49790859,8.08889885 Z M12,16 C9.790861,16 8,14.209139 8,12 C8,9.790861 9.790861,8 12,8 C14.209139,8 16,9.790861 16,12 C16,14.209139 14.209139,16 12,16 Z" id="path-1"></path>
|
||||
</defs>
|
||||
<g id="icon-/-Environmental-/-Sky-Highlight" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<mask id="mask-2" fill="white">
|
||||
<use xlink:href="#path-1"></use>
|
||||
</mask>
|
||||
<use id="Shape" fill="#8BDD15" xlink:href="#path-1"></use>
|
||||
</g>
|
||||
</svg>
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M22.3438 17.1096C22.3438 17.4789 22.1402 17.8181 21.8144 17.9919L12.4933 22.9632C12.1997 23.1197 11.8476 23.1201 11.5538 22.9641L2.18687 17.9915C1.86006 17.818 1.65576 17.4783 1.65576 17.1083V7.01127C1.65576 6.64874 1.85197 6.31461 2.16855 6.13798L11.0673 1.17335C11.6754 0.834121 12.4161 0.835535 13.0228 1.17709L21.8343 6.13729C22.149 6.31445 22.3438 6.64755 22.3438 7.0087V17.1096ZM18.5409 15.0325C18.4611 15.3007 18.3148 15.558 18.0922 15.7637L20.0423 16.8389L12.0216 21.1724L3.93436 16.8389L6.15868 15.6225C5.97079 15.422 5.80027 15.1916 5.66768 14.9666L3.58362 15.9796V7.38525L11.5543 3.0379V5.15478C11.9078 5.05561 12.2944 5.10892 12.6106 5.27134V3.0379L20.4596 7.38525V15.9796L18.5409 15.0325Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M14.2539 15.7233C14.6484 16.1002 15.2132 16.3362 15.8405 16.3362C17.0332 16.3362 18 15.4831 18 14.4307C18 13.7082 17.5443 13.0796 16.8726 12.7566C16.9535 12.5453 16.9976 12.3178 16.9976 12.0806C16.9976 10.9698 16.0308 10.0693 14.8381 10.0693C13.6455 10.0693 12.6786 10.9698 12.6786 12.0806L12.6786 12.0885C12.2188 12.0885 11.846 12.4357 11.846 12.864C11.846 12.9208 11.8525 12.9761 11.865 13.0295C11.8017 13.0186 11.7369 13.0074 11.6705 12.9957C10.4155 12.7742 9.33382 13.6413 9.33382 14.6001C9.33382 15.5589 9.94577 15.9127 11.1967 16.3785C12.0329 16.5537 13.4996 16.6779 14.2539 15.7233Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.56534 13.9147C7.87582 13.2747 7.44183 12.3443 7.44183 11.3086C7.44183 9.3783 8.94955 7.81348 10.8094 7.81348C11.9738 7.81348 13.0002 8.42682 13.605 9.3592C13.1565 9.55085 12.9243 9.83046 12.758 10.0721C12.472 10.4875 12.2905 10.9511 12.2905 11.286C12.1112 11.1809 11.7089 11.3109 11.5191 11.5007C11.3294 11.6904 11.2139 12.0368 11.2764 12.32L11.269 12.3179C11.0257 12.2484 10.4232 12.076 9.71747 12.4856C9.18575 12.7942 8.75166 13.2238 8.56534 13.9147Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.7198 5.5C11.1341 5.50049 11.4694 5.83668 11.4689 6.25089L11.4676 7.4149C11.225 7.36682 10.9741 7.34161 10.7173 7.34161C10.4607 7.34161 10.21 7.36679 9.96757 7.41481L9.96895 6.24911C9.96944 5.8349 10.3056 5.49951 10.7198 5.5ZM7.62108 8.9143C7.91801 8.50756 8.29357 8.16156 8.7255 7.89854L7.96371 7.11207C7.67552 6.81455 7.2007 6.80698 6.90318 7.09517C6.60566 7.38336 6.59809 7.85818 6.88628 8.1557L7.62108 8.9143ZM6.91223 11.5693C6.89769 11.4341 6.89023 11.2968 6.89023 11.1578C6.89023 10.7796 6.9454 10.4142 7.04816 10.0693H5.81698C5.40277 10.0693 5.06698 10.4051 5.06698 10.8193C5.06698 11.2335 5.40277 11.5693 5.81698 11.5693H6.91223ZM8.06611 13.9099C7.75724 13.614 7.49807 13.2669 7.30234 12.8822L6.30037 13.6199C5.96681 13.8655 5.89548 14.3349 6.14106 14.6685C6.38663 15.0021 6.85612 15.0734 7.18968 14.8278L8.1963 14.0867L8.06611 13.9099ZM12.8339 7.97783C13.2546 8.25681 13.6168 8.61658 13.8981 9.03493L14.6921 8.24344C14.9854 7.95101 14.9862 7.47613 14.6938 7.18278C14.4013 6.88943 13.9265 6.88869 13.6331 7.18112L12.8339 7.97783Z" fill="white"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 3.1 KiB |
+2
-2
@@ -101,9 +101,9 @@ namespace MaterialEditor
|
||||
{
|
||||
if (IsInstanceNodePropertyModifed(node))
|
||||
{
|
||||
return ":/PropertyEditor/Resources/changed_data_item.png";
|
||||
return ":/Icons/changed_property.svg";
|
||||
}
|
||||
return ":/PropertyEditor/Resources/blank.png";
|
||||
return ":/Icons/blank.png";
|
||||
}
|
||||
|
||||
void MaterialInspector::AddOverviewGroup()
|
||||
|
||||
@@ -88,18 +88,18 @@ namespace MaterialEditor
|
||||
toneMappingButton->setVisible(true);
|
||||
addWidget(toneMappingButton);
|
||||
|
||||
// Add model combo box
|
||||
auto modelPresetComboBox = new ModelPresetComboBox(this);
|
||||
modelPresetComboBox->setSizeAdjustPolicy(QComboBox::SizeAdjustPolicy::AdjustToContents);
|
||||
modelPresetComboBox->view()->setMinimumWidth(200);
|
||||
addWidget(modelPresetComboBox);
|
||||
|
||||
// Add lighting preset combo box
|
||||
auto lightingPresetComboBox = new LightingPresetComboBox(this);
|
||||
lightingPresetComboBox->setSizeAdjustPolicy(QComboBox::SizeAdjustPolicy::AdjustToContents);
|
||||
lightingPresetComboBox->view()->setMinimumWidth(200);
|
||||
addWidget(lightingPresetComboBox);
|
||||
|
||||
// Add model combo box
|
||||
auto modelPresetComboBox = new ModelPresetComboBox(this);
|
||||
modelPresetComboBox->setSizeAdjustPolicy(QComboBox::SizeAdjustPolicy::AdjustToContents);
|
||||
modelPresetComboBox->view()->setMinimumWidth(200);
|
||||
addWidget(modelPresetComboBox);
|
||||
|
||||
MaterialViewportNotificationBus::Handler::BusConnect();
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -521,9 +521,9 @@ namespace AZ
|
||||
{
|
||||
if (IsInstanceNodePropertyModifed(node))
|
||||
{
|
||||
return ":/PropertyEditor/Resources/changed_data_item.png";
|
||||
return ":/Icons/changed_property.svg";
|
||||
}
|
||||
return ":/PropertyEditor/Resources/blank.png";
|
||||
return ":/Icons/blank.png";
|
||||
}
|
||||
|
||||
bool MaterialPropertyInspector::SaveMaterial() const
|
||||
|
||||
@@ -37,11 +37,11 @@ namespace
|
||||
using UiAnimSystemUnorderedMap = AZStd::unordered_map<KeyType, MappedType, Hasher, EqualKey, AZStd::stateless_allocator>;
|
||||
}
|
||||
// Serialization for anim nodes & param types
|
||||
#define REGISTER_NODE_TYPE(name) assert(g_animNodeEnumToStringMap.contains(eUiAnimNodeType_ ## name)); \
|
||||
#define REGISTER_NODE_TYPE(name) assert(!g_animNodeEnumToStringMap.contains(eUiAnimNodeType_ ## name)); \
|
||||
g_animNodeEnumToStringMap[eUiAnimNodeType_ ## name] = AZ_STRINGIZE(name); \
|
||||
g_animNodeStringToEnumMap[UiAnimParamSystemString(AZ_STRINGIZE(name))] = eUiAnimNodeType_ ## name;
|
||||
|
||||
#define REGISTER_PARAM_TYPE(name) assert(g_animParamEnumToStringMap.contains(eUiAnimParamType_ ## name)); \
|
||||
#define REGISTER_PARAM_TYPE(name) assert(!g_animParamEnumToStringMap.contains(eUiAnimParamType_ ## name)); \
|
||||
g_animParamEnumToStringMap[eUiAnimParamType_ ## name] = AZ_STRINGIZE(name); \
|
||||
g_animParamStringToEnumMap[UiAnimParamSystemString(AZ_STRINGIZE(name))] = eUiAnimParamType_ ## name;
|
||||
|
||||
|
||||
@@ -87,11 +87,11 @@ namespace
|
||||
}
|
||||
|
||||
// Serialization for anim nodes & param types
|
||||
#define REGISTER_NODE_TYPE(name) assert(g_animNodeEnumToStringMap.contains(AnimNodeType::name)); \
|
||||
#define REGISTER_NODE_TYPE(name) assert(!g_animNodeEnumToStringMap.contains(AnimNodeType::name)); \
|
||||
g_animNodeEnumToStringMap[AnimNodeType::name] = AZ_STRINGIZE(name); \
|
||||
g_animNodeStringToEnumMap[AnimParamSystemString(AZ_STRINGIZE(name))] = AnimNodeType::name;
|
||||
|
||||
#define REGISTER_PARAM_TYPE(name) assert(g_animParamEnumToStringMap.contains(AnimParamType::name)); \
|
||||
#define REGISTER_PARAM_TYPE(name) assert(!g_animParamEnumToStringMap.contains(AnimParamType::name)); \
|
||||
g_animParamEnumToStringMap[AnimParamType::name] = AZ_STRINGIZE(name); \
|
||||
g_animParamStringToEnumMap[AnimParamSystemString(AZ_STRINGIZE(name))] = AnimParamType::name;
|
||||
|
||||
|
||||
@@ -308,6 +308,12 @@ namespace Multiplayer
|
||||
return true;
|
||||
}
|
||||
|
||||
void MultiplayerSystemComponent::OnUpdateSessionBegin(const AzFramework::SessionConfig& sessionConfig, const AZStd::string& updateReason)
|
||||
{
|
||||
AZ_UNUSED(sessionConfig);
|
||||
AZ_UNUSED(updateReason);
|
||||
}
|
||||
|
||||
void MultiplayerSystemComponent::OnTick(float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint time)
|
||||
{
|
||||
const AZ::TimeMs deltaTimeMs = aznumeric_cast<AZ::TimeMs>(static_cast<int32_t>(deltaTime * 1000.0f));
|
||||
|
||||
@@ -70,6 +70,7 @@ namespace Multiplayer
|
||||
bool OnSessionHealthCheck() override;
|
||||
bool OnCreateSessionBegin(const AzFramework::SessionConfig& sessionConfig) override;
|
||||
bool OnDestroySessionBegin() override;
|
||||
void OnUpdateSessionBegin(const AzFramework::SessionConfig& sessionConfig, const AZStd::string& updateReason) override;
|
||||
//! @}
|
||||
|
||||
//! AZ::TickBus::Handler overrides.
|
||||
|
||||
@@ -4,12 +4,6 @@
|
||||
"parentMaterial": "",
|
||||
"propertyLayoutVersion": 1,
|
||||
"properties": {
|
||||
"macroColor": {
|
||||
"useTexture": false
|
||||
},
|
||||
"macroNormal": {
|
||||
"useTexture": false
|
||||
},
|
||||
"baseColor": {
|
||||
"color": [ 0.18, 0.18, 0.18 ],
|
||||
"useTexture": false
|
||||
|
||||
@@ -133,67 +133,6 @@
|
||||
}
|
||||
}
|
||||
],
|
||||
"macroColor": [
|
||||
{
|
||||
"id": "textureMap",
|
||||
"displayName": "Texture",
|
||||
"description": "Macro color texture map",
|
||||
"type": "Image",
|
||||
"connection": {
|
||||
"type": "ShaderInput",
|
||||
"id": "m_macroColorMap"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "useTexture",
|
||||
"displayName": "Use Texture",
|
||||
"description": "Whether to use the texture.",
|
||||
"type": "Bool",
|
||||
"defaultValue": true
|
||||
}
|
||||
|
||||
],
|
||||
"macroNormal": [
|
||||
{
|
||||
"id": "textureMap",
|
||||
"displayName": "Texture",
|
||||
"description": "Macro normal texture map",
|
||||
"type": "Image",
|
||||
"connection": {
|
||||
"type": "ShaderInput",
|
||||
"id": "m_macroNormalMap"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "useTexture",
|
||||
"displayName": "Use Texture",
|
||||
"description": "Whether to use the texture.",
|
||||
"type": "Bool",
|
||||
"defaultValue": true
|
||||
},
|
||||
{
|
||||
"id": "flipX",
|
||||
"displayName": "Flip X Channel",
|
||||
"description": "Flip tangent direction for this normal map.",
|
||||
"type": "Bool",
|
||||
"defaultValue": false,
|
||||
"connection": {
|
||||
"type": "ShaderInput",
|
||||
"id": "m_flipMacroNormalX"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "flipY",
|
||||
"displayName": "Flip Y Channel",
|
||||
"description": "Flip bitangent direction for this normal map.",
|
||||
"type": "Bool",
|
||||
"defaultValue": false,
|
||||
"connection": {
|
||||
"type": "ShaderInput",
|
||||
"id": "m_flipMacroNormalY"
|
||||
}
|
||||
}
|
||||
],
|
||||
"baseColor": [
|
||||
{
|
||||
"id": "color",
|
||||
@@ -380,22 +319,6 @@
|
||||
}
|
||||
],
|
||||
"functors": [
|
||||
{
|
||||
"type": "UseTexture",
|
||||
"args": {
|
||||
"textureProperty": "macroColor.textureMap",
|
||||
"useTextureProperty": "macroColor.useTexture",
|
||||
"shaderOption": "o_macroColor_useTexture"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "UseTexture",
|
||||
"args": {
|
||||
"textureProperty": "macroNormal.textureMap",
|
||||
"useTextureProperty": "macroNormal.useTexture",
|
||||
"shaderOption": "o_macroNormal_useTexture"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "UseTexture",
|
||||
"args": {
|
||||
|
||||
@@ -4,42 +4,59 @@
|
||||
"version": 1,
|
||||
"groups": [
|
||||
{
|
||||
"id": "settings",
|
||||
"displayName": "Settings"
|
||||
"name": "baseColor",
|
||||
"displayName": "Base Color",
|
||||
"description": "Properties for configuring the surface reflected color for dielectrics or reflectance values for metals."
|
||||
},
|
||||
{
|
||||
"name": "normal",
|
||||
"displayName": "Normal",
|
||||
"description": "Properties related to configuring surface normal."
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"macroColor": [
|
||||
"baseColor": [
|
||||
{
|
||||
"id": "useTexture",
|
||||
"displayName": "Use Texture",
|
||||
"description": "Whether to use the texture.",
|
||||
"type": "Bool",
|
||||
"defaultValue": true
|
||||
"name": "textureMap",
|
||||
"displayName": "Texture",
|
||||
"description": "Base color of the macro material",
|
||||
"type": "Image"
|
||||
}
|
||||
|
||||
],
|
||||
"macroNormal": [
|
||||
"normal": [
|
||||
{
|
||||
"id": "useTexture",
|
||||
"displayName": "Use Texture",
|
||||
"description": "Whether to use the texture.",
|
||||
"name": "textureMap",
|
||||
"displayName": "Texture",
|
||||
"description": "Texture for defining surface normal direction. These will override normals generated from the geometry.",
|
||||
"type": "Image"
|
||||
},
|
||||
{
|
||||
"name": "flipX",
|
||||
"displayName": "Flip X Channel",
|
||||
"description": "Flip tangent direction for this normal map.",
|
||||
"type": "Bool",
|
||||
"defaultValue": true
|
||||
"defaultValue": false
|
||||
},
|
||||
{
|
||||
"name": "flipY",
|
||||
"displayName": "Flip Y Channel",
|
||||
"description": "Flip bitangent direction for this normal map.",
|
||||
"type": "Bool",
|
||||
"defaultValue": false
|
||||
},
|
||||
{
|
||||
"name": "factor",
|
||||
"displayName": "Factor",
|
||||
"description": "Strength factor for scaling the values",
|
||||
"type": "Float",
|
||||
"defaultValue": 1.0,
|
||||
"min": 0.0,
|
||||
"softMax": 2.0
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"shaders": [
|
||||
{
|
||||
"file": "../../Shaders/Terrain/TerrainPBR_ForwardPass.shader"
|
||||
},
|
||||
{
|
||||
"file": "../../Shaders/Terrain/Terrain_Shadowmap.shader"
|
||||
},
|
||||
{
|
||||
"file": "../../Shaders/Terrain/Terrain_DepthPass.shader"
|
||||
}
|
||||
],
|
||||
"functors": [
|
||||
]
|
||||
|
||||
@@ -26,8 +26,24 @@ ShaderResourceGroup ObjectSrg : SRG_PerObject
|
||||
float m_heightScale;
|
||||
};
|
||||
|
||||
struct MacroMaterialData
|
||||
{
|
||||
float2 m_uvMin;
|
||||
float2 m_uvMax;
|
||||
float m_normalFactor;
|
||||
bool m_flipNormalX;
|
||||
bool m_flipNormalY;
|
||||
uint m_mapsInUse;
|
||||
};
|
||||
|
||||
TerrainData m_terrainData;
|
||||
|
||||
MacroMaterialData m_macroMaterialData[4];
|
||||
uint m_macroMaterialCount;
|
||||
|
||||
Texture2D m_macroColorMap[4];
|
||||
Texture2D m_macroNormalMap[4];
|
||||
|
||||
// The below shouldn't be in this SRG but needs to be for now because the lighting functions depend on them.
|
||||
|
||||
//! Reflection Probe (smallest probe volume that overlaps the object position)
|
||||
@@ -101,14 +117,6 @@ ShaderResourceGroup TerrainMaterialSrg : SRG_PerMaterial
|
||||
MaxAnisotropy = 16;
|
||||
};
|
||||
|
||||
// Macro Color
|
||||
Texture2D m_macroColorMap;
|
||||
|
||||
// Macro normal
|
||||
Texture2D m_macroNormalMap;
|
||||
bool m_flipMacroNormalX;
|
||||
bool m_flipMacroNormalY;
|
||||
|
||||
// Base Color
|
||||
float3 m_baseColor;
|
||||
float m_baseColorFactor;
|
||||
@@ -130,8 +138,6 @@ ShaderResourceGroup TerrainMaterialSrg : SRG_PerMaterial
|
||||
}
|
||||
|
||||
option bool o_useTerrainSmoothing = false;
|
||||
option bool o_macroColor_useTexture = true;
|
||||
option bool o_macroNormal_useTexture = true;
|
||||
option bool o_baseColor_useTexture = true;
|
||||
option bool o_specularF0_useTexture = true;
|
||||
option bool o_normal_useTexture = true;
|
||||
|
||||
@@ -68,7 +68,6 @@ VSOutput TerrainPBR_MainPassVS(VertexInput IN)
|
||||
ForwardPassOutput TerrainPBR_MainPassPS(VSOutput IN)
|
||||
{
|
||||
// ------- Surface -------
|
||||
|
||||
Surface surface;
|
||||
|
||||
// Position
|
||||
@@ -83,12 +82,32 @@ ForwardPassOutput TerrainPBR_MainPassPS(VSOutput IN)
|
||||
|
||||
// ------- Normal -------
|
||||
float3 macroNormal = IN.m_normal;
|
||||
if (o_macroNormal_useTexture)
|
||||
{
|
||||
macroNormal = GetNormalInputTS(TerrainMaterialSrg::m_macroNormalMap, TerrainMaterialSrg::m_sampler,
|
||||
origUv, TerrainMaterialSrg::m_flipMacroNormalX, TerrainMaterialSrg::m_flipMacroNormalY, CreateIdentity3x3(), true, 1.0);
|
||||
}
|
||||
|
||||
// ------- Macro Color / Normal -------
|
||||
float3 macroColor = TerrainMaterialSrg::m_baseColor.rgb;
|
||||
[unroll] for (uint i = 0; i < 4; ++i)
|
||||
{
|
||||
float2 macroUvMin = ObjectSrg::m_macroMaterialData[i].m_uvMin;
|
||||
float2 macroUvMax = ObjectSrg::m_macroMaterialData[i].m_uvMax;
|
||||
float2 macroUv = lerp(macroUvMin, macroUvMax, IN.m_uv);
|
||||
if (macroUv.x >= 0.0 && macroUv.x <= 1.0 && macroUv.y >= 0.0 && macroUv.y <= 1.0)
|
||||
{
|
||||
if ((ObjectSrg::m_macroMaterialData[i].m_mapsInUse & 1) > 0)
|
||||
{
|
||||
macroColor = GetBaseColorInput(ObjectSrg::m_macroColorMap[i], TerrainMaterialSrg::m_sampler, macroUv, macroColor, true);
|
||||
}
|
||||
if ((ObjectSrg::m_macroMaterialData[i].m_mapsInUse & 2) > 0)
|
||||
{
|
||||
bool flipX = ObjectSrg::m_macroMaterialData[i].m_flipNormalX;
|
||||
bool flipY = ObjectSrg::m_macroMaterialData[i].m_flipNormalY;
|
||||
bool factor = ObjectSrg::m_macroMaterialData[i].m_normalFactor;
|
||||
macroNormal = GetNormalInputTS(ObjectSrg::m_macroNormalMap[i], TerrainMaterialSrg::m_sampler,
|
||||
macroUv, flipX, flipY, CreateIdentity3x3(), true, factor);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
float3 detailNormal = GetNormalInputTS(TerrainMaterialSrg::m_normalMap, TerrainMaterialSrg::m_sampler,
|
||||
detailUv, TerrainMaterialSrg::m_flipNormalX, TerrainMaterialSrg::m_flipNormalY, CreateIdentity3x3(), o_normal_useTexture, TerrainMaterialSrg::m_normalFactor);
|
||||
|
||||
@@ -97,9 +116,6 @@ ForwardPassOutput TerrainPBR_MainPassPS(VSOutput IN)
|
||||
surface.normal = normalize(surface.normal);
|
||||
surface.vertexNormal = normalize(IN.m_normal);
|
||||
|
||||
// ------- Macro Color -------
|
||||
float3 macroColor = GetBaseColorInput(TerrainMaterialSrg::m_macroColorMap, TerrainMaterialSrg::m_sampler, origUv, TerrainMaterialSrg::m_baseColor.rgb, o_baseColor_useTexture);
|
||||
|
||||
// ------- Base Color -------
|
||||
float3 detailColor = GetBaseColorInput(TerrainMaterialSrg::m_baseColorMap, TerrainMaterialSrg::m_sampler, detailUv, TerrainMaterialSrg::m_baseColor.rgb, o_baseColor_useTexture);
|
||||
float3 blendedColor = BlendBaseColor(lerp(detailColor, TerrainMaterialSrg::m_baseColor.rgb, detailFactor), macroColor, TerrainMaterialSrg::m_baseColorFactor, o_baseColorTextureBlendMode, o_baseColor_useTexture);
|
||||
|
||||
@@ -49,13 +49,25 @@ namespace Terrain
|
||||
|
||||
namespace MaterialInputs
|
||||
{
|
||||
// Terrain material
|
||||
static const char* const HeightmapImage("settings.heightmapImage");
|
||||
|
||||
// Macro material
|
||||
static const char* const MacroColorTextureMap("baseColor.textureMap");
|
||||
static const char* const MacroNormalTextureMap("normal.textureMap");
|
||||
static const char* const MacroNormalFlipX("normal.flipX");
|
||||
static const char* const MacroNormalFlipY("normal.flipY");
|
||||
static const char* const MacroNormalFactor("normal.factor");
|
||||
}
|
||||
|
||||
namespace ShaderInputs
|
||||
{
|
||||
static const char* const ModelToWorld("m_modelToWorld");
|
||||
static const char* const TerrainData("m_terrainData");
|
||||
static const char* const MacroMaterialData("m_macroMaterialData");
|
||||
static const char* const MacroMaterialCount("m_macroMaterialCount");
|
||||
static const char* const MacroColorMap("m_macroColorMap");
|
||||
static const char* const MacroNormalMap("m_macroNormalMap");
|
||||
}
|
||||
|
||||
|
||||
@@ -71,8 +83,6 @@ namespace Terrain
|
||||
|
||||
void TerrainFeatureProcessor::Activate()
|
||||
{
|
||||
m_areaData = {};
|
||||
m_dirtyRegion = AZ::Aabb::CreateNull();
|
||||
Initialize();
|
||||
AzFramework::Terrain::TerrainDataNotificationBus::Handler::BusConnect();
|
||||
}
|
||||
@@ -94,6 +104,10 @@ namespace Terrain
|
||||
{
|
||||
AZ_Error("TerrainFeatureProcessor", false, "No per-object ShaderResourceGroup found on terrain material.");
|
||||
}
|
||||
else
|
||||
{
|
||||
PrepareMaterialData();
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -107,11 +121,17 @@ namespace Terrain
|
||||
|
||||
void TerrainFeatureProcessor::Deactivate()
|
||||
{
|
||||
TerrainMacroMaterialNotificationBus::Handler::BusDisconnect();
|
||||
AzFramework::Terrain::TerrainDataNotificationBus::Handler::BusDisconnect();
|
||||
AZ::RPI::MaterialReloadNotificationBus::Handler::BusDisconnect();
|
||||
|
||||
m_patchModel = {};
|
||||
m_areaData = {};
|
||||
m_dirtyRegion = AZ::Aabb::CreateNull();
|
||||
m_sectorData.clear();
|
||||
m_macroMaterials.Clear();
|
||||
m_materialAssetLoader = {};
|
||||
m_materialInstance = {};
|
||||
}
|
||||
|
||||
void TerrainFeatureProcessor::Render(const AZ::RPI::FeatureProcessor::RenderPacket& packet)
|
||||
@@ -126,7 +146,7 @@ namespace Terrain
|
||||
|
||||
void TerrainFeatureProcessor::OnTerrainDataChanged(const AZ::Aabb& dirtyRegion, TerrainDataChangedMask dataChangedMask)
|
||||
{
|
||||
if (dataChangedMask != TerrainDataChangedMask::HeightData && dataChangedMask != TerrainDataChangedMask::Settings)
|
||||
if ((dataChangedMask & (TerrainDataChangedMask::HeightData | TerrainDataChangedMask::Settings)) == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -140,14 +160,21 @@ namespace Terrain
|
||||
m_dirtyRegion.AddAabb(regionToUpdate);
|
||||
m_dirtyRegion.Clamp(worldBounds);
|
||||
|
||||
AZ::Transform transform = AZ::Transform::CreateTranslation(worldBounds.GetCenter());
|
||||
const AZ::Transform transform = AZ::Transform::CreateTranslation(worldBounds.GetCenter());
|
||||
|
||||
AZ::Vector2 queryResolution = AZ::Vector2(1.0f);
|
||||
AzFramework::Terrain::TerrainDataRequestBus::BroadcastResult(
|
||||
queryResolution, &AzFramework::Terrain::TerrainDataRequests::GetTerrainHeightQueryResolution);
|
||||
|
||||
// Sectors need to be rebuilt if the world bounds change in the x/y, or the sample spacing changes.
|
||||
m_areaData.m_rebuildSectors = m_areaData.m_rebuildSectors ||
|
||||
m_areaData.m_terrainBounds.GetMin().GetX() != worldBounds.GetMin().GetX() ||
|
||||
m_areaData.m_terrainBounds.GetMin().GetY() != worldBounds.GetMin().GetY() ||
|
||||
m_areaData.m_terrainBounds.GetMax().GetX() != worldBounds.GetMax().GetX() ||
|
||||
m_areaData.m_terrainBounds.GetMax().GetY() != worldBounds.GetMax().GetY() ||
|
||||
m_areaData.m_sampleSpacing != queryResolution.GetX();
|
||||
|
||||
m_areaData.m_transform = transform;
|
||||
m_areaData.m_heightScale = worldBounds.GetZExtent();
|
||||
m_areaData.m_terrainBounds = worldBounds;
|
||||
m_areaData.m_heightmapImageWidth = aznumeric_cast<uint32_t>(worldBounds.GetXExtent() / queryResolution.GetX());
|
||||
m_areaData.m_heightmapImageHeight = aznumeric_cast<uint32_t>(worldBounds.GetYExtent() / queryResolution.GetY());
|
||||
@@ -155,7 +182,93 @@ namespace Terrain
|
||||
m_areaData.m_updateHeight = aznumeric_cast<uint32_t>(m_dirtyRegion.GetYExtent() / queryResolution.GetY());
|
||||
// Currently query resolution is multidimensional but the rendering system only supports this changing in one dimension.
|
||||
m_areaData.m_sampleSpacing = queryResolution.GetX();
|
||||
m_areaData.m_propertiesDirty = true;
|
||||
m_areaData.m_heightmapUpdated = true;
|
||||
}
|
||||
|
||||
void TerrainFeatureProcessor::OnTerrainMacroMaterialCreated(AZ::EntityId entityId, MaterialInstance material, const AZ::Aabb& region)
|
||||
{
|
||||
MacroMaterialData& materialData = FindOrCreateMacroMaterial(entityId);
|
||||
materialData.m_bounds = region;
|
||||
|
||||
UpdateMacroMaterialData(materialData, material);
|
||||
|
||||
// Update all sectors in region.
|
||||
ForOverlappingSectors(materialData.m_bounds,
|
||||
[&](SectorData& sectorData) {
|
||||
if (sectorData.m_macroMaterials.size() < sectorData.m_macroMaterials.max_size())
|
||||
{
|
||||
sectorData.m_macroMaterials.push_back(m_macroMaterials.GetIndexForData(&materialData));
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void TerrainFeatureProcessor::OnTerrainMacroMaterialChanged(AZ::EntityId entityId, MaterialInstance macroMaterial)
|
||||
{
|
||||
if (macroMaterial)
|
||||
{
|
||||
MacroMaterialData& data = FindOrCreateMacroMaterial(entityId);
|
||||
UpdateMacroMaterialData(data, macroMaterial);
|
||||
}
|
||||
else
|
||||
{
|
||||
RemoveMacroMaterial(entityId);
|
||||
}
|
||||
}
|
||||
|
||||
void TerrainFeatureProcessor::OnTerrainMacroMaterialRegionChanged(AZ::EntityId entityId, [[maybe_unused]] const AZ::Aabb& oldRegion, const AZ::Aabb& newRegion)
|
||||
{
|
||||
MacroMaterialData& materialData = FindOrCreateMacroMaterial(entityId);
|
||||
for (SectorData& sectorData : m_sectorData)
|
||||
{
|
||||
bool overlapsOld = sectorData.m_aabb.Overlaps(materialData.m_bounds);
|
||||
bool overlapsNew = sectorData.m_aabb.Overlaps(newRegion);
|
||||
if (overlapsOld && !overlapsNew)
|
||||
{
|
||||
// Remove the macro material from this sector
|
||||
for (uint16_t& idx : sectorData.m_macroMaterials)
|
||||
{
|
||||
if (m_macroMaterials.GetData(idx).m_entityId == entityId)
|
||||
{
|
||||
idx = sectorData.m_macroMaterials.back();
|
||||
sectorData.m_macroMaterials.pop_back();
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (overlapsNew && !overlapsOld)
|
||||
{
|
||||
// Add the macro material to this sector
|
||||
if (sectorData.m_macroMaterials.size() < MaxMaterialsPerSector)
|
||||
{
|
||||
sectorData.m_macroMaterials.push_back(m_macroMaterials.GetIndexForData(&materialData));
|
||||
}
|
||||
}
|
||||
}
|
||||
m_areaData.m_macroMaterialsUpdated = true;
|
||||
materialData.m_bounds = newRegion;
|
||||
}
|
||||
|
||||
void TerrainFeatureProcessor::OnTerrainMacroMaterialDestroyed(AZ::EntityId entityId)
|
||||
{
|
||||
MacroMaterialData* materialData = FindMacroMaterial(entityId);
|
||||
|
||||
if (materialData)
|
||||
{
|
||||
uint16_t destroyedMaterialIndex = m_macroMaterials.GetIndexForData(materialData);
|
||||
ForOverlappingSectors(materialData->m_bounds,
|
||||
[&](SectorData& sectorData) {
|
||||
for (uint16_t& idx : sectorData.m_macroMaterials)
|
||||
{
|
||||
if (idx == destroyedMaterialIndex)
|
||||
{
|
||||
idx = sectorData.m_macroMaterials.back();
|
||||
sectorData.m_macroMaterials.pop_back();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
m_areaData.m_macroMaterialsUpdated = true;
|
||||
}
|
||||
|
||||
void TerrainFeatureProcessor::UpdateTerrainData()
|
||||
@@ -165,9 +278,9 @@ namespace Terrain
|
||||
uint32_t width = m_areaData.m_updateWidth;
|
||||
uint32_t height = m_areaData.m_updateHeight;
|
||||
const AZ::Aabb& worldBounds = m_areaData.m_terrainBounds;
|
||||
float queryResolution = m_areaData.m_sampleSpacing;
|
||||
const float queryResolution = m_areaData.m_sampleSpacing;
|
||||
|
||||
AZ::RHI::Size worldSize = AZ::RHI::Size(m_areaData.m_heightmapImageWidth, m_areaData.m_heightmapImageHeight, 1);
|
||||
const AZ::RHI::Size worldSize = AZ::RHI::Size(m_areaData.m_heightmapImageWidth, m_areaData.m_heightmapImageHeight, 1);
|
||||
|
||||
if (!m_areaData.m_heightmapImage || m_areaData.m_heightmapImage->GetDescriptor().m_size != worldSize)
|
||||
{
|
||||
@@ -176,7 +289,7 @@ namespace Terrain
|
||||
height = worldSize.m_height;
|
||||
m_dirtyRegion = worldBounds;
|
||||
|
||||
AZ::Data::Instance<AZ::RPI::AttachmentImagePool> imagePool = AZ::RPI::ImageSystemInterface::Get()->GetSystemAttachmentPool();
|
||||
const AZ::Data::Instance<AZ::RPI::AttachmentImagePool> imagePool = AZ::RPI::ImageSystemInterface::Get()->GetSystemAttachmentPool();
|
||||
AZ::RHI::ImageDescriptor imageDescriptor = AZ::RHI::ImageDescriptor::Create2D(
|
||||
AZ::RHI::ImageBindFlags::ShaderRead, width, height, AZ::RHI::Format::R16_UNORM
|
||||
);
|
||||
@@ -210,9 +323,9 @@ namespace Terrain
|
||||
AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT,
|
||||
&terrainExists);
|
||||
|
||||
float clampedHeight = AZ::GetClamp((terrainHeight - worldBounds.GetMin().GetZ()) / worldBounds.GetExtents().GetZ(), 0.0f, 1.0f);
|
||||
float expandedHeight = AZStd::roundf(clampedHeight * AZStd::numeric_limits<uint16_t>::max());
|
||||
uint16_t uint16Height = aznumeric_cast<uint16_t>(expandedHeight);
|
||||
const float clampedHeight = AZ::GetClamp((terrainHeight - worldBounds.GetMin().GetZ()) / worldBounds.GetExtents().GetZ(), 0.0f, 1.0f);
|
||||
const float expandedHeight = AZStd::roundf(clampedHeight * AZStd::numeric_limits<uint16_t>::max());
|
||||
const uint16_t uint16Height = aznumeric_cast<uint16_t>(expandedHeight);
|
||||
|
||||
pixels.push_back(uint16Height);
|
||||
}
|
||||
@@ -241,118 +354,248 @@ namespace Terrain
|
||||
m_dirtyRegion = AZ::Aabb::CreateNull();
|
||||
}
|
||||
|
||||
void TerrainFeatureProcessor::PrepareMaterialData()
|
||||
{
|
||||
const auto layout = m_materialInstance->GetAsset()->GetObjectSrgLayout();
|
||||
|
||||
m_modelToWorldIndex = layout->FindShaderInputConstantIndex(AZ::Name(ShaderInputs::ModelToWorld));
|
||||
AZ_Error(TerrainFPName, m_modelToWorldIndex.IsValid(), "Failed to find shader input constant %s.", ShaderInputs::ModelToWorld);
|
||||
|
||||
m_terrainDataIndex = layout->FindShaderInputConstantIndex(AZ::Name(ShaderInputs::TerrainData));
|
||||
AZ_Error(TerrainFPName, m_terrainDataIndex.IsValid(), "Failed to find shader input constant %s.", ShaderInputs::TerrainData);
|
||||
|
||||
m_macroMaterialDataIndex = layout->FindShaderInputConstantIndex(AZ::Name(ShaderInputs::MacroMaterialData));
|
||||
AZ_Error(TerrainFPName, m_macroMaterialDataIndex.IsValid(), "Failed to find shader input constant %s.", ShaderInputs::MacroMaterialData);
|
||||
|
||||
m_macroMaterialCountIndex = layout->FindShaderInputConstantIndex(AZ::Name(ShaderInputs::MacroMaterialCount));
|
||||
AZ_Error(TerrainFPName, m_macroMaterialCountIndex.IsValid(), "Failed to find shader input constant %s.", ShaderInputs::MacroMaterialCount);
|
||||
|
||||
m_macroColorMapIndex = layout->FindShaderInputImageIndex(AZ::Name(ShaderInputs::MacroColorMap));
|
||||
AZ_Error(TerrainFPName, m_macroColorMapIndex.IsValid(), "Failed to find shader input constant %s.", ShaderInputs::MacroColorMap);
|
||||
|
||||
m_macroNormalMapIndex = layout->FindShaderInputImageIndex(AZ::Name(ShaderInputs::MacroNormalMap));
|
||||
AZ_Error(TerrainFPName, m_macroNormalMapIndex.IsValid(), "Failed to find shader input constant %s.", ShaderInputs::MacroNormalMap);
|
||||
|
||||
m_heightmapPropertyIndex = m_materialInstance->GetMaterialPropertiesLayout()->FindPropertyIndex(AZ::Name(MaterialInputs::HeightmapImage));
|
||||
AZ_Error(TerrainFPName, m_heightmapPropertyIndex.IsValid(), "Failed to find material input constant %s.", MaterialInputs::HeightmapImage);
|
||||
|
||||
TerrainMacroMaterialRequestBus::EnumerateHandlers(
|
||||
[&](TerrainMacroMaterialRequests* handler)
|
||||
{
|
||||
MaterialInstance macroMaterial;
|
||||
AZ::Aabb bounds;
|
||||
handler->GetTerrainMacroMaterialData(macroMaterial, bounds);
|
||||
AZ::EntityId entityId = *(Terrain::TerrainMacroMaterialRequestBus::GetCurrentBusId());
|
||||
OnTerrainMacroMaterialCreated(entityId, macroMaterial, bounds);
|
||||
return true;
|
||||
}
|
||||
);
|
||||
TerrainMacroMaterialNotificationBus::Handler::BusConnect();
|
||||
}
|
||||
|
||||
void TerrainFeatureProcessor::UpdateMacroMaterialData(MacroMaterialData& macroMaterialData, MaterialInstance material)
|
||||
{
|
||||
// Since we're using an actual macro material instance for now, get the values from it that we care about.
|
||||
const auto materialLayout = material->GetMaterialPropertiesLayout();
|
||||
|
||||
const AZ::RPI::MaterialPropertyIndex macroColorTextureMapIndex = materialLayout->FindPropertyIndex(AZ::Name(MaterialInputs::MacroColorTextureMap));
|
||||
AZ_Error(TerrainFPName, macroColorTextureMapIndex.IsValid(), "Failed to find shader input constant %s.", MaterialInputs::MacroColorTextureMap);
|
||||
|
||||
const AZ::RPI::MaterialPropertyIndex macroNormalTextureMapIndex = materialLayout->FindPropertyIndex(AZ::Name(MaterialInputs::MacroNormalTextureMap));
|
||||
AZ_Error(TerrainFPName, macroNormalTextureMapIndex.IsValid(), "Failed to find shader input constant %s.", MaterialInputs::MacroNormalTextureMap);
|
||||
|
||||
const AZ::RPI::MaterialPropertyIndex macroNormalFlipXIndex = materialLayout->FindPropertyIndex(AZ::Name(MaterialInputs::MacroNormalFlipX));
|
||||
AZ_Error(TerrainFPName, macroNormalFlipXIndex.IsValid(), "Failed to find shader input constant %s.", MaterialInputs::MacroNormalFlipX);
|
||||
|
||||
const AZ::RPI::MaterialPropertyIndex macroNormalFlipYIndex = materialLayout->FindPropertyIndex(AZ::Name(MaterialInputs::MacroNormalFlipY));
|
||||
AZ_Error(TerrainFPName, macroNormalFlipYIndex.IsValid(), "Failed to find shader input constant %s.", MaterialInputs::MacroNormalFlipY);
|
||||
|
||||
const AZ::RPI::MaterialPropertyIndex macroNormalFactorIndex = materialLayout->FindPropertyIndex(AZ::Name(MaterialInputs::MacroNormalFactor));
|
||||
AZ_Error(TerrainFPName, macroNormalFactorIndex.IsValid(), "Failed to find shader input constant %s.", MaterialInputs::MacroNormalFactor);
|
||||
|
||||
macroMaterialData.m_colorImage = material->GetPropertyValue(macroColorTextureMapIndex).GetValue<AZ::Data::Instance<AZ::RPI::Image>>();
|
||||
macroMaterialData.m_normalImage = material->GetPropertyValue(macroNormalTextureMapIndex).GetValue<AZ::Data::Instance<AZ::RPI::Image>>();
|
||||
macroMaterialData.m_normalFlipX = material->GetPropertyValue(macroNormalFlipXIndex).GetValue<bool>();
|
||||
macroMaterialData.m_normalFlipY = material->GetPropertyValue(macroNormalFlipYIndex).GetValue<bool>();
|
||||
macroMaterialData.m_normalFactor = material->GetPropertyValue(macroNormalFactorIndex).GetValue<float>();
|
||||
|
||||
if (macroMaterialData.m_bounds.IsValid())
|
||||
{
|
||||
m_areaData.m_macroMaterialsUpdated = true;
|
||||
}
|
||||
}
|
||||
|
||||
void TerrainFeatureProcessor::ProcessSurfaces(const FeatureProcessor::RenderPacket& process)
|
||||
{
|
||||
AZ_PROFILE_FUNCTION(AzRender);
|
||||
|
||||
const AZ::Aabb& terrainBounds = m_areaData.m_terrainBounds;
|
||||
|
||||
if (!m_areaData.m_terrainBounds.IsValid())
|
||||
if (!terrainBounds.IsValid())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_areaData.m_propertiesDirty && m_materialInstance && m_materialInstance->CanCompile())
|
||||
|
||||
if (m_materialInstance && m_materialInstance->CanCompile())
|
||||
{
|
||||
UpdateTerrainData();
|
||||
|
||||
m_areaData.m_propertiesDirty = false;
|
||||
m_sectorData.clear();
|
||||
|
||||
AZ::RPI::MaterialPropertyIndex heightmapPropertyIndex =
|
||||
m_materialInstance->GetMaterialPropertiesLayout()->FindPropertyIndex(AZ::Name(MaterialInputs::HeightmapImage));
|
||||
AZ_Error(TerrainFPName, heightmapPropertyIndex.IsValid(), "Failed to find material input constant %s.", MaterialInputs::HeightmapImage);
|
||||
AZ::Data::Instance<AZ::RPI::Image> heightmapImage = m_areaData.m_heightmapImage;
|
||||
m_materialInstance->SetPropertyValue(heightmapPropertyIndex, heightmapImage);
|
||||
m_materialInstance->Compile();
|
||||
|
||||
const auto layout = m_materialInstance->GetAsset()->GetObjectSrgLayout();
|
||||
|
||||
m_modelToWorldIndex = layout->FindShaderInputConstantIndex(AZ::Name(ShaderInputs::ModelToWorld));
|
||||
AZ_Error(TerrainFPName, m_modelToWorldIndex.IsValid(), "Failed to find shader input constant %s.", ShaderInputs::ModelToWorld);
|
||||
|
||||
m_terrainDataIndex = layout->FindShaderInputConstantIndex(AZ::Name(ShaderInputs::TerrainData));
|
||||
AZ_Error(TerrainFPName, m_terrainDataIndex.IsValid(), "Failed to find shader input constant %s.", ShaderInputs::TerrainData);
|
||||
|
||||
float xFirstPatchStart =
|
||||
m_areaData.m_terrainBounds.GetMin().GetX() - fmod(m_areaData.m_terrainBounds.GetMin().GetX(), GridMeters);
|
||||
float xLastPatchStart = m_areaData.m_terrainBounds.GetMax().GetX() - fmod(m_areaData.m_terrainBounds.GetMax().GetX(), GridMeters);
|
||||
float yFirstPatchStart =
|
||||
m_areaData.m_terrainBounds.GetMin().GetY() - fmod(m_areaData.m_terrainBounds.GetMin().GetY(), GridMeters);
|
||||
float yLastPatchStart = m_areaData.m_terrainBounds.GetMax().GetY() - fmod(m_areaData.m_terrainBounds.GetMax().GetY(), GridMeters);
|
||||
|
||||
for (float yPatch = yFirstPatchStart; yPatch <= yLastPatchStart; yPatch += GridMeters)
|
||||
if (m_areaData.m_rebuildSectors)
|
||||
{
|
||||
for (float xPatch = xFirstPatchStart; xPatch <= xLastPatchStart; xPatch += GridMeters)
|
||||
// Something about the whole world changed, so the sectors need to be rebuilt
|
||||
|
||||
m_areaData.m_rebuildSectors = false;
|
||||
|
||||
m_sectorData.clear();
|
||||
const float xFirstPatchStart = terrainBounds.GetMin().GetX() - fmod(terrainBounds.GetMin().GetX(), GridMeters);
|
||||
const float xLastPatchStart = terrainBounds.GetMax().GetX() - fmod(terrainBounds.GetMax().GetX(), GridMeters);
|
||||
const float yFirstPatchStart = terrainBounds.GetMin().GetY() - fmod(terrainBounds.GetMin().GetY(), GridMeters);
|
||||
const float yLastPatchStart = terrainBounds.GetMax().GetY() - fmod(terrainBounds.GetMax().GetY(), GridMeters);
|
||||
|
||||
const auto& materialAsset = m_materialInstance->GetAsset();
|
||||
const auto& shaderAsset = materialAsset->GetMaterialTypeAsset()->GetShaderAssetForObjectSrg();
|
||||
|
||||
for (float yPatch = yFirstPatchStart; yPatch <= yLastPatchStart; yPatch += GridMeters)
|
||||
{
|
||||
const auto& materialAsset = m_materialInstance->GetAsset();
|
||||
auto& shaderAsset = materialAsset->GetMaterialTypeAsset()->GetShaderAssetForObjectSrg();
|
||||
auto objectSrg = AZ::RPI::ShaderResourceGroup::Create(shaderAsset, materialAsset->GetObjectSrgLayout()->GetName());
|
||||
if (!objectSrg)
|
||||
for (float xPatch = xFirstPatchStart; xPatch <= xLastPatchStart; xPatch += GridMeters)
|
||||
{
|
||||
AZ_Warning("TerrainFeatureProcessor", false, "Failed to create a new shader resource group, skipping.");
|
||||
continue;
|
||||
}
|
||||
|
||||
{ // Update SRG
|
||||
|
||||
AZStd::array<float, 2> uvMin = { 0.0f, 0.0f };
|
||||
AZStd::array<float, 2> uvMax = { 1.0f, 1.0f };
|
||||
|
||||
uvMin[0] = (float)((xPatch - m_areaData.m_terrainBounds.GetMin().GetX()) / m_areaData.m_terrainBounds.GetXExtent());
|
||||
uvMin[1] = (float)((yPatch - m_areaData.m_terrainBounds.GetMin().GetY()) / m_areaData.m_terrainBounds.GetYExtent());
|
||||
|
||||
uvMax[0] =
|
||||
(float)(((xPatch + GridMeters) - m_areaData.m_terrainBounds.GetMin().GetX()) / m_areaData.m_terrainBounds.GetXExtent());
|
||||
uvMax[1] =
|
||||
(float)(((yPatch + GridMeters) - m_areaData.m_terrainBounds.GetMin().GetY()) / m_areaData.m_terrainBounds.GetYExtent());
|
||||
|
||||
AZStd::array<float, 2> uvStep =
|
||||
auto objectSrg = AZ::RPI::ShaderResourceGroup::Create(shaderAsset, materialAsset->GetObjectSrgLayout()->GetName());
|
||||
if (!objectSrg)
|
||||
{
|
||||
1.0f / m_areaData.m_heightmapImageWidth, 1.0f / m_areaData.m_heightmapImageHeight,
|
||||
};
|
||||
|
||||
AZ::Transform transform = m_areaData.m_transform;
|
||||
transform.SetTranslation(xPatch, yPatch, m_areaData.m_transform.GetTranslation().GetZ());
|
||||
|
||||
AZ::Matrix3x4 matrix3x4 = AZ::Matrix3x4::CreateFromTransform(transform);
|
||||
|
||||
objectSrg->SetConstant(m_modelToWorldIndex, matrix3x4);
|
||||
|
||||
ShaderTerrainData terrainDataForSrg;
|
||||
terrainDataForSrg.m_sampleSpacing = m_areaData.m_sampleSpacing;
|
||||
terrainDataForSrg.m_heightScale = m_areaData.m_heightScale;
|
||||
terrainDataForSrg.m_uvMin = uvMin;
|
||||
terrainDataForSrg.m_uvMax = uvMax;
|
||||
terrainDataForSrg.m_uvStep = uvStep;
|
||||
objectSrg->SetConstant(m_terrainDataIndex, terrainDataForSrg);
|
||||
|
||||
objectSrg->Compile();
|
||||
}
|
||||
|
||||
m_sectorData.push_back();
|
||||
SectorData& sectorData = m_sectorData.back();
|
||||
|
||||
for (auto& lod : m_patchModel->GetLods())
|
||||
{
|
||||
AZ::RPI::ModelLod& modelLod = *lod.get();
|
||||
sectorData.m_drawPackets.emplace_back(modelLod, 0, m_materialInstance, objectSrg);
|
||||
AZ::RPI::MeshDrawPacket& drawPacket = sectorData.m_drawPackets.back();
|
||||
|
||||
// set the shader option to select forward pass IBL specular if necessary
|
||||
if (!drawPacket.SetShaderOption(AZ::Name("o_meshUseForwardPassIBLSpecular"), AZ::RPI::ShaderOptionValue{ false }))
|
||||
{
|
||||
AZ_Warning("MeshDrawPacket", false, "Failed to set o_meshUseForwardPassIBLSpecular on mesh draw packet");
|
||||
AZ_Warning("TerrainFeatureProcessor", false, "Failed to create a new shader resource group, skipping.");
|
||||
continue;
|
||||
}
|
||||
uint8_t stencilRef = AZ::Render::StencilRefs::UseDiffuseGIPass | AZ::Render::StencilRefs::UseIBLSpecularPass;
|
||||
drawPacket.SetStencilRef(stencilRef);
|
||||
drawPacket.Update(*GetParentScene(), true);
|
||||
|
||||
m_sectorData.push_back();
|
||||
SectorData& sectorData = m_sectorData.back();
|
||||
|
||||
for (auto& lod : m_patchModel->GetLods())
|
||||
{
|
||||
AZ::RPI::ModelLod& modelLod = *lod.get();
|
||||
sectorData.m_drawPackets.emplace_back(modelLod, 0, m_materialInstance, objectSrg);
|
||||
AZ::RPI::MeshDrawPacket& drawPacket = sectorData.m_drawPackets.back();
|
||||
|
||||
// set the shader option to select forward pass IBL specular if necessary
|
||||
if (!drawPacket.SetShaderOption(AZ::Name("o_meshUseForwardPassIBLSpecular"), AZ::RPI::ShaderOptionValue{ false }))
|
||||
{
|
||||
AZ_Warning("MeshDrawPacket", false, "Failed to set o_meshUseForwardPassIBLSpecular on mesh draw packet");
|
||||
}
|
||||
const uint8_t stencilRef = AZ::Render::StencilRefs::UseDiffuseGIPass | AZ::Render::StencilRefs::UseIBLSpecularPass;
|
||||
drawPacket.SetStencilRef(stencilRef);
|
||||
drawPacket.Update(*GetParentScene(), true);
|
||||
}
|
||||
|
||||
sectorData.m_aabb =
|
||||
AZ::Aabb::CreateFromMinMax(
|
||||
AZ::Vector3(xPatch, yPatch, terrainBounds.GetMin().GetZ()),
|
||||
AZ::Vector3(xPatch + GridMeters, yPatch + GridMeters, terrainBounds.GetMax().GetZ())
|
||||
);
|
||||
sectorData.m_srg = objectSrg;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_areaData.m_macroMaterialsUpdated)
|
||||
{
|
||||
// sectors were rebuilt, so any cached macro material data needs to be regenerated
|
||||
for (SectorData& sectorData : m_sectorData)
|
||||
{
|
||||
for (MacroMaterialData& macroMaterialData : m_macroMaterials.GetDataVector())
|
||||
{
|
||||
if (macroMaterialData.m_bounds.Overlaps(sectorData.m_aabb))
|
||||
{
|
||||
sectorData.m_macroMaterials.push_back(m_macroMaterials.GetIndexForData(¯oMaterialData));
|
||||
if (sectorData.m_macroMaterials.size() == MaxMaterialsPerSector)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (m_areaData.m_heightmapUpdated)
|
||||
{
|
||||
UpdateTerrainData();
|
||||
|
||||
const AZ::Data::Instance<AZ::RPI::Image> heightmapImage = m_areaData.m_heightmapImage;
|
||||
m_materialInstance->SetPropertyValue(m_heightmapPropertyIndex, heightmapImage);
|
||||
m_materialInstance->Compile();
|
||||
}
|
||||
|
||||
if (m_areaData.m_heightmapUpdated || m_areaData.m_macroMaterialsUpdated)
|
||||
{
|
||||
// Currently when anything in the heightmap changes we're updating all the srgs, but this could probably
|
||||
// be optimized to only update the srgs that changed.
|
||||
|
||||
m_areaData.m_heightmapUpdated = false;
|
||||
m_areaData.m_macroMaterialsUpdated = false;
|
||||
|
||||
for (SectorData& sectorData : m_sectorData)
|
||||
{
|
||||
ShaderTerrainData terrainDataForSrg;
|
||||
|
||||
const float xPatch = sectorData.m_aabb.GetMin().GetX();
|
||||
const float yPatch = sectorData.m_aabb.GetMin().GetY();
|
||||
|
||||
terrainDataForSrg.m_uvMin = {
|
||||
(xPatch - terrainBounds.GetMin().GetX()) / terrainBounds.GetXExtent(),
|
||||
(yPatch - terrainBounds.GetMin().GetY()) / terrainBounds.GetYExtent()
|
||||
};
|
||||
|
||||
terrainDataForSrg.m_uvMax = {
|
||||
((xPatch + GridMeters) - terrainBounds.GetMin().GetX()) / terrainBounds.GetXExtent(),
|
||||
((yPatch + GridMeters) - terrainBounds.GetMin().GetY()) / terrainBounds.GetYExtent()
|
||||
};
|
||||
|
||||
terrainDataForSrg.m_uvStep =
|
||||
{
|
||||
1.0f / m_areaData.m_heightmapImageWidth,
|
||||
1.0f / m_areaData.m_heightmapImageHeight,
|
||||
};
|
||||
|
||||
AZ::Transform transform = m_areaData.m_transform;
|
||||
transform.SetTranslation(xPatch, yPatch, m_areaData.m_transform.GetTranslation().GetZ());
|
||||
|
||||
terrainDataForSrg.m_sampleSpacing = m_areaData.m_sampleSpacing;
|
||||
terrainDataForSrg.m_heightScale = terrainBounds.GetZExtent();
|
||||
|
||||
sectorData.m_srg->SetConstant(m_terrainDataIndex, terrainDataForSrg);
|
||||
|
||||
AZStd::array<ShaderMacroMaterialData, MaxMaterialsPerSector> macroMaterialData;
|
||||
for (uint32_t i = 0; i < sectorData.m_macroMaterials.size(); ++i)
|
||||
{
|
||||
const MacroMaterialData& materialData = m_macroMaterials.GetData(sectorData.m_macroMaterials.at(i));
|
||||
ShaderMacroMaterialData& shaderData = macroMaterialData.at(i);
|
||||
const AZ::Aabb& materialBounds = materialData.m_bounds;
|
||||
|
||||
shaderData.m_uvMin = {
|
||||
(xPatch - materialBounds.GetMin().GetX()) / materialBounds.GetXExtent(),
|
||||
(yPatch - materialBounds.GetMin().GetY()) / materialBounds.GetYExtent()
|
||||
};
|
||||
shaderData.m_uvMax = {
|
||||
((xPatch + GridMeters) - materialBounds.GetMin().GetX()) / materialBounds.GetXExtent(),
|
||||
((yPatch + GridMeters) - materialBounds.GetMin().GetY()) / materialBounds.GetYExtent()
|
||||
};
|
||||
shaderData.m_normalFactor = materialData.m_normalFactor;
|
||||
shaderData.m_flipNormalX = materialData.m_normalFlipX;
|
||||
shaderData.m_flipNormalY = materialData.m_normalFlipY;
|
||||
|
||||
const AZ::RHI::ImageView* colorImageView = materialData.m_colorImage ? materialData.m_colorImage->GetImageView() : nullptr;
|
||||
sectorData.m_srg->SetImageView(m_macroColorMapIndex, colorImageView, i);
|
||||
|
||||
const AZ::RHI::ImageView* normalImageView = materialData.m_normalImage ? materialData.m_normalImage->GetImageView() : nullptr;
|
||||
sectorData.m_srg->SetImageView(m_macroNormalMapIndex, normalImageView, i);
|
||||
|
||||
// set flags for which images are used.
|
||||
shaderData.m_mapsInUse = (colorImageView ? ColorImageUsed : 0) | (normalImageView ? NormalImageUsed : 0);
|
||||
}
|
||||
|
||||
sectorData.m_aabb =
|
||||
AZ::Aabb::CreateFromMinMax(
|
||||
AZ::Vector3(xPatch, yPatch, m_areaData.m_terrainBounds.GetMin().GetZ()),
|
||||
AZ::Vector3(xPatch + GridMeters, yPatch + GridMeters, m_areaData.m_terrainBounds.GetMax().GetZ())
|
||||
);
|
||||
sectorData.m_srg = objectSrg;
|
||||
sectorData.m_srg->SetConstantArray(m_macroMaterialDataIndex, macroMaterialData);
|
||||
sectorData.m_srg->SetConstant(m_macroMaterialCountIndex, aznumeric_cast<uint32_t>(sectorData.m_macroMaterials.size()));
|
||||
|
||||
const AZ::Matrix3x4 matrix3x4 = AZ::Matrix3x4::CreateFromTransform(transform);
|
||||
sectorData.m_srg->SetConstant(m_modelToWorldIndex, matrix3x4);
|
||||
|
||||
sectorData.m_srg->Compile();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -366,12 +609,20 @@ namespace Terrain
|
||||
{
|
||||
if ((view->GetUsageFlags() & AZ::RPI::View::UsageFlags::UsageCamera) > 0)
|
||||
{
|
||||
AZ::Vector3 cameraPosition = view->GetCameraTransform().GetTranslation();
|
||||
AZ::Vector2 cameraPositionXY = AZ::Vector2(cameraPosition.GetX(), cameraPosition.GetY());
|
||||
AZ::Vector2 sectorCenterXY = AZ::Vector2(sectorData.m_aabb.GetCenter().GetX(), sectorData.m_aabb.GetCenter().GetY());
|
||||
const AZ::Vector3 cameraPosition = view->GetCameraTransform().GetTranslation();
|
||||
const AZ::Vector2 cameraPositionXY = AZ::Vector2(cameraPosition.GetX(), cameraPosition.GetY());
|
||||
const AZ::Vector2 sectorCenterXY = AZ::Vector2(sectorData.m_aabb.GetCenter().GetX(), sectorData.m_aabb.GetCenter().GetY());
|
||||
|
||||
float sectorDistance = sectorCenterXY.GetDistance(cameraPositionXY);
|
||||
float lodForCamera = floorf(AZ::GetMax(0.0f, log2f(sectorDistance / (GridMeters * 4.0f))));
|
||||
const float sectorDistance = sectorCenterXY.GetDistance(cameraPositionXY);
|
||||
|
||||
// This will be configurable later
|
||||
const float minDistanceForLod0 = (GridMeters * 4.0f);
|
||||
|
||||
// For every distance doubling beyond a minDistanceForLod0, we only need half the mesh density. Each LOD
|
||||
// is exactly half the resolution of the last.
|
||||
const float lodForCamera = floorf(AZ::GetMax(0.0f, log2f(sectorDistance / minDistanceForLod0)));
|
||||
|
||||
// All cameras should render the same LOD so effects like shadows are consistent.
|
||||
lodChoice = AZ::GetMin(lodChoice, aznumeric_cast<uint8_t>(lodForCamera));
|
||||
}
|
||||
}
|
||||
@@ -382,7 +633,7 @@ namespace Terrain
|
||||
AZ::Frustum viewFrustum = AZ::Frustum::CreateFromMatrixColumnMajor(view->GetWorldToClipMatrix());
|
||||
if (viewFrustum.IntersectAabb(sectorData.m_aabb) != AZ::IntersectResult::Exterior)
|
||||
{
|
||||
uint8_t lodToRender = AZ::GetMin(lodChoice, aznumeric_cast<uint8_t>(sectorData.m_drawPackets.size() - 1));
|
||||
const uint8_t lodToRender = AZ::GetMin(lodChoice, aznumeric_cast<uint8_t>(sectorData.m_drawPackets.size() - 1));
|
||||
view->AddDrawPacket(sectorData.m_drawPackets.at(lodToRender).GetRHIDrawPacket());
|
||||
}
|
||||
}
|
||||
@@ -395,9 +646,8 @@ namespace Terrain
|
||||
patchdata.m_uvs.clear();
|
||||
patchdata.m_indices.clear();
|
||||
|
||||
uint16_t gridVertices = gridSize + 1; // For m_gridSize quads, (m_gridSize + 1) vertices are needed.
|
||||
size_t size = gridVertices * gridVertices;
|
||||
size *= size;
|
||||
const uint16_t gridVertices = gridSize + 1; // For m_gridSize quads, (m_gridSize + 1) vertices are needed.
|
||||
const size_t size = gridVertices * gridVertices;
|
||||
|
||||
patchdata.m_positions.reserve(size);
|
||||
patchdata.m_uvs.reserve(size);
|
||||
@@ -417,10 +667,10 @@ namespace Terrain
|
||||
{
|
||||
for (uint16_t x = 0; x < gridSize; ++x)
|
||||
{
|
||||
uint16_t topLeft = y * gridVertices + x;
|
||||
uint16_t topRight = topLeft + 1;
|
||||
uint16_t bottomLeft = (y + 1) * gridVertices + x;
|
||||
uint16_t bottomRight = bottomLeft + 1;
|
||||
const uint16_t topLeft = y * gridVertices + x;
|
||||
const uint16_t topRight = topLeft + 1;
|
||||
const uint16_t bottomLeft = (y + 1) * gridVertices + x;
|
||||
const uint16_t bottomRight = bottomLeft + 1;
|
||||
|
||||
patchdata.m_indices.emplace_back(topLeft);
|
||||
patchdata.m_indices.emplace_back(topRight);
|
||||
@@ -469,14 +719,14 @@ namespace Terrain
|
||||
PatchData patchData;
|
||||
InitializeTerrainPatch(gridSize, gridSpacing, patchData);
|
||||
|
||||
auto positionBufferViewDesc = AZ::RHI::BufferViewDescriptor::CreateTyped(0, aznumeric_cast<uint32_t>(patchData.m_positions.size()), AZ::RHI::Format::R32G32_FLOAT);
|
||||
auto positionsOutcome = CreateBufferAsset(patchData.m_positions.data(), positionBufferViewDesc, "TerrainPatchPositions");
|
||||
const auto positionBufferViewDesc = AZ::RHI::BufferViewDescriptor::CreateTyped(0, aznumeric_cast<uint32_t>(patchData.m_positions.size()), AZ::RHI::Format::R32G32_FLOAT);
|
||||
const auto positionsOutcome = CreateBufferAsset(patchData.m_positions.data(), positionBufferViewDesc, "TerrainPatchPositions");
|
||||
|
||||
auto uvBufferViewDesc = AZ::RHI::BufferViewDescriptor::CreateTyped(0, aznumeric_cast<uint32_t>(patchData.m_uvs.size()), AZ::RHI::Format::R32G32_FLOAT);
|
||||
auto uvsOutcome = CreateBufferAsset(patchData.m_uvs.data(), uvBufferViewDesc, "TerrainPatchUvs");
|
||||
const auto uvBufferViewDesc = AZ::RHI::BufferViewDescriptor::CreateTyped(0, aznumeric_cast<uint32_t>(patchData.m_uvs.size()), AZ::RHI::Format::R32G32_FLOAT);
|
||||
const auto uvsOutcome = CreateBufferAsset(patchData.m_uvs.data(), uvBufferViewDesc, "TerrainPatchUvs");
|
||||
|
||||
auto indexBufferViewDesc = AZ::RHI::BufferViewDescriptor::CreateTyped(0, aznumeric_cast<uint32_t>(patchData.m_indices.size()), AZ::RHI::Format::R16_UINT);
|
||||
auto indicesOutcome = CreateBufferAsset(patchData.m_indices.data(), indexBufferViewDesc, "TerrainPatchIndices");
|
||||
const auto indexBufferViewDesc = AZ::RHI::BufferViewDescriptor::CreateTyped(0, aznumeric_cast<uint32_t>(patchData.m_indices.size()), AZ::RHI::Format::R16_UINT);
|
||||
const auto indicesOutcome = CreateBufferAsset(patchData.m_indices.data(), indexBufferViewDesc, "TerrainPatchIndices");
|
||||
|
||||
if (!positionsOutcome.IsSuccess() || !uvsOutcome.IsSuccess() || !indicesOutcome.IsSuccess())
|
||||
{
|
||||
@@ -514,7 +764,7 @@ namespace Terrain
|
||||
return success;
|
||||
}
|
||||
|
||||
void TerrainFeatureProcessor::OnMaterialReinitialized([[maybe_unused]] const AZ::Data::Instance<AZ::RPI::Material>& material)
|
||||
void TerrainFeatureProcessor::OnMaterialReinitialized([[maybe_unused]] const MaterialInstance& material)
|
||||
{
|
||||
for (auto& sectorData : m_sectorData)
|
||||
{
|
||||
@@ -530,4 +780,57 @@ namespace Terrain
|
||||
// This will control the max rendering size. Actual terrain size can be much
|
||||
// larger but this will limit how much is rendered.
|
||||
}
|
||||
|
||||
TerrainFeatureProcessor::MacroMaterialData* TerrainFeatureProcessor::FindMacroMaterial(AZ::EntityId entityId)
|
||||
{
|
||||
for (MacroMaterialData& data : m_macroMaterials.GetDataVector())
|
||||
{
|
||||
if (data.m_entityId == entityId)
|
||||
{
|
||||
return &data;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
TerrainFeatureProcessor::MacroMaterialData& TerrainFeatureProcessor::FindOrCreateMacroMaterial(AZ::EntityId entityId)
|
||||
{
|
||||
MacroMaterialData* dataPtr = FindMacroMaterial(entityId);
|
||||
if (dataPtr != nullptr)
|
||||
{
|
||||
return *dataPtr;
|
||||
}
|
||||
|
||||
const uint16_t slotId = m_macroMaterials.GetFreeSlotIndex();
|
||||
AZ_Assert(slotId != m_macroMaterials.NoFreeSlot, "Ran out of indices for macro materials");
|
||||
|
||||
MacroMaterialData& data = m_macroMaterials.GetData(slotId);
|
||||
data.m_entityId = entityId;
|
||||
return data;
|
||||
}
|
||||
|
||||
void TerrainFeatureProcessor::RemoveMacroMaterial(AZ::EntityId entityId)
|
||||
{
|
||||
for (MacroMaterialData& data : m_macroMaterials.GetDataVector())
|
||||
{
|
||||
if (data.m_entityId == entityId)
|
||||
{
|
||||
m_macroMaterials.RemoveData(&data);
|
||||
return;
|
||||
}
|
||||
}
|
||||
AZ_Assert(false, "Entity Id not found in m_macroMaterials.")
|
||||
}
|
||||
|
||||
template<typename Callback>
|
||||
void TerrainFeatureProcessor::ForOverlappingSectors(const AZ::Aabb& bounds, Callback callback)
|
||||
{
|
||||
for (SectorData& sectorData : m_sectorData)
|
||||
{
|
||||
if (sectorData.m_aabb.Overlaps(bounds))
|
||||
{
|
||||
callback(sectorData);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,11 +11,13 @@
|
||||
#include <AzCore/Component/Component.h>
|
||||
|
||||
#include <AzFramework/Terrain/TerrainDataRequestBus.h>
|
||||
#include <TerrainRenderer/TerrainMacroMaterialBus.h>
|
||||
|
||||
#include <Atom/RPI.Public/FeatureProcessor.h>
|
||||
#include <Atom/RPI.Public/Image/AttachmentImage.h>
|
||||
#include <Atom/RPI.Public/MeshDrawPacket.h>
|
||||
#include <Atom/RPI.Public/Material/MaterialReloadNotificationBus.h>
|
||||
#include <Atom/Feature/Utils/IndexedDataVector.h>
|
||||
|
||||
namespace AZ::RPI
|
||||
{
|
||||
@@ -25,6 +27,7 @@ namespace AZ::RPI
|
||||
}
|
||||
class Material;
|
||||
class Model;
|
||||
class StreamingImage;
|
||||
}
|
||||
|
||||
namespace Terrain
|
||||
@@ -33,6 +36,7 @@ namespace Terrain
|
||||
: public AZ::RPI::FeatureProcessor
|
||||
, private AZ::RPI::MaterialReloadNotificationBus::Handler
|
||||
, private AzFramework::Terrain::TerrainDataNotificationBus::Handler
|
||||
, private TerrainMacroMaterialNotificationBus::Handler
|
||||
{
|
||||
public:
|
||||
AZ_RTTI(TerrainFeatureProcessor, "{D7DAC1F9-4A9F-4D3C-80AE-99579BF8AB1C}", AZ::RPI::FeatureProcessor);
|
||||
@@ -52,6 +56,15 @@ namespace Terrain
|
||||
void SetWorldSize(AZ::Vector2 sizeInMeters);
|
||||
|
||||
private:
|
||||
|
||||
using MaterialInstance = AZ::Data::Instance<AZ::RPI::Material>;
|
||||
static constexpr uint32_t MaxMaterialsPerSector = 4;
|
||||
|
||||
enum MacroMaterialFlags
|
||||
{
|
||||
ColorImageUsed = 0b01,
|
||||
NormalImageUsed = 0b10,
|
||||
};
|
||||
|
||||
struct ShaderTerrainData // Must align with struct in Object Srg
|
||||
{
|
||||
@@ -61,7 +74,17 @@ namespace Terrain
|
||||
float m_sampleSpacing;
|
||||
float m_heightScale;
|
||||
};
|
||||
|
||||
|
||||
struct ShaderMacroMaterialData
|
||||
{
|
||||
AZStd::array<float, 2> m_uvMin;
|
||||
AZStd::array<float, 2> m_uvMax;
|
||||
float m_normalFactor;
|
||||
uint32_t m_flipNormalX{ 0 }; // bool in shader
|
||||
uint32_t m_flipNormalY{ 0 }; // bool in shader
|
||||
uint32_t m_mapsInUse{ 0b00 }; // 0b01 = color, 0b10 = normal
|
||||
};
|
||||
|
||||
struct VertexPosition
|
||||
{
|
||||
float m_posx;
|
||||
@@ -81,21 +104,56 @@ namespace Terrain
|
||||
AZStd::vector<uint16_t> m_indices;
|
||||
};
|
||||
|
||||
struct SectorData
|
||||
{
|
||||
AZ::Data::Instance<AZ::RPI::ShaderResourceGroup> m_srg; // Hold on to ref so it's not dropped
|
||||
AZ::Aabb m_aabb;
|
||||
AZStd::fixed_vector<AZ::RPI::MeshDrawPacket, AZ::RPI::ModelLodAsset::LodCountMax> m_drawPackets;
|
||||
AZStd::fixed_vector<uint16_t, MaxMaterialsPerSector> m_macroMaterials;
|
||||
};
|
||||
|
||||
struct MacroMaterialData
|
||||
{
|
||||
AZ::EntityId m_entityId;
|
||||
AZ::Aabb m_bounds = AZ::Aabb::CreateNull();
|
||||
|
||||
AZ::Data::Instance<AZ::RPI::Image> m_colorImage;
|
||||
AZ::Data::Instance<AZ::RPI::Image> m_normalImage;
|
||||
bool m_normalFlipX{ false };
|
||||
bool m_normalFlipY{ false };
|
||||
float m_normalFactor{ 0.0f };
|
||||
};
|
||||
|
||||
// AZ::RPI::MaterialReloadNotificationBus::Handler overrides...
|
||||
void OnMaterialReinitialized(const AZ::Data::Instance<AZ::RPI::Material>& material) override;
|
||||
void OnMaterialReinitialized(const MaterialInstance& material) override;
|
||||
|
||||
// AzFramework::Terrain::TerrainDataNotificationBus overrides...
|
||||
void OnTerrainDataDestroyBegin() override;
|
||||
void OnTerrainDataChanged(const AZ::Aabb& dirtyRegion, TerrainDataChangedMask dataChangedMask) override;
|
||||
|
||||
// TerrainMacroMaterialNotificationBus overrides...
|
||||
void OnTerrainMacroMaterialCreated(AZ::EntityId entityId, MaterialInstance material, const AZ::Aabb& region) override;
|
||||
void OnTerrainMacroMaterialChanged(AZ::EntityId entityId, MaterialInstance material) override;
|
||||
void OnTerrainMacroMaterialRegionChanged(AZ::EntityId entityId, const AZ::Aabb& oldRegion, const AZ::Aabb& newRegion) override;
|
||||
void OnTerrainMacroMaterialDestroyed(AZ::EntityId entityId) override;
|
||||
|
||||
void Initialize();
|
||||
void InitializeTerrainPatch(uint16_t gridSize, float gridSpacing, PatchData& patchdata);
|
||||
bool InitializePatchModel();
|
||||
|
||||
void UpdateTerrainData();
|
||||
void PrepareMaterialData();
|
||||
void UpdateMacroMaterialData(MacroMaterialData& macroMaterialData, MaterialInstance material);
|
||||
|
||||
void ProcessSurfaces(const FeatureProcessor::RenderPacket& process);
|
||||
|
||||
MacroMaterialData* FindMacroMaterial(AZ::EntityId entityId);
|
||||
MacroMaterialData& FindOrCreateMacroMaterial(AZ::EntityId entityId);
|
||||
void RemoveMacroMaterial(AZ::EntityId entityId);
|
||||
|
||||
template<typename Callback>
|
||||
void ForOverlappingSectors(const AZ::Aabb& bounds, Callback callback);
|
||||
|
||||
AZ::Outcome<AZ::Data::Asset<AZ::RPI::BufferAsset>> CreateBufferAsset(
|
||||
const void* data, const AZ::RHI::BufferViewDescriptor& bufferViewDescriptor, const AZStd::string& bufferName);
|
||||
|
||||
@@ -105,10 +163,15 @@ namespace Terrain
|
||||
static constexpr float GridMeters{ GridSpacing * GridSize };
|
||||
|
||||
AZStd::unique_ptr<AZ::RPI::AssetUtils::AsyncAssetLoader> m_materialAssetLoader;
|
||||
AZ::Data::Instance<AZ::RPI::Material> m_materialInstance;
|
||||
MaterialInstance m_materialInstance;
|
||||
|
||||
AZ::RHI::ShaderInputConstantIndex m_modelToWorldIndex;
|
||||
AZ::RHI::ShaderInputConstantIndex m_terrainDataIndex;
|
||||
AZ::RHI::ShaderInputConstantIndex m_macroMaterialDataIndex;
|
||||
AZ::RHI::ShaderInputConstantIndex m_macroMaterialCountIndex;
|
||||
AZ::RHI::ShaderInputImageIndex m_macroColorMapIndex;
|
||||
AZ::RHI::ShaderInputImageIndex m_macroNormalMapIndex;
|
||||
AZ::RPI::MaterialPropertyIndex m_heightmapPropertyIndex;
|
||||
|
||||
AZ::Data::Instance<AZ::RPI::Model> m_patchModel;
|
||||
|
||||
@@ -117,26 +180,22 @@ namespace Terrain
|
||||
{
|
||||
AZ::Transform m_transform{ AZ::Transform::CreateIdentity() };
|
||||
AZ::Aabb m_terrainBounds{ AZ::Aabb::CreateNull() };
|
||||
float m_heightScale{ 0.0f };
|
||||
AZ::Data::Instance<AZ::RPI::AttachmentImage> m_heightmapImage;
|
||||
uint32_t m_heightmapImageWidth{ 0 };
|
||||
uint32_t m_heightmapImageHeight{ 0 };
|
||||
uint32_t m_updateWidth{ 0 };
|
||||
uint32_t m_updateHeight{ 0 };
|
||||
bool m_propertiesDirty{ true };
|
||||
float m_sampleSpacing{ 0.0f };
|
||||
bool m_heightmapUpdated{ true };
|
||||
bool m_macroMaterialsUpdated{ true };
|
||||
bool m_rebuildSectors{ true };
|
||||
};
|
||||
|
||||
TerrainAreaData m_areaData;
|
||||
AZ::Aabb m_dirtyRegion{ AZ::Aabb::CreateNull() };
|
||||
|
||||
struct SectorData
|
||||
{
|
||||
AZ::Data::Instance<AZ::RPI::ShaderResourceGroup> m_srg; // Hold on to ref so it's not dropped
|
||||
AZ::Aabb m_aabb;
|
||||
AZStd::fixed_vector<AZ::RPI::MeshDrawPacket, AZ::RPI::ModelLodAsset::LodCountMax> m_drawPackets;
|
||||
};
|
||||
|
||||
AZStd::vector<SectorData> m_sectorData;
|
||||
|
||||
AZ::Render::IndexedDataVector<MacroMaterialData> m_macroMaterials;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -106,7 +106,7 @@
|
||||
// "exclude": "mac"
|
||||
// }
|
||||
|
||||
"ScanFolder Game": {
|
||||
"ScanFolder Project/Assets": {
|
||||
"watch": "@PROJECTROOT@",
|
||||
"display": "@PROJECTNAME@",
|
||||
"recursive": 1,
|
||||
@@ -129,6 +129,11 @@
|
||||
"order": 30000,
|
||||
"include": "tools,renderer"
|
||||
},
|
||||
"ScanFolder Engine/Registry": {
|
||||
"watch": "@ENGINEROOT@/Registry",
|
||||
"recursive": 1,
|
||||
"order": 40000
|
||||
},
|
||||
|
||||
// Excludes files that match the pattern or glob
|
||||
// if you use a pattern, remember to escape your backslashes (\\)
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
"remote_port": 45643,
|
||||
"connect_to_remote": 0,
|
||||
"windows_connect_to_remote": 1,
|
||||
"linux_connect_to_remote": 0,
|
||||
"linux_connect_to_remote": 1,
|
||||
"provo_connect_to_remote": 1,
|
||||
"salem_connect_to_remote": 0,
|
||||
"jasper_connect_to_remote": 0,
|
||||
@@ -29,7 +29,7 @@
|
||||
"salem_wait_for_connect": 0,
|
||||
"jasper_wait_for_connect": 0,
|
||||
"windows_wait_for_connect": 1,
|
||||
"linux_wait_for_connect": 0,
|
||||
"linux_wait_for_connect": 1,
|
||||
"android_wait_for_connect": 0,
|
||||
"ios_wait_for_connect": 0,
|
||||
"mac_wait_for_connect": 0,
|
||||
|
||||
@@ -1,14 +1,21 @@
|
||||
{
|
||||
"Amazon":
|
||||
{
|
||||
"${Name}.Assets":
|
||||
{
|
||||
"SourcePaths":
|
||||
[
|
||||
"Assets",
|
||||
"ShaderLib",
|
||||
"Shaders"
|
||||
]
|
||||
"Amazon": {
|
||||
"AssetProcessor": {
|
||||
"ScanFolder Project/ShaderLib": {
|
||||
"watch": "@PROJECTROOT@/ShaderLib",
|
||||
"recursive": 1,
|
||||
"order": 1
|
||||
},
|
||||
"ScanFolder Project/Shaders": {
|
||||
"watch": "@PROJECTROOT@/Shaders",
|
||||
"recurisve": 1,
|
||||
"order": 2
|
||||
},
|
||||
"ScanFolder Project/Registry": {
|
||||
"watch": "@PROJECTROOT@/Registry",
|
||||
"recursive": 1,
|
||||
"order": 3
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,21 @@
|
||||
{
|
||||
"Amazon":
|
||||
{
|
||||
"${Name}.Assets":
|
||||
{
|
||||
"SourcePaths":
|
||||
[
|
||||
"Assets",
|
||||
"ShaderLib",
|
||||
"Shaders"
|
||||
]
|
||||
"Amazon": {
|
||||
"AssetProcessor": {
|
||||
"ScanFolder Project/ShaderLib": {
|
||||
"watch": "@PROJECTROOT@/ShaderLib",
|
||||
"recursive": 1,
|
||||
"order": 1
|
||||
},
|
||||
"ScanFolder Project/Shaders": {
|
||||
"watch": "@PROJECTROOT@/Shaders",
|
||||
"recurisve": 1,
|
||||
"order": 2
|
||||
},
|
||||
"ScanFolder Project/Registry": {
|
||||
"watch": "@PROJECTROOT@/Registry",
|
||||
"recursive": 1,
|
||||
"order": 3
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -488,6 +488,9 @@ class AssetProcessor(object):
|
||||
logger.info(f"Launching AP with command: {command}")
|
||||
try:
|
||||
self._ap_proc = subprocess.Popen(command, cwd=ap_exe_path, env=process_utils.get_display_env())
|
||||
time.sleep(1)
|
||||
if self._ap_proc.poll() is not None:
|
||||
raise AssetProcessorError(f"AssetProcessor immediately quit with errorcode {self._ap_proc.returncode}")
|
||||
|
||||
if accept_input:
|
||||
self.connect_control()
|
||||
@@ -506,10 +509,11 @@ class AssetProcessor(object):
|
||||
logger.exception("Exception while starting Asset Processor", be)
|
||||
# clean up to avoid leaking open AP process to future tests
|
||||
try:
|
||||
self._ap_proc.kill()
|
||||
if self._ap_proc:
|
||||
self._ap_proc.kill()
|
||||
except Exception as ex:
|
||||
logger.exception("Ignoring exception while trying to terminate Asset Processor", ex)
|
||||
raise # raise whatever prompted us to clean up
|
||||
raise be # raise whatever prompted us to clean up
|
||||
|
||||
def connect_listen(self, timeout=DEFAULT_TIMEOUT_SECONDS):
|
||||
# Wait for the AP we launched to be ready to accept a connection
|
||||
|
||||
@@ -45,6 +45,7 @@ class TestAssetProcessor(object):
|
||||
@mock.patch('subprocess.Popen')
|
||||
@mock.patch('ly_test_tools.o3de.asset_processor.AssetProcessor.connect_socket')
|
||||
@mock.patch('ly_test_tools.o3de.asset_processor.ASSET_PROCESSOR_PLATFORM_MAP', {'foo': 'bar'})
|
||||
@mock.patch('time.sleep', mock.MagicMock())
|
||||
def test_Start_NoneRunning_ProcStarted(self, mock_connect, mock_popen, mock_workspace):
|
||||
mock_ap_path = 'mock_ap_path'
|
||||
mock_workspace.asset_processor_platform = 'foo'
|
||||
@@ -54,6 +55,9 @@ class TestAssetProcessor(object):
|
||||
under_test = ly_test_tools.o3de.asset_processor.AssetProcessor(mock_workspace)
|
||||
under_test.enable_asset_processor_platform = mock.MagicMock()
|
||||
under_test.wait_for_idle = mock.MagicMock()
|
||||
mock_proc_object = mock.MagicMock()
|
||||
mock_proc_object.poll.return_value = None
|
||||
mock_popen.return_value = mock_proc_object
|
||||
|
||||
under_test.start(connect_to_ap=True)
|
||||
|
||||
|
||||
@@ -252,6 +252,18 @@ def find_snapshot_id(ec2_client, snapshot_hint, repository_name, project, pipeli
|
||||
snapshot_id = snapshot['SnapshotId']
|
||||
return snapshot_id
|
||||
|
||||
|
||||
def offline_drive(disk_number=1):
|
||||
"""Use diskpart to offline a Windows drive"""
|
||||
with tempfile.NamedTemporaryFile(delete=False) as f:
|
||||
f.write(f"""
|
||||
select disk {disk_number}
|
||||
offline disk
|
||||
""".encode('utf-8'))
|
||||
subprocess.run(['diskpart', '/s', f.name])
|
||||
os.unlink(f.name)
|
||||
|
||||
|
||||
def create_volume(ec2_client, availability_zone, snapshot_hint, repository_name, project, pipeline, branch, platform, build_type, disk_size, disk_type):
|
||||
# The actual EBS default calculation for IOps is a floating point number, the closest approxmiation is 4x of the disk size for simplicity
|
||||
mount_name = get_mount_name(repository_name, project, pipeline, branch, platform, build_type)
|
||||
@@ -310,23 +322,26 @@ def create_volume(ec2_client, availability_zone, snapshot_hint, repository_name,
|
||||
def mount_volume_to_device(created):
|
||||
print('Mounting volume...')
|
||||
if os.name == 'nt':
|
||||
f = tempfile.NamedTemporaryFile(delete=False)
|
||||
f.write("""
|
||||
select disk 1
|
||||
online disk
|
||||
attribute disk clear readonly
|
||||
""".encode('utf-8')) # assume disk # for now
|
||||
# Verify drive is in an offline state.
|
||||
# Some Windows configs will automatically set new drives as online causing diskpart setup script to fail.
|
||||
offline_drive()
|
||||
|
||||
if created:
|
||||
print('Creating filesystem on new volume')
|
||||
f.write("""create partition primary
|
||||
select partition 1
|
||||
format quick fs=ntfs
|
||||
assign
|
||||
active
|
||||
""".encode('utf-8'))
|
||||
with tempfile.NamedTemporaryFile(delete=False) as f:
|
||||
f.write("""
|
||||
select disk 1
|
||||
online disk
|
||||
attribute disk clear readonly
|
||||
""".encode('utf-8')) # assume disk # for now
|
||||
|
||||
f.close()
|
||||
if created:
|
||||
print('Creating filesystem on new volume')
|
||||
f.write("""
|
||||
create partition primary
|
||||
select partition 1
|
||||
format quick fs=ntfs
|
||||
assign
|
||||
active
|
||||
""".encode('utf-8'))
|
||||
|
||||
subprocess.call(['diskpart', '/s', f.name])
|
||||
|
||||
@@ -377,14 +392,7 @@ def unmount_volume_from_device():
|
||||
print('Unmounting EBS volume from device...')
|
||||
if os.name == 'nt':
|
||||
kill_processes(MOUNT_PATH + 'workspace')
|
||||
f = tempfile.NamedTemporaryFile(delete=False)
|
||||
f.write("""
|
||||
select disk 1
|
||||
offline disk
|
||||
""".encode('utf-8'))
|
||||
f.close()
|
||||
subprocess.call('diskpart /s %s' % f.name)
|
||||
os.unlink(f.name)
|
||||
offline_drive()
|
||||
else:
|
||||
kill_processes(MOUNT_PATH)
|
||||
subprocess.call(['umount', '-f', MOUNT_PATH])
|
||||
|
||||
Reference in New Issue
Block a user