Merge branch 'development' of https://github.com/o3de/o3de into Network/olexl/multiplayer_per_entity_analytics

This commit is contained in:
AMZN-Olex
2021-08-03 17:47:32 -04:00
102 changed files with 3670 additions and 2446 deletions
@@ -97,7 +97,7 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS)
PATH ${CMAKE_CURRENT_LIST_DIR}/asset_bundler_batch_tests.py
EXCLUDE_TEST_RUN_TARGET_FROM_IDE
TEST_SERIAL
TIMEOUT 1500
TIMEOUT 2400
TEST_SUITE periodic
RUNTIME_DEPENDENCIES
AZ::AssetProcessor
@@ -113,7 +113,7 @@ class TestsAssetBuilder_WindowsAndMac(object):
if listening_port:
corrupted_slice_command.append(f'-port={listening_port}')
if workspace.project:
corrupted_slice_command.append(f'-gamename={workspace.project}')
corrupted_slice_command.append(f'--project-path={workspace.project}')
corrupted_slice_output = utils.safe_subprocess(corrupted_slice_command)
# Verify corrupted slice produced error
@@ -902,7 +902,7 @@ class TestsAssetBundlerBatch_WindowsAndMac(object):
second_input_arg = asset_lists_to_string(second_asset_list) # --secondAssetList
output_arg = asset_lists_to_string(output_file) # --output
def generate_compare_command(platform_arg: str) -> object:
def generate_compare_command(platform_arg: str, project_name : str) -> object:
"""Creates a string containing a full Compare command. This string can be executed as-is."""
cmd = [helper["bundler_batch"], "compare", f"--firstassetFile={first_input_arg}", f"--output={output_arg}"]
if platform_arg is not None:
@@ -918,6 +918,8 @@ class TestsAssetBundlerBatch_WindowsAndMac(object):
if comp_type == "4":
# Extra arguments for pattern comparison
cmd.extend([f"--filePatternType={pattern_type}", f"--filePattern={pattern}"])
if workspace.project:
cmd.append(f'--project-path={project_name}')
return cmd
# End generate_compare_command()
@@ -936,6 +938,7 @@ class TestsAssetBundlerBatch_WindowsAndMac(object):
# End verify_asset_list_contents()
def run_compare_command_and_verify(platform_arg: str, expect_pc_output: bool, expect_mac_output: bool) -> None:
# Expected asset list to equal result of comparison
expected_pc_asset_list = None
expected_mac_asset_list = None
@@ -957,7 +960,7 @@ class TestsAssetBundlerBatch_WindowsAndMac(object):
output_mac_asset_list = helper.platform_file_name(last_output_arg, platform)
# Build execution command
cmd = generate_compare_command(platform_arg)
cmd = generate_compare_command(platform_arg, workspace.project)
# Execute command
subprocess.check_call(cmd)
@@ -992,10 +995,12 @@ class TestsAssetBundlerBatch_WindowsAndMac(object):
f"--comparisonRulesFile={rule_file}",
f"--comparisonType={args[1]}",
r"--addComparison",
f"--project-path={workspace.project}",
]
if args[1] == "4":
# If pattern comparison, append a few extra arguments
cmd.extend(["--filePatternType=0", "--filePattern=*.dat"])
subprocess.check_call(cmd)
assert os.path.exists(rule_file), f"Rule file {args[0]} was not created at location: {rule_file}"
@@ -17,7 +17,7 @@ if(PAL_TRAIT_BUILD_HOST_TOOLS AND PAL_TRAIT_BUILD_TESTS_SUPPORTED AND AutomatedT
TEST_SUITE main
PATH ${CMAKE_CURRENT_LIST_DIR}/test_Atom_MainSuite.py
TEST_SERIAL
TIMEOUT 400
TIMEOUT 600
RUNTIME_DEPENDENCIES
AssetProcessor
AutomatedTesting.Assets
@@ -0,0 +1,217 @@
"""
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
Hydra script that creates an entity, attaches the Light component to it for test verifications.
The test verifies that each light type option is available and can be selected without errors.
"""
import os
import sys
import azlmbr.bus as bus
import azlmbr.editor as editor
import azlmbr.math as math
import azlmbr.paths
import azlmbr.legacy.general as general
sys.path.append(os.path.join(azlmbr.paths.devassets, "Gem", "PythonTests"))
import editor_python_test_tools.hydra_editor_utils as hydra
from atom_renderer.atom_utils.atom_component_helper import LIGHT_TYPES
LIGHT_TYPE_PROPERTY = 'Controller|Configuration|Light type'
SPHERE_AND_SPOT_DISK_LIGHT_PROPERTIES = [
("Controller|Configuration|Shadows|Enable shadow", True),
("Controller|Configuration|Shadows|Shadowmap size", 0), # 256
("Controller|Configuration|Shadows|Shadowmap size", 1), # 512
("Controller|Configuration|Shadows|Shadowmap size", 2), # 1024
("Controller|Configuration|Shadows|Shadowmap size", 3), # 2048
("Controller|Configuration|Shadows|Shadow filter method", 1), # PCF
("Controller|Configuration|Shadows|Filtering sample count", 4.0),
("Controller|Configuration|Shadows|Filtering sample count", 64.0),
("Controller|Configuration|Shadows|PCF method", 0), # Bicubic
("Controller|Configuration|Shadows|PCF method", 1), # Boundary search
("Controller|Configuration|Shadows|Shadow filter method", 2), # ECM
("Controller|Configuration|Shadows|ESM exponent", 50),
("Controller|Configuration|Shadows|ESM exponent", 5000),
("Controller|Configuration|Shadows|Shadow filter method", 3), # ESM+PCF
]
QUAD_LIGHT_PROPERTIES = [
("Controller|Configuration|Both directions", True),
("Controller|Configuration|Fast approximation", True),
]
SIMPLE_POINT_LIGHT_PROPERTIES = [
("Controller|Configuration|Attenuation radius|Mode", 0),
("Controller|Configuration|Attenuation radius|Radius", 100.0),
]
SIMPLE_SPOT_LIGHT_PROPERTIES = [
("Controller|Configuration|Shutters|Inner angle", 45.0),
("Controller|Configuration|Shutters|Outer angle", 90.0),
]
def verify_required_component_property_value(entity_name, component, property_path, expected_property_value):
"""
Compares the property value of component against the expected_property_value.
:param entity_name: name of the entity to use (for test verification purposes).
:param component: component to check on a given entity for its current property value.
:param property_path: the path to the property inside the component.
:param expected_property_value: The value expected from the value inside property_path.
:return: None, but prints to general.log() which the test uses to verify against.
"""
property_value = editor.EditorComponentAPIBus(
bus.Broadcast, "GetComponentProperty", component, property_path).GetValue()
general.log(f"{entity_name}_test: Property value is {property_value} "
f"which matches {expected_property_value}")
def run():
"""
Test Case - Light Component
1. Creates a "light_entity" Entity and attaches a "Light" component to it.
2. Updates the Light component to each light type option from the LIGHT_TYPES constant.
3. The test will check the Editor log to ensure each light type was selected.
4. Prints the string "Light component test (non-GPU) completed" after completion.
Tests will fail immediately if any of these log lines are found:
1. Trace::Assert
2. Trace::Error
3. Traceback (most recent call last):
:return: None
"""
# Create a "light_entity" entity with "Light" component.
light_entity_name = "light_entity"
light_component = "Light"
light_entity = hydra.Entity(light_entity_name)
light_entity.create_entity(math.Vector3(-1.0, -2.0, 3.0), [light_component])
general.log(
f"{light_entity_name}_test: Component added to the entity: "
f"{hydra.has_components(light_entity.id, [light_component])}")
# Populate the light_component_id_pair value so that it can be used to select all Light component options.
light_component_id_pair = None
component_type_id_list = azlmbr.editor.EditorComponentAPIBus(
azlmbr.bus.Broadcast, 'FindComponentTypeIdsByEntityType', [light_component], 0)
if len(component_type_id_list) < 1:
general.log(f"ERROR: A component class with name {light_component} doesn't exist")
light_component_id_pair = None
elif len(component_type_id_list) > 1:
general.log(f"ERROR: Found more than one component classes with same name: {light_component}")
light_component_id_pair = None
entity_component_id_pair = azlmbr.editor.EditorComponentAPIBus(
azlmbr.bus.Broadcast, 'GetComponentOfType', light_entity.id, component_type_id_list[0])
if entity_component_id_pair.IsSuccess():
light_component_id_pair = entity_component_id_pair.GetValue()
# Test each Light component option can be selected and it's properties updated.
# Point (sphere) light type checks.
light_type_property_test(
light_type=LIGHT_TYPES['sphere'],
light_properties=SPHERE_AND_SPOT_DISK_LIGHT_PROPERTIES,
light_component_id_pair=light_component_id_pair,
light_entity_name=light_entity_name,
light_entity=light_entity
)
# Spot (disk) light type checks.
light_type_property_test(
light_type=LIGHT_TYPES['spot_disk'],
light_properties=SPHERE_AND_SPOT_DISK_LIGHT_PROPERTIES,
light_component_id_pair=light_component_id_pair,
light_entity_name=light_entity_name,
light_entity=light_entity
)
# Capsule light type checks.
azlmbr.editor.EditorComponentAPIBus(
azlmbr.bus.Broadcast,
'SetComponentProperty',
light_component_id_pair,
LIGHT_TYPE_PROPERTY,
LIGHT_TYPES['capsule']
)
verify_required_component_property_value(
entity_name=light_entity_name,
component=light_entity.components[0],
property_path=LIGHT_TYPE_PROPERTY,
expected_property_value=LIGHT_TYPES['capsule']
)
# Quad light type checks.
light_type_property_test(
light_type=LIGHT_TYPES['quad'],
light_properties=QUAD_LIGHT_PROPERTIES,
light_component_id_pair=light_component_id_pair,
light_entity_name=light_entity_name,
light_entity=light_entity
)
# Polygon light type checks.
azlmbr.editor.EditorComponentAPIBus(
azlmbr.bus.Broadcast,
'SetComponentProperty',
light_component_id_pair,
LIGHT_TYPE_PROPERTY,
LIGHT_TYPES['polygon']
)
verify_required_component_property_value(
entity_name=light_entity_name,
component=light_entity.components[0],
property_path=LIGHT_TYPE_PROPERTY,
expected_property_value=LIGHT_TYPES['polygon']
)
# Point (simple punctual) light type checks.
light_type_property_test(
light_type=LIGHT_TYPES['simple_point'],
light_properties=SIMPLE_POINT_LIGHT_PROPERTIES,
light_component_id_pair=light_component_id_pair,
light_entity_name=light_entity_name,
light_entity=light_entity
)
# Spot (simple punctual) light type checks.
light_type_property_test(
light_type=LIGHT_TYPES['simple_spot'],
light_properties=SIMPLE_SPOT_LIGHT_PROPERTIES,
light_component_id_pair=light_component_id_pair,
light_entity_name=light_entity_name,
light_entity=light_entity
)
general.log("Light component test (non-GPU) completed.")
def light_type_property_test(light_type, light_properties, light_component_id_pair, light_entity_name, light_entity):
"""
Updates the current light type and modifies its properties, then verifies they are accurate to what was set.
:param light_type: The type of light to update, must match a value in LIGHT_TYPES
:param light_properties: List of tuples detailing properties to modify with update values.
:param light_component_id_pair: Entity + component ID pair for updating the light component on a given entity.
:param light_entity_name: the name of the Entity holding the light component.
:param light_entity: the Entity object containing the light component.
:return: None
"""
azlmbr.editor.EditorComponentAPIBus(
azlmbr.bus.Broadcast,
'SetComponentProperty',
light_component_id_pair,
LIGHT_TYPE_PROPERTY,
light_type
)
verify_required_component_property_value(
entity_name=light_entity_name,
component=light_entity.components[0],
property_path=LIGHT_TYPE_PROPERTY,
expected_property_value=light_type
)
for light_property in light_properties:
light_entity.get_set_test(0, light_property[0], light_property[1])
if __name__ == "__main__":
run()
@@ -0,0 +1,19 @@
"""
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
File to assist with common hydra component functions or constants used across various Atom tests.
"""
# Light type options for the Light component.
LIGHT_TYPES = {
'unknown': 0,
'sphere': 1,
'spot_disk': 2,
'capsule': 3,
'quad': 4,
'polygon': 5,
'simple_point': 6,
'simple_spot': 7,
}
@@ -12,9 +12,10 @@ import os
import pytest
import editor_python_test_tools.hydra_test_utils as hydra
from atom_renderer.atom_utils.atom_component_helper import LIGHT_TYPES
logger = logging.getLogger(__name__)
EDITOR_TIMEOUT = 300
EDITOR_TIMEOUT = 120
TEST_DIRECTORY = os.path.join(os.path.dirname(__file__), "atom_hydra_scripts")
@@ -180,3 +181,64 @@ class TestAtomEditorComponentsMain(object):
null_renderer=True,
cfg_args=cfg_args,
)
def test_AtomEditorComponents_LightComponent(
self, request, editor, workspace, project, launcher_platform, level):
"""
Please review the hydra script run by this test for more specific test info.
Tests that the Light component has the expected property options available to it.
"""
cfg_args = [level]
expected_lines = [
"light_entity Entity successfully created",
"Entity has a Light component",
"light_entity_test: Component added to the entity: True",
f"light_entity_test: Property value is {LIGHT_TYPES['sphere']} which matches {LIGHT_TYPES['sphere']}",
"Controller|Configuration|Shadows|Enable shadow set to True",
"light_entity Controller|Configuration|Shadows|Shadowmap size: SUCCESS",
"Controller|Configuration|Shadows|Shadow filter method set to 1", # PCF
"Controller|Configuration|Shadows|Filtering sample count set to 4",
"Controller|Configuration|Shadows|Filtering sample count set to 64",
"Controller|Configuration|Shadows|PCF method set to 0",
"Controller|Configuration|Shadows|PCF method set to 1",
"Controller|Configuration|Shadows|Shadow filter method set to 2", # ESM
"Controller|Configuration|Shadows|ESM exponent set to 50.0",
"Controller|Configuration|Shadows|ESM exponent set to 5000.0",
"Controller|Configuration|Shadows|Shadow filter method set to 3", # ESM+PCF
f"light_entity_test: Property value is {LIGHT_TYPES['spot_disk']} which matches {LIGHT_TYPES['spot_disk']}",
f"light_entity_test: Property value is {LIGHT_TYPES['capsule']} which matches {LIGHT_TYPES['capsule']}",
f"light_entity_test: Property value is {LIGHT_TYPES['quad']} which matches {LIGHT_TYPES['quad']}",
"light_entity Controller|Configuration|Fast approximation: SUCCESS",
"light_entity Controller|Configuration|Both directions: SUCCESS",
f"light_entity_test: Property value is {LIGHT_TYPES['polygon']} which matches {LIGHT_TYPES['polygon']}",
f"light_entity_test: Property value is {LIGHT_TYPES['simple_point']} "
f"which matches {LIGHT_TYPES['simple_point']}",
"Controller|Configuration|Attenuation radius|Mode set to 0",
"Controller|Configuration|Attenuation radius|Radius set to 100.0",
f"light_entity_test: Property value is {LIGHT_TYPES['simple_spot']} "
f"which matches {LIGHT_TYPES['simple_spot']}",
"Controller|Configuration|Shutters|Outer angle set to 45.0",
"Controller|Configuration|Shutters|Outer angle set to 90.0",
"light_entity_test: Component added to the entity: True",
"Light component test (non-GPU) completed.",
]
unexpected_lines = [
"Trace::Assert",
"Trace::Error",
"Traceback (most recent call last):",
]
hydra.launch_and_validate_results(
request,
TEST_DIRECTORY,
editor,
"hydra_AtomEditorComponents_LightComponent.py",
timeout=EDITOR_TIMEOUT,
expected_lines=expected_lines,
unexpected_lines=unexpected_lines,
halt_on_unexpected=True,
null_renderer=True,
cfg_args=cfg_args,
)
+483
View File
@@ -0,0 +1,483 @@
{
"Type": "JsonSerialization",
"Version": 1,
"ClassName": "PassAsset",
"ClassData": {
"PassTemplate": {
"Name": "MainPipeline",
"PassClass": "ParentPass",
"Slots": [
{
"Name": "SwapChainOutput",
"SlotType": "InputOutput",
"ScopeAttachmentUsage": "RenderTarget"
}
],
"PassRequests": [
{
"Name": "MorphTargetPass",
"TemplateName": "MorphTargetPassTemplate"
},
{
"Name": "SkinningPass",
"TemplateName": "SkinningPassTemplate",
"Connections": [
{
"LocalSlot": "SkinnedMeshOutputStream",
"AttachmentRef": {
"Pass": "MorphTargetPass",
"Attachment": "MorphTargetDeltaOutput"
}
}
]
},
{
"Name": "RayTracingAccelerationStructurePass",
"TemplateName": "RayTracingAccelerationStructurePassTemplate"
},
{
"Name": "DiffuseProbeGridUpdatePass",
"TemplateName": "DiffuseProbeGridUpdatePassTemplate",
"ExecuteAfter": [
"RayTracingAccelerationStructurePass"
]
},
{
"Name": "DepthPrePass",
"TemplateName": "DepthMSAAParentTemplate",
"Connections": [
{
"LocalSlot": "SkinnedMeshes",
"AttachmentRef": {
"Pass": "SkinningPass",
"Attachment": "SkinnedMeshOutputStream"
}
},
{
"LocalSlot": "SwapChainOutput",
"AttachmentRef": {
"Pass": "Parent",
"Attachment": "SwapChainOutput"
}
}
]
},
{
"Name": "MotionVectorPass",
"TemplateName": "MotionVectorParentTemplate",
"Connections": [
{
"LocalSlot": "SkinnedMeshes",
"AttachmentRef": {
"Pass": "SkinningPass",
"Attachment": "SkinnedMeshOutputStream"
}
},
{
"LocalSlot": "Depth",
"AttachmentRef": {
"Pass": "DepthPrePass",
"Attachment": "Depth"
}
},
{
"LocalSlot": "SwapChainOutput",
"AttachmentRef": {
"Pass": "Parent",
"Attachment": "SwapChainOutput"
}
}
]
},
{
"Name": "LightCullingPass",
"TemplateName": "LightCullingParentTemplate",
"Connections": [
{
"LocalSlot": "SkinnedMeshes",
"AttachmentRef": {
"Pass": "SkinningPass",
"Attachment": "SkinnedMeshOutputStream"
}
},
{
"LocalSlot": "DepthMSAA",
"AttachmentRef": {
"Pass": "DepthPrePass",
"Attachment": "DepthMSAA"
}
},
{
"LocalSlot": "SwapChainOutput",
"AttachmentRef": {
"Pass": "Parent",
"Attachment": "SwapChainOutput"
}
}
]
},
{
"Name": "ShadowPass",
"TemplateName": "ShadowParentTemplate",
"Connections": [
{
"LocalSlot": "SkinnedMeshes",
"AttachmentRef": {
"Pass": "SkinningPass",
"Attachment": "SkinnedMeshOutputStream"
}
},
{
"LocalSlot": "SwapChainOutput",
"AttachmentRef": {
"Pass": "Parent",
"Attachment": "SwapChainOutput"
}
}
]
},
{
"Name": "OpaquePass",
"TemplateName": "OpaqueParentTemplate",
"Connections": [
{
"LocalSlot": "DirectionalShadowmap",
"AttachmentRef": {
"Pass": "ShadowPass",
"Attachment": "DirectionalShadowmap"
}
},
{
"LocalSlot": "DirectionalESM",
"AttachmentRef": {
"Pass": "ShadowPass",
"Attachment": "DirectionalESM"
}
},
{
"LocalSlot": "ProjectedShadowmap",
"AttachmentRef": {
"Pass": "ShadowPass",
"Attachment": "ProjectedShadowmap"
}
},
{
"LocalSlot": "ProjectedESM",
"AttachmentRef": {
"Pass": "ShadowPass",
"Attachment": "ProjectedESM"
}
},
{
"LocalSlot": "TileLightData",
"AttachmentRef": {
"Pass": "LightCullingPass",
"Attachment": "TileLightData"
}
},
{
"LocalSlot": "LightListRemapped",
"AttachmentRef": {
"Pass": "LightCullingPass",
"Attachment": "LightListRemapped"
}
},
{
"LocalSlot": "DepthLinear",
"AttachmentRef": {
"Pass": "DepthPrePass",
"Attachment": "DepthLinear"
}
},
{
"LocalSlot": "DepthStencil",
"AttachmentRef": {
"Pass": "DepthPrePass",
"Attachment": "DepthMSAA"
}
},
{
"LocalSlot": "SwapChainOutput",
"AttachmentRef": {
"Pass": "Parent",
"Attachment": "SwapChainOutput"
}
}
]
},
{
"Name": "TransparentPass",
"TemplateName": "TransparentParentTemplate",
"Connections": [
{
"LocalSlot": "DirectionalShadowmap",
"AttachmentRef": {
"Pass": "ShadowPass",
"Attachment": "DirectionalShadowmap"
}
},
{
"LocalSlot": "DirectionalESM",
"AttachmentRef": {
"Pass": "ShadowPass",
"Attachment": "DirectionalESM"
}
},
{
"LocalSlot": "ProjectedShadowmap",
"AttachmentRef": {
"Pass": "ShadowPass",
"Attachment": "ProjectedShadowmap"
}
},
{
"LocalSlot": "ProjectedESM",
"AttachmentRef": {
"Pass": "ShadowPass",
"Attachment": "ProjectedESM"
}
},
{
"LocalSlot": "TileLightData",
"AttachmentRef": {
"Pass": "LightCullingPass",
"Attachment": "TileLightData"
}
},
{
"LocalSlot": "LightListRemapped",
"AttachmentRef": {
"Pass": "LightCullingPass",
"Attachment": "LightListRemapped"
}
},
{
"LocalSlot": "InputLinearDepth",
"AttachmentRef": {
"Pass": "DepthPrePass",
"Attachment": "DepthLinear"
}
},
{
"LocalSlot": "DepthStencil",
"AttachmentRef": {
"Pass": "DepthPrePass",
"Attachment": "Depth"
}
},
{
"LocalSlot": "InputOutput",
"AttachmentRef": {
"Pass": "OpaquePass",
"Attachment": "Output"
}
}
]
},
{
"Name": "DeferredFogPass",
"TemplateName": "DeferredFogPassTemplate",
"Enabled": false,
"Connections": [
{
"LocalSlot": "InputLinearDepth",
"AttachmentRef": {
"Pass": "DepthPrePass",
"Attachment": "DepthLinear"
}
},
{
"LocalSlot": "InputDepthStencil",
"AttachmentRef": {
"Pass": "DepthPrePass",
"Attachment": "Depth"
}
},
{
"LocalSlot": "RenderTargetInputOutput",
"AttachmentRef": {
"Pass": "TransparentPass",
"Attachment": "InputOutput"
}
}
],
"PassData": {
"$type": "FullscreenTrianglePassData",
"ShaderAsset": {
"FilePath": "Shaders/ScreenSpace/DeferredFog.shader"
},
"PipelineViewTag": "MainCamera"
}
},
{
"Name": "ReflectionCopyFrameBufferPass",
"TemplateName": "ReflectionCopyFrameBufferPassTemplate",
"Enabled": false,
"Connections": [
{
"LocalSlot": "Input",
"AttachmentRef": {
"Pass": "DeferredFogPass",
"Attachment": "RenderTargetInputOutput"
}
}
]
},
{
"Name": "PostProcessPass",
"TemplateName": "PostProcessParentTemplate",
"Connections": [
{
"LocalSlot": "LightingInput",
"AttachmentRef": {
"Pass": "DeferredFogPass",
"Attachment": "RenderTargetInputOutput"
}
},
{
"LocalSlot": "Depth",
"AttachmentRef": {
"Pass": "DepthPrePass",
"Attachment": "Depth"
}
},
{
"LocalSlot": "MotionVectors",
"AttachmentRef": {
"Pass": "MotionVectorPass",
"Attachment": "MotionVectorOutput"
}
},
{
"LocalSlot": "SwapChainOutput",
"AttachmentRef": {
"Pass": "Parent",
"Attachment": "SwapChainOutput"
}
}
]
},
{
"Name": "AuxGeomPass",
"TemplateName": "AuxGeomPassTemplate",
"Enabled": true,
"Connections": [
{
"LocalSlot": "ColorInputOutput",
"AttachmentRef": {
"Pass": "PostProcessPass",
"Attachment": "Output"
}
},
{
"LocalSlot": "DepthInputOutput",
"AttachmentRef": {
"Pass": "DepthPrePass",
"Attachment": "Depth"
}
}
],
"PassData": {
"$type": "RasterPassData",
"DrawListTag": "auxgeom",
"PipelineViewTag": "MainCamera"
}
},
{
"Name": "DebugOverlayPass",
"TemplateName": "DebugOverlayParentTemplate",
"Connections": [
{
"LocalSlot": "TileLightData",
"AttachmentRef": {
"Pass": "LightCullingPass",
"Attachment": "TileLightData"
}
},
{
"LocalSlot": "RawLightingInput",
"AttachmentRef": {
"Pass": "PostProcessPass",
"Attachment": "RawLightingOutput"
}
},
{
"LocalSlot": "LuminanceMipChainInput",
"AttachmentRef": {
"Pass": "PostProcessPass",
"Attachment": "LuminanceMipChainOutput"
}
},
{
"LocalSlot": "InputOutput",
"AttachmentRef": {
"Pass": "AuxGeomPass",
"Attachment": "ColorInputOutput"
}
}
]
},
{
"Name": "LyShinePass",
"TemplateName": "LyShineParentTemplate",
"Connections": [
{
"LocalSlot": "ColorInputOutput",
"AttachmentRef": {
"Pass": "DebugOverlayPass",
"Attachment": "InputOutput"
}
},
{
"LocalSlot": "DepthInputOutput",
"AttachmentRef": {
"Pass": "DepthPrePass",
"Attachment": "Depth"
}
}
]
},
{
"Name": "UIPass",
"TemplateName": "UIParentTemplate",
"Connections": [
{
"LocalSlot": "InputOutput",
"AttachmentRef": {
"Pass": "LyShinePass",
"Attachment": "ColorInputOutput"
}
},
{
"LocalSlot": "DepthInputOutput",
"AttachmentRef": {
"Pass": "DepthPrePass",
"Attachment": "Depth"
}
}
]
},
{
"Name": "CopyToSwapChain",
"TemplateName": "FullscreenCopyTemplate",
"Connections": [
{
"LocalSlot": "Input",
"AttachmentRef": {
"Pass": "UIPass",
"Attachment": "InputOutput"
}
},
{
"LocalSlot": "Output",
"AttachmentRef": {
"Pass": "Parent",
"Attachment": "SwapChainOutput"
}
}
]
}
]
}
}
}
+6 -2
View File
@@ -472,6 +472,12 @@ void EditorViewportWidget::Update()
m_Camera.SetZRange(cameraState.m_nearClip, cameraState.m_farClip);
}
// Ensure the FOV matches our internally stored setting if we're using the Editor camera
if (!m_viewEntityId.IsValid() && !GetIEditor()->IsInGameMode())
{
SetFOV(GetFOV());
}
// Reset the camera update flag now that we're finished updating our viewport context
m_updateCameraPositionNextTick = false;
@@ -2624,8 +2630,6 @@ void EditorViewportWidget::DestroyRenderContext()
//////////////////////////////////////////////////////////////////////////
void EditorViewportWidget::SetDefaultCamera()
{
// Ensure the FOV matches our internally stored setting
SetFOV(GetFOV());
if (IsDefaultCamera())
{
return;
@@ -1150,7 +1150,7 @@ namespace UnitTest
#if AZ_TRAIT_DISABLE_FAILED_ASSET_MANAGER_TESTS
TEST_F(AssetJobsFloodTest, DISABLED_ContainerFilterTest_ContainersWithAndWithoutFiltering_Success)
#else
TEST_F(AssetJobsFloodTest, ContainerFilterTest_ContainersWithAndWithoutFiltering_Success)
TEST_F(AssetJobsFloodTest, DISABLED_ContainerFilterTest_ContainersWithAndWithoutFiltering_Success)
#endif // !AZ_TRAIT_DISABLE_FAILED_ASSET_MANAGER_TESTS
{
m_assetHandlerAndCatalog->AssetCatalogRequestBus::Handler::BusConnect();
@@ -103,6 +103,14 @@ namespace AzNetworking
//! @return boolean true on success
virtual bool Disconnect(ConnectionId connectionId, DisconnectReason reason) = 0;
//! Sets whether this connection interface can disconnect by virtue of a timeout
//! @param timeoutEnabled If this connection interface will automatically disconnect due to a timeout
virtual void SetTimeoutEnabled(bool timeoutEnabled) = 0;
//! Whether this connection interface will disconnect by virtue of a time out (does not account for cvars affecting all connections)
//! @return boolean true if this connection will not disconnect on timeout (does not account for cvars affecting all connections)
virtual bool IsTimeoutEnabled() const = 0;
//! Const access to the metrics tracked by this network interface.
//! @return const reference to the metrics tracked by this network interface
const NetworkInterfaceMetrics& GetMetrics() const;
@@ -174,6 +174,16 @@ namespace AzNetworking
return connection->Disconnect(reason, TerminationEndpoint::Local);
}
void TcpNetworkInterface::SetTimeoutEnabled(bool timeoutEnabled)
{
m_timeoutEnabled = timeoutEnabled;
}
bool TcpNetworkInterface::IsTimeoutEnabled() const
{
return m_timeoutEnabled;
}
void TcpNetworkInterface::QueueNewConnection(const PendingConnection& pendingConnection)
{
m_pendingConnections.PushBackItem(pendingConnection);
@@ -306,7 +316,7 @@ namespace AzNetworking
{
tcpConnection->SendReliablePacket(CorePackets::HeartbeatPacket());
}
else if (net_TcpTimeoutConnections)
else if (net_TcpTimeoutConnections && m_networkInterface.IsTimeoutEnabled())
{
tcpConnection->Disconnect(DisconnectReason::Timeout, TerminationEndpoint::Local);
return TimeoutResult::Delete;
@@ -99,6 +99,8 @@ namespace AzNetworking
bool WasPacketAcked(ConnectionId connectionId, PacketId packetId) override;
bool StopListening() override;
bool Disconnect(ConnectionId connectionId, DisconnectReason reason) override;
void SetTimeoutEnabled(bool timeoutEnabled) override;
bool IsTimeoutEnabled() const override;
//! @}
//! Queues a new incoming connection for this network interface.
@@ -154,6 +156,7 @@ namespace AzNetworking
AZ::Name m_name;
TrustZone m_trustZone;
uint16_t m_port = 0;
bool m_timeoutEnabled = true;
IConnectionListener& m_connectionListener;
TcpConnectionSet m_connectionSet;
TcpSocketManager m_tcpSocketManager;
@@ -397,6 +397,16 @@ namespace AzNetworking
return connection->Disconnect(reason, TerminationEndpoint::Local);
}
void UdpNetworkInterface::SetTimeoutEnabled(bool timeoutEnabled)
{
m_timeoutEnabled = timeoutEnabled;
}
bool UdpNetworkInterface::IsTimeoutEnabled() const
{
return m_timeoutEnabled;
}
bool UdpNetworkInterface::IsEncrypted() const
{
return m_socket->IsEncrypted();
@@ -729,7 +739,7 @@ namespace AzNetworking
{
udpConnection->SendUnreliablePacket(CorePackets::HeartbeatPacket());
}
else if (net_UdpTimeoutConnections)
else if (net_UdpTimeoutConnections && m_networkInterface.IsTimeoutEnabled())
{
udpConnection->Disconnect(DisconnectReason::Timeout, TerminationEndpoint::Local);
return TimeoutResult::Delete;
@@ -104,6 +104,8 @@ namespace AzNetworking
bool WasPacketAcked(ConnectionId connectionId, PacketId packetId) override;
bool StopListening() override;
bool Disconnect(ConnectionId connectionId, DisconnectReason reason) override;
void SetTimeoutEnabled(bool timeoutEnabled) override;
bool IsTimeoutEnabled() const override;
//! @}
//! Returns true if this is an encrypted socket, false if not.
@@ -179,6 +181,7 @@ namespace AzNetworking
TrustZone m_trustZone;
uint16_t m_port = 0;
bool m_allowIncomingConnections = false;
bool m_timeoutEnabled = true;
IConnectionListener& m_connectionListener;
UdpConnectionSet m_connectionSet;
TimeoutQueue m_connectionTimeoutQueue;
@@ -118,7 +118,9 @@ namespace O3DE::ProjectManager
}
}
if (m_configProjectProcess->exitCode() != 0 || !containsGeneratingDone)
if (m_configProjectProcess->exitStatus() != QProcess::ExitStatus::NormalExit
|| m_configProjectProcess->exitCode() != 0
|| !containsGeneratingDone)
{
QString error = tr("Configuring project failed. See log for details.");
QStringToAZTracePrint(error);
@@ -180,7 +182,8 @@ namespace O3DE::ProjectManager
}
}
if (m_configProjectProcess->exitCode() != 0)
if (m_configProjectProcess->exitStatus() != QProcess::ExitStatus::NormalExit
|| m_configProjectProcess->exitCode() != 0)
{
QString error = tr("Building project failed. See log for details.");
QStringToAZTracePrint(error);
@@ -265,6 +265,6 @@ namespace O3DE::ProjectManager
void CreateProjectCtrl::ReinitGemCatalogForSelectedTemplate()
{
const QString projectTemplatePath = m_newProjectSettingsScreen->GetProjectTemplatePath();
m_gemCatalogScreen->ReinitForProject(projectTemplatePath + "/Template", /*isNewProject=*/true);
m_gemCatalogScreen->ReinitForProject(projectTemplatePath + "/Template");
}
} // namespace O3DE::ProjectManager
@@ -62,10 +62,10 @@ namespace O3DE::ProjectManager
hLayout->addWidget(m_gemInspector);
}
void GemCatalogScreen::ReinitForProject(const QString& projectPath, bool isNewProject)
void GemCatalogScreen::ReinitForProject(const QString& projectPath)
{
m_gemModel->clear();
FillModel(projectPath, isNewProject);
FillModel(projectPath);
if (m_filterWidget)
{
@@ -88,18 +88,9 @@ namespace O3DE::ProjectManager
});
}
void GemCatalogScreen::FillModel(const QString& projectPath, bool isNewProject)
void GemCatalogScreen::FillModel(const QString& projectPath)
{
AZ::Outcome<QVector<GemInfo>, AZStd::string> allGemInfosResult;
if (isNewProject)
{
allGemInfosResult = PythonBindingsInterface::Get()->GetEngineGemInfos();
}
else
{
allGemInfosResult = PythonBindingsInterface::Get()->GetAllGemInfos(projectPath);
}
AZ::Outcome<QVector<GemInfo>, AZStd::string> allGemInfosResult = PythonBindingsInterface::Get()->GetAllGemInfos(projectPath);
if (allGemInfosResult.IsSuccess())
{
// Add all available gems to the model.
@@ -28,13 +28,13 @@ namespace O3DE::ProjectManager
~GemCatalogScreen() = default;
ProjectManagerScreen GetScreenEnum() override;
void ReinitForProject(const QString& projectPath, bool isNewProject);
void ReinitForProject(const QString& projectPath);
bool EnableDisableGemsForProject(const QString& projectPath);
GemModel* GetGemModel() const { return m_gemModel; }
private:
void FillModel(const QString& projectPath, bool isNewProject);
void FillModel(const QString& projectPath);
GemListView* m_gemListView = nullptr;
GemInspector* m_gemInspector = nullptr;
@@ -104,7 +104,7 @@ namespace O3DE::ProjectManager
QMessageBox::critical(m_parent, tr("Project Failed to Build!"), result);
m_projectInfo.m_buildFailed = true;
m_projectInfo.m_logUrl = QUrl();
m_projectInfo.m_logUrl = QUrl("file:///" + m_worker->GetLogFilePath());
emit NotifyBuildProject(m_projectInfo);
}
@@ -94,7 +94,7 @@ namespace O3DE::ProjectManager
Update();
// Gather the available gems that will be shown in the gem catalog.
m_gemCatalogScreen->ReinitForProject(m_projectInfo.m_path, /*isNewProject=*/false);
m_gemCatalogScreen->ReinitForProject(m_projectInfo.m_path);
}
void UpdateProjectCtrl::HandleGemsButton()
@@ -235,7 +235,7 @@ namespace AZ
UpdateViewToClipMatrix();
}
void CameraComponent::SetOrthographic(bool orthographic)
void CameraComponent::SetOrthographic([[maybe_unused]] bool orthographic)
{
AZ_Assert(!orthographic, "DebugCamera does not support orthographic projection");
}
@@ -13,13 +13,7 @@
"ScopeAttachmentUsage": "DepthStencil",
"LoadStoreAction": {
"ClearValue": {
"Type": "DepthStencil",
"Value": [
0.0,
0.0,
0.0,
0.0
]
"Type": "DepthStencil"
},
"LoadActionStencil": "Clear"
}
@@ -172,7 +172,7 @@ namespace AZ
}
m_pipelineState = m_shader->AcquirePipelineState(descriptor);
}
}
m_dirty = false;
}
return m_pipelineState;
@@ -16,18 +16,21 @@
namespace AtomToolsFramework
{
class ModernViewportCameraControllerInstance;
class ModularViewportCameraControllerInstance;
//! Builder class to create and configure a ModularViewportCameraControllerInstance.
class ModularViewportCameraController
: public AzFramework::MultiViewportController<
ModernViewportCameraControllerInstance, AzFramework::ViewportControllerPriority::DispatchToAllPriorities>
ModularViewportCameraControllerInstance,
AzFramework::ViewportControllerPriority::DispatchToAllPriorities>
{
public:
using CameraListBuilder = AZStd::function<void(AzFramework::Cameras&)>;
using CameraPropsBuilder = AZStd::function<void(AzFramework::CameraProps&)>;
//! Sets the camera list builder callback used to populate new ModernViewportCameraControllerInstances
//! Sets the camera list builder callback used to populate new ModularViewportCameraControllerInstances
void SetCameraListBuilderCallback(const CameraListBuilder& builder);
//! Sets the camera props builder callback used to populate new ModernViewportCameraControllerInstances
//! Sets the camera props builder callback used to populate new ModularViewportCameraControllerInstances
void SetCameraPropsBuilderCallback(const CameraPropsBuilder& builder);
//! Sets up a camera list based on this controller's CameraListBuilderCallback
void SetupCameras(AzFramework::Cameras& cameras);
@@ -35,18 +38,22 @@ namespace AtomToolsFramework
void SetupCameraProperies(AzFramework::CameraProps& cameraProps);
private:
//! Builder to generate a list of CameraInputs to run in the ModularViewportCameraControllerInstance.
CameraListBuilder m_cameraListBuilder;
CameraPropsBuilder m_cameraPropsBuilder;
CameraPropsBuilder m_cameraPropsBuilder; //!< Builder to define custom camera properties to use for things such as rotate and
//!< translate interpolation.
};
class ModernViewportCameraControllerInstance final
: public AzFramework::MultiViewportControllerInstanceInterface<ModularViewportCameraController>,
public ModularViewportCameraControllerRequestBus::Handler,
private AzFramework::ViewportDebugDisplayEventBus::Handler
//! A customizable camera controller that can be configured to run a varying set of CameraInput instances.
//! The controller can also be animated from its current transform to a new translation and orientation.
class ModularViewportCameraControllerInstance final
: public AzFramework::MultiViewportControllerInstanceInterface<ModularViewportCameraController>
, public ModularViewportCameraControllerRequestBus::Handler
, private AzFramework::ViewportDebugDisplayEventBus::Handler
{
public:
explicit ModernViewportCameraControllerInstance(AzFramework::ViewportId viewportId, ModularViewportCameraController* controller);
~ModernViewportCameraControllerInstance() override;
explicit ModularViewportCameraControllerInstance(AzFramework::ViewportId viewportId, ModularViewportCameraController* controller);
~ModularViewportCameraControllerInstance() override;
// MultiViewportControllerInstanceInterface overrides ...
bool HandleInputChannelEvent(const AzFramework::ViewportControllerInputEvent& event) override;
@@ -60,25 +67,34 @@ namespace AtomToolsFramework
// AzFramework::ViewportDebugDisplayEventBus overrides ...
void DisplayViewport(const AzFramework::ViewportInfo& viewportInfo, AzFramework::DebugDisplayRequests& debugDisplay) override;
//! The current mode the camera controller is in.
enum class CameraMode
{
Control,
Animation
Control, //!< The camera is being driven by user input.
Animation //!< The camera is being animated (interpolated) from one transform to another.
};
AzFramework::Camera m_camera;
AzFramework::Camera m_targetCamera;
AzFramework::CameraSystem m_cameraSystem;
AzFramework::CameraProps m_cameraProps;
//! Encapsulates an animation (interpolation) between two transforms.
struct CameraAnimation
{
//! The transform of the camera at the start of the animation.
AZ::Transform m_transformStart = AZ::Transform::CreateIdentity();
AZ::Transform m_transformEnd = AZ::Transform::CreateIdentity(); //!< The transform of the camera at the end of the animation.
float m_time = 0.0f; //!< The interpolation amount between the start and end transforms (in the range 0.0 - 1.0).
};
AZ::Transform m_transformStart = AZ::Transform::CreateIdentity();
AZ::Transform m_transformEnd = AZ::Transform::CreateIdentity();
float m_animationT = 0.0f;
CameraMode m_cameraMode = CameraMode::Control;
AzFramework::Camera m_camera; //!< The current camera state (pitch/yaw/position/look-distance).
AzFramework::Camera m_targetCamera; //!< The target (next) camera state that m_camera is catching up to.
AzFramework::CameraSystem m_cameraSystem; //!< The camera system responsible for managing all CameraInputs.
AzFramework::CameraProps m_cameraProps; //!< Camera properties to control rotate and translate smoothness.
CameraAnimation m_cameraAnimation; //!< Camera animation state (used during CameraMode::Animation).
CameraMode m_cameraMode = CameraMode::Control; //!< The current mode the camera is operating in.
AZStd::optional<AZ::Vector3> m_lookAtAfterInterpolation; //!< The look at point after an interpolation has finished.
//!< Will be cleared when the view changes (camera looks away).
bool m_updatingTransform = false;
//! Flag to prevent circular updates of the camera transform (while the viewport transform is being updated internally).
bool m_updatingTransformInternally = false;
//! Listen for camera view changes outside of the camera controller.
AZ::RPI::ViewportContext::MatrixChangedEvent::Handler m_cameraViewMatrixChangeHandler;
};
} // namespace AtomToolsFramework
@@ -84,7 +84,7 @@ namespace AtomToolsFramework
}
}
ModernViewportCameraControllerInstance::ModernViewportCameraControllerInstance(
ModularViewportCameraControllerInstance::ModularViewportCameraControllerInstance(
const AzFramework::ViewportId viewportId, ModularViewportCameraController* controller)
: MultiViewportControllerInstanceInterface<ModularViewportCameraController>(viewportId, controller)
{
@@ -95,7 +95,8 @@ namespace AtomToolsFramework
{
auto handleCameraChange = [this, viewportContext](const AZ::Matrix4x4&)
{
if (!m_updatingTransform)
// ignore these updates if the camera is being updated internally
if (!m_updatingTransformInternally)
{
UpdateCameraFromTransform(m_targetCamera, viewportContext->GetCameraTransform());
m_camera = m_targetCamera;
@@ -111,7 +112,7 @@ namespace AtomToolsFramework
ModularViewportCameraControllerRequestBus::Handler::BusConnect(viewportId);
}
ModernViewportCameraControllerInstance::~ModernViewportCameraControllerInstance()
ModularViewportCameraControllerInstance::~ModularViewportCameraControllerInstance()
{
ModularViewportCameraControllerRequestBus::Handler::BusDisconnect();
AzFramework::ViewportDebugDisplayEventBus::Handler::BusDisconnect();
@@ -132,7 +133,7 @@ namespace AtomToolsFramework
return AzFramework::ViewportControllerPriority::Normal;
}
bool ModernViewportCameraControllerInstance::HandleInputChannelEvent(const AzFramework::ViewportControllerInputEvent& event)
bool ModularViewportCameraControllerInstance::HandleInputChannelEvent(const AzFramework::ViewportControllerInputEvent& event)
{
if (event.m_priority == GetPriority(m_cameraSystem))
{
@@ -142,7 +143,7 @@ namespace AtomToolsFramework
return false;
}
void ModernViewportCameraControllerInstance::UpdateViewport(const AzFramework::ViewportControllerUpdateEvent& event)
void ModularViewportCameraControllerInstance::UpdateViewport(const AzFramework::ViewportControllerUpdateEvent& event)
{
// only update for a single priority (normal is the default)
if (event.m_priority != AzFramework::ViewportControllerPriority::Normal)
@@ -152,7 +153,7 @@ namespace AtomToolsFramework
if (auto viewportContext = RetrieveViewportContext(GetViewportId()))
{
m_updatingTransform = true;
m_updatingTransformInternally = true;
if (m_cameraMode == CameraMode::Control)
{
@@ -180,10 +181,12 @@ namespace AtomToolsFramework
return t * t * t * (t * (t * 6.0f - 15.0f) + 10.0f);
};
const float transitionT = smootherStepFn(m_animationT);
const auto& [transformStart, transformEnd, animationTime] = m_cameraAnimation;
const float transitionTime = smootherStepFn(animationTime);
const AZ::Transform current = AZ::Transform::CreateFromQuaternionAndTranslation(
m_transformStart.GetRotation().Slerp(m_transformEnd.GetRotation(), transitionT),
m_transformStart.GetTranslation().Lerp(m_transformEnd.GetTranslation(), transitionT));
transformStart.GetRotation().Slerp(transformEnd.GetRotation(), transitionTime),
transformStart.GetTranslation().Lerp(transformEnd.GetTranslation(), transitionTime));
const AZ::Vector3 eulerAngles = AzFramework::EulerAngles(AZ::Matrix3x3::CreateFromTransform(current));
m_camera.m_pitch = eulerAngles.GetX();
@@ -191,21 +194,21 @@ namespace AtomToolsFramework
m_camera.m_lookAt = current.GetTranslation();
m_targetCamera = m_camera;
if (m_animationT >= 1.0f)
if (animationTime >= 1.0f)
{
m_cameraMode = CameraMode::Control;
}
m_animationT = AZ::GetClamp(m_animationT + event.m_deltaTime.count(), 0.0f, 1.0f);
m_cameraAnimation.m_time = AZ::GetClamp(animationTime + event.m_deltaTime.count(), 0.0f, 1.0f);
viewportContext->SetCameraTransform(current);
}
m_updatingTransform = false;
m_updatingTransformInternally = false;
}
}
void ModernViewportCameraControllerInstance::DisplayViewport(
void ModularViewportCameraControllerInstance::DisplayViewport(
[[maybe_unused]] const AzFramework::ViewportInfo& viewportInfo, AzFramework::DebugDisplayRequests& debugDisplay)
{
if (const float alpha = AZStd::min(-m_camera.m_lookDist / 5.0f, 1.0f); alpha > AZ::Constants::FloatEpsilon)
@@ -216,16 +219,14 @@ namespace AtomToolsFramework
}
}
void ModernViewportCameraControllerInstance::InterpolateToTransform(const AZ::Transform& worldFromLocal, const float lookAtDistance)
void ModularViewportCameraControllerInstance::InterpolateToTransform(const AZ::Transform& worldFromLocal, const float lookAtDistance)
{
m_animationT = 0.0f;
m_cameraMode = CameraMode::Animation;
m_transformStart = m_camera.Transform();
m_transformEnd = worldFromLocal;
m_lookAtAfterInterpolation = m_transformEnd.GetTranslation() + m_transformEnd.GetBasisY() * lookAtDistance;
m_cameraAnimation = CameraAnimation{ m_camera.Transform(), worldFromLocal, 0.0f };
m_lookAtAfterInterpolation = worldFromLocal.GetTranslation() + worldFromLocal.GetBasisY() * lookAtDistance;
}
AZStd::optional<AZ::Vector3> ModernViewportCameraControllerInstance::LookAtAfterInterpolation() const
AZStd::optional<AZ::Vector3> ModularViewportCameraControllerInstance::LookAtAfterInterpolation() const
{
return m_lookAtAfterInterpolation;
}
@@ -10,9 +10,6 @@
#include <Atom/Features/PostProcessing/PostProcessUtil.azsli>
// Indicates whether to use pre-multiplied alpha
option bool o_preMultiplyAlpha;
// If true pixels with an alpha value of less than 0.5 are clipped
option bool o_alphaTest;
@@ -86,9 +83,9 @@ struct PSOutput
float4 m_color : SV_Target0;
};
float4 SampleTriangleTexture(int texIndex, float2 uv)
float4 SampleTriangleTexture(uint texIndex, float2 uv)
{
if ((InstanceSrg::m_isClamp & (1 << texIndex)) != 0)
if ((InstanceSrg::m_isClamp & (1U << texIndex)) != 0)
{
return InstanceSrg::m_texture[texIndex].Sample(InstanceSrg::m_clampSampler, uv);
}
@@ -120,14 +117,6 @@ PSOutput MainPS(VSOutput IN)
resColor.xyz = LinearToSRGB(resColor.xyz);
}
// Check for flag to premultiply alpha
if (o_preMultiplyAlpha)
{
// premultiply the color by the alpha. This would not be required if we had full access to the separate alpha blend mode
float preMult = resColor.w;
resColor.xyz *= preMult;
}
// If the o_modulate option is not None it means that the verts have two texture indicies. The second texture is used to
// mask the first. This is used for gradient masks.
if (o_modulate == Modulate::Alpha)
@@ -4,7 +4,6 @@
{
"StableId": 1,
"Options": {
"o_preMultiplyAlpha": "false",
"o_alphaTest": "false",
"o_srgbWrite": "true",
"o_modulate": "Modulate::None"
@@ -13,11 +12,26 @@
{
"StableId": 2,
"Options": {
"o_preMultiplyAlpha": "false",
"o_alphaTest": "true",
"o_srgbWrite": "true",
"o_alphaTest": "false",
"o_srgbWrite": "false",
"o_modulate": "Modulate::None"
}
},
{
"StableId": 3,
"Options": {
"o_alphaTest": "true",
"o_srgbWrite": "false",
"o_modulate": "Modulate::None"
}
},
{
"StableId": 4,
"Options": {
"o_alphaTest": "false",
"o_srgbWrite": "false",
"o_modulate": "Modulate::Alpha"
}
}
]
}
@@ -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
*
*/
#include <Asset/BlastChunksAsset.h>
#include <AzCore/RTTI/BehaviorContext.h>
namespace Blast
{
void BlastChunksAsset::SetModelAssetIds(const AZStd::vector<AZ::Data::AssetId>& modelAssetIds)
{
m_modelAssetIds = modelAssetIds;
}
const AZStd::vector<AZ::Data::AssetId>& BlastChunksAsset::GetModelAssetIds() const
{
return m_modelAssetIds;
}
void BlastChunksAsset::Reflect(AZ::ReflectContext* context)
{
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<BlastChunksAsset, AZ::Data::AssetData>()
->Version(1)
->Field("modelAssetIds", &BlastChunksAsset::m_modelAssetIds);
}
}
} // namespace Blast
@@ -0,0 +1,32 @@
/*
* 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/Asset/AssetCommon.h>
namespace Blast
{
//! The product asset file from a .blast_chunks file product asset file
class BlastChunksAsset final
: public AZ::Data::AssetData
{
public:
AZ_RTTI(BlastChunksAsset, "{993F0B0F-37D9-48C6-9CC2-E27D3F3E343E}", AZ::Data::AssetData);
AZ_CLASS_ALLOCATOR(BlastChunksAsset, AZ::SystemAllocator, 0);
BlastChunksAsset() = default;
~BlastChunksAsset() override = default;
void SetModelAssetIds(const AZStd::vector<AZ::Data::AssetId>& modelAssetIds);
const AZStd::vector<AZ::Data::AssetId>& GetModelAssetIds() const;
static void Reflect(AZ::ReflectContext* context);
private:
AZStd::vector<AZ::Data::AssetId> m_modelAssetIds;
};
} // namespace Blast
@@ -1,62 +0,0 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <Asset/BlastSliceAsset.h>
#include <AzCore/RTTI/BehaviorContext.h>
namespace Blast
{
void BlastSliceAsset::SetMeshIdList(const AZStd::vector<AZ::Data::AssetId>& meshAssetIdList)
{
m_meshAssetIdList = meshAssetIdList;
}
const AZStd::vector<AZ::Data::AssetId>& BlastSliceAsset::GetMeshIdList() const
{
return m_meshAssetIdList;
}
void BlastSliceAsset::SetMaterialId(const AZ::Data::AssetId& materialAssetId)
{
m_materialAssetId = materialAssetId;
}
const AZ::Data::AssetId& BlastSliceAsset::GetMaterialId() const
{
return m_materialAssetId;
}
void BlastSliceAsset::Reflect(AZ::ReflectContext* context)
{
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<BlastSliceAsset, AZ::Data::AssetData>()
->Version(1)
->Field("meshAssetIdList", &BlastSliceAsset::m_meshAssetIdList)
->Field("materialAssetId", &BlastSliceAsset::m_materialAssetId);
}
if (AZ::BehaviorContext* behavior = azrtti_cast<AZ::BehaviorContext*>(context))
{
behavior->Class<BlastSliceAsset>("BlastSliceAsset")
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation)
->Attribute(AZ::Script::Attributes::Module, "blast")
->Method("SetMeshIdList", &BlastSliceAsset::SetMeshIdList)
->Method("GetMeshIdList", &BlastSliceAsset::GetMeshIdList)
->Method("SetMaterialId", &BlastSliceAsset::SetMaterialId)
->Method("GetMaterialId", &BlastSliceAsset::GetMaterialId)
->Method(
"GetAssetTypeId",
[](BlastSliceAsset*)
{
return azrtti_typeid<BlastSliceAsset>();
});
}
}
} // namespace Blast
@@ -1,36 +0,0 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <AzCore/Asset/AssetCommon.h>
namespace Blast
{
//! The product asset file from a .blast_slice file product asset file
class BlastSliceAsset final : public AZ::Data::AssetData
{
public:
AZ_RTTI(BlastSliceAsset, "{D04AAF07-EB12-4E50-8964-114A9B9C1FD1}", AZ::Data::AssetData);
AZ_CLASS_ALLOCATOR(BlastSliceAsset, AZ::SystemAllocator, 0);
BlastSliceAsset() = default;
~BlastSliceAsset() override = default;
void SetMeshIdList(const AZStd::vector<AZ::Data::AssetId>& meshAssetIdList);
const AZStd::vector<AZ::Data::AssetId>& GetMeshIdList() const;
void SetMaterialId(const AZ::Data::AssetId& materialAssetId);
const AZ::Data::AssetId& GetMaterialId() const;
static void Reflect(AZ::ReflectContext* context);
private:
AZStd::vector<AZ::Data::AssetId> m_meshAssetIdList;
AZ::Data::AssetId m_materialAssetId;
};
} // namespace Blast
+1 -3
View File
@@ -16,7 +16,6 @@
#ifdef BLAST_EDITOR
#include <Editor/EditorBlastFamilyComponent.h>
#include <Editor/EditorBlastMeshDataComponent.h>
#include <Editor/EditorBlastSliceAssetHandler.h>
#include <Editor/EditorSystemComponent.h>
#endif
@@ -40,8 +39,7 @@ namespace Blast
#ifdef BLAST_EDITOR
EditorSystemComponent::CreateDescriptor(),
EditorBlastFamilyComponent::CreateDescriptor(),
EditorBlastMeshDataComponent::CreateDescriptor(),
BlastSliceAssetStorageComponent::CreateDescriptor(),
EditorBlastMeshDataComponent::CreateDescriptor()
#endif
});
}
@@ -0,0 +1,144 @@
/*
* 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 <Editor/EditorBlastMeshDataComponent.h>
#include <Editor/EditorBlastChunksAssetHandler.h>
#include <AzCore/Component/Entity.h>
#include <AzCore/IO/FileIO.h>
#include <AzCore/IO/GenericStreams.h>
#include <AzCore/Serialization/ObjectStream.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/Utils.h>
#include <AzCore/StringFunc/StringFunc.h>
#include <AzCore/std/smart_ptr/make_shared.h>
#include <AzFramework/StringFunc/StringFunc.h>
#include <AzToolsFramework/API/EditorAssetSystemAPI.h>
namespace Blast
{
//
// EditorBlastChunksAssetHandler
//
EditorBlastChunksAssetHandler::~EditorBlastChunksAssetHandler()
{
Unregister();
}
AZ::Data::AssetPtr EditorBlastChunksAssetHandler::CreateAsset(const AZ::Data::AssetId& id, const AZ::Data::AssetType& type)
{
if (type != GetAssetType())
{
AZ_Error("Blast", type == GetAssetType(), "Invalid asset type! We only handle 'BlastChunksAsset'");
return {};
}
if (!CanHandleAsset(id))
{
return nullptr;
}
return aznew BlastChunksAsset;
}
AZ::Data::AssetHandler::LoadResult EditorBlastChunksAssetHandler::LoadAssetData(
const AZ::Data::Asset<AZ::Data::AssetData>& asset,
AZStd::shared_ptr<AZ::Data::AssetDataStream> stream,
[[maybe_unused]] const AZ::Data::AssetFilterCB& assetLoadFilterCB)
{
BlastChunksAsset* blastChunksAsset = asset.GetAs<BlastChunksAsset>();
AZ_Error("blast", blastChunksAsset,
"This should be a BlastChunksAsset type, as this is the only type we process!");
if (!blastChunksAsset)
{
return LoadResult::Error;
}
// get all products from the source scene asset
bool found = false;
AZStd::vector<AZ::Data::AssetInfo> productsAssetInfo;
AzToolsFramework::AssetSystemRequestBus::BroadcastResult(
found,
&AzToolsFramework::AssetSystemRequestBus::Events::GetAssetsProducedBySourceUUID,
asset.Get()->GetId().m_guid,
productsAssetInfo);
if (!found)
{
AZ_Error("blast",
found,
"Could not find asset models produced by source asset ID %s, verify the output product model assets.",
asset.Get()->GetId().m_guid.ToString<AZStd::string>().c_str());
return LoadResult::Error;
}
// find all model assets
AZStd::vector<AZ::Data::AssetId> modelAssetIdList;
for (const AZ::Data::AssetInfo& assetInfo : productsAssetInfo)
{
if (azrtti_typeid<AZ::RPI::ModelAsset>() == assetInfo.m_assetType)
{
modelAssetIdList.push_back(assetInfo.m_assetId);
}
}
blastChunksAsset->SetModelAssetIds(modelAssetIdList);
return LoadResult::LoadComplete;
}
void EditorBlastChunksAssetHandler::DestroyAsset(AZ::Data::AssetPtr ptr)
{
delete ptr;
}
void EditorBlastChunksAssetHandler::GetHandledAssetTypes(AZStd::vector<AZ::Data::AssetType>& assetTypes)
{
assetTypes.push_back(azrtti_typeid<BlastChunksAsset>());
}
void EditorBlastChunksAssetHandler::Register()
{
AZ_Assert(AZ::Data::AssetManager::IsReady(), "Asset manager isn't ready!");
AZ::Data::AssetManager::Instance().RegisterHandler(this, azrtti_typeid<BlastChunksAsset>());
AZ::AssetTypeInfoBus::Handler::BusConnect(azrtti_typeid<BlastChunksAsset>());
}
void EditorBlastChunksAssetHandler::Unregister()
{
AZ::AssetTypeInfoBus::Handler::BusDisconnect(azrtti_typeid<BlastChunksAsset>());
if (AZ::Data::AssetManager::IsReady())
{
AZ::Data::AssetManager::Instance().UnregisterHandler(this);
}
}
AZ::Data::AssetType EditorBlastChunksAssetHandler::GetAssetType() const
{
return azrtti_typeid<BlastChunksAsset>();
}
const char* EditorBlastChunksAssetHandler::GetAssetTypeDisplayName() const
{
return "Blast Chunks Asset";
}
const char* EditorBlastChunksAssetHandler::GetGroup() const
{
return "Blast";
}
const char* EditorBlastChunksAssetHandler::GetBrowserIcon() const
{
return "Icons/Components/Box.png";
}
void EditorBlastChunksAssetHandler::GetAssetTypeExtensions(AZStd::vector<AZStd::string>& extensions)
{
extensions.push_back("blast_chunks");
}
} // namespace Blast
@@ -0,0 +1,46 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <Asset/BlastChunksAsset.h>
#include <AzCore/Asset/AssetManager.h>
#include <AzCore/Asset/AssetTypeInfoBus.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzToolsFramework/ToolsComponents/EditorComponentBase.h>
namespace Blast
{
class EditorBlastChunksAssetHandler final
: public AZ::Data::AssetHandler
, public AZ::AssetTypeInfoBus::Handler
{
public:
AZ_CLASS_ALLOCATOR(EditorBlastChunksAssetHandler, AZ::SystemAllocator, 0);
~EditorBlastChunksAssetHandler() override;
// AZ::Data::AssetHandler
AZ::Data::AssetPtr CreateAsset(const AZ::Data::AssetId& id, const AZ::Data::AssetType& type) override;
LoadResult LoadAssetData(
const AZ::Data::Asset<AZ::Data::AssetData>& asset,
AZStd::shared_ptr<AZ::Data::AssetDataStream> stream,
const AZ::Data::AssetFilterCB& assetLoadFilterCB) override;
void DestroyAsset(AZ::Data::AssetPtr ptr) override;
void GetHandledAssetTypes(AZStd::vector<AZ::Data::AssetType>& assetTypes) override;
// AZ::AssetTypeInfoBus::Handler
AZ::Data::AssetType GetAssetType() const override;
const char* GetAssetTypeDisplayName() const override;
const char* GetGroup() const override;
const char* GetBrowserIcon() const override;
void GetAssetTypeExtensions(AZStd::vector<AZStd::string>& extensions) override;
void Register();
void Unregister();
};
} // namespace Blast
@@ -45,10 +45,10 @@ namespace Blast
if (AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context))
{
serialize->Class<EditorBlastMeshDataComponent, AzToolsFramework::Components::EditorComponentBase>()
->Version(4)
->Version(5)
->Field("Show Mesh Assets", &EditorBlastMeshDataComponent::m_showMeshAssets)
->Field("Mesh Assets", &EditorBlastMeshDataComponent::m_meshAssets)
->Field("Blast Slice", &EditorBlastMeshDataComponent::m_blastSliceAsset);
->Field("Blast Chunks", &EditorBlastMeshDataComponent::m_blastChunksAsset);
if (AZ::EditContext* ec = serialize->GetEditContext())
{
@@ -77,9 +77,9 @@ namespace Blast
->Attribute(AZ::Edit::Attributes::AutoExpand, false)
->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorBlastMeshDataComponent::OnMeshAssetsChanged)
->DataElement(
AZ::Edit::UIHandlers::Default, &EditorBlastMeshDataComponent::m_blastSliceAsset, "Blast Slice",
"Slice override to fill out meshes and material")
->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorBlastMeshDataComponent::OnSliceAssetChanged);
AZ::Edit::UIHandlers::Default, &EditorBlastMeshDataComponent::m_blastChunksAsset, "Blast Chunks",
"Manifest override to fill out meshes and material")
->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorBlastMeshDataComponent::OnBlastChunksAssetChanged);
}
}
}
@@ -107,23 +107,27 @@ namespace Blast
UnregisterModel();
}
void EditorBlastMeshDataComponent::OnSliceAssetChanged()
void EditorBlastMeshDataComponent::OnBlastChunksAssetChanged()
{
if (!m_blastSliceAsset.GetId().IsValid())
if (!m_blastChunksAsset.GetId().IsValid())
{
return;
}
using namespace AZ::Data;
const AssetId blastAssetId = m_blastChunksAsset.GetId();
m_blastChunksAsset = AssetManager::Instance().GetAsset<BlastChunksAsset>(blastAssetId, AssetLoadBehavior::QueueLoad);
m_blastChunksAsset.BlockUntilLoadComplete();
const AssetId blastAssetId = m_blastSliceAsset.GetId();
m_blastSliceAsset =
AssetManager::Instance().GetAsset<BlastSliceAsset>(blastAssetId, AssetLoadBehavior::QueueLoad);
m_blastSliceAsset.BlockUntilLoadComplete();
if (!m_blastChunksAsset.Get() || m_blastChunksAsset.Get()->GetModelAssetIds().empty())
{
AZ_Warning("blast", false, "Blast Chunk Asset does not contain any models.")
return;
}
// load up the new mesh list
m_meshAssets.clear();
for (const auto& meshId : m_blastSliceAsset.Get()->GetMeshIdList())
for (const auto& meshId : m_blastChunksAsset.Get()->GetModelAssetIds())
{
auto meshAsset = AssetManager::Instance().GetAsset<AZ::RPI::ModelAsset>(meshId, AssetLoadBehavior::QueueLoad);
if (meshAsset)
@@ -135,8 +139,8 @@ namespace Blast
UnregisterModel();
RegisterModel();
AzToolsFramework::ToolsApplicationEvents::Bus::Broadcast(
&AzToolsFramework::ToolsApplicationEvents::InvalidatePropertyDisplay, AzToolsFramework::Refresh_EntireTree);
using namespace AzToolsFramework;
ToolsApplicationEvents::Bus::Broadcast(&ToolsApplicationEvents::InvalidatePropertyDisplay, Refresh_EntireTree);
}
void EditorBlastMeshDataComponent::OnMeshAssetsChanged()
@@ -205,9 +209,9 @@ namespace Blast
gameEntity->CreateComponent<BlastMeshDataComponent>(m_meshAssets);
}
const AZ::Data::Asset<BlastSliceAsset>& EditorBlastMeshDataComponent::GetBlastSliceAsset() const
const AZ::Data::Asset<BlastChunksAsset>& EditorBlastMeshDataComponent::GetBlastChunksAsset() const
{
return m_blastSliceAsset;
return m_blastChunksAsset;
}
const AZStd::vector<AZ::Data::Asset<AZ::RPI::ModelAsset>>& EditorBlastMeshDataComponent::GetMeshAssets() const
@@ -7,7 +7,7 @@
*/
#pragma once
#include <Asset/BlastSliceAsset.h>
#include <Asset/BlastChunksAsset.h>
#include <Atom/Feature/Mesh/MeshFeatureProcessorInterface.h>
#include <Atom/RPI.Public/Model/Model.h>
#include <AtomLyIntegration/CommonFeatures/Material/MaterialComponentBus.h>
@@ -43,14 +43,14 @@ namespace Blast
// EditorComponentBase
void BuildGameEntity(AZ::Entity* gameEntity) override;
const AZ::Data::Asset<BlastSliceAsset>& GetBlastSliceAsset() const;
const AZ::Data::Asset<BlastChunksAsset>& GetBlastChunksAsset() const;
const AZStd::vector<AZ::Data::Asset<AZ::RPI::ModelAsset>>& GetMeshAssets() const;
void OnMaterialsUpdated(const AZ::Render::MaterialAssignmentMap& materials) override;
void OnTransformChanged(const AZ::Transform& local, const AZ::Transform& world) override;
private:
void OnSliceAssetChanged();
void OnBlastChunksAssetChanged();
void OnMeshAssetsChanged();
AZ::Crc32 GetMeshAssetsVisibility() const;
void OnMeshAssetsVisibilityChanged();
@@ -62,7 +62,7 @@ namespace Blast
//////////////////////////////////////////////////////////////////////////
// Reflected data
bool m_showMeshAssets = false;
AZ::Data::Asset<BlastSliceAsset> m_blastSliceAsset;
AZ::Data::Asset<BlastChunksAsset> m_blastChunksAsset;
AZStd::vector<AZ::Data::Asset<AZ::RPI::ModelAsset>> m_meshAssets;
//////////////////////////////////////////////////////////////////////////
@@ -1,345 +0,0 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <Editor/EditorBlastMeshDataComponent.h>
#include <Editor/EditorBlastSliceAssetHandler.h>
#include <AzCore/Component/Entity.h>
#include <AzCore/IO/FileIO.h>
#include <AzCore/IO/GenericStreams.h>
#include <AzCore/Serialization/ObjectStream.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/Utils.h>
#include <AzCore/Slice/SliceComponent.h>
#include <AzCore/StringFunc/StringFunc.h>
#include <AzCore/std/smart_ptr/make_shared.h>
#include <AzFramework/StringFunc/StringFunc.h>
#include <SceneAPI/SceneCore/Containers/RuleContainer.h>
#include <SceneAPI/SceneCore/Containers/SceneManifest.h>
#include <SceneAPI/SceneCore/DataTypes/Groups/IMeshGroup.h>
#include <SceneAPI/SceneData/Groups/MeshGroup.h>
#include <SceneAPI/SceneData/ManifestBase/SceneNodeSelectionList.h>
#include <SceneAPI/SceneData/Rules/MaterialRule.h>
#include <GFxFramework/MaterialIO/Material.h>
namespace Blast
{
// BlastSliceAssetStorageComponent
void BlastSliceAssetStorageComponent::Reflect(AZ::ReflectContext* context)
{
using namespace AZ::Edit;
if (AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context))
{
serialize->Class<BlastSliceAssetStorageComponent, AzToolsFramework::Components::EditorComponentBase>()
->Version(2)
->Field("Mesh Data", &BlastSliceAssetStorageComponent::m_meshAssetIdList)
->Field("Mesh Path List", &BlastSliceAssetStorageComponent::m_meshAssetPathList);
if (AZ::EditContext* ec = serialize->GetEditContext())
{
ec->Class<BlastSliceAssetStorageComponent>(
"Blast Slice Storage Component", "Used process blast slice data")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::Category, "Physics")
->Attribute(AZ::Edit::Attributes::Icon, "Icons/Components/Box.png")
->Attribute(AZ::Edit::Attributes::ViewportIcon, "Icons/Components/Viewport/Box.png")
->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC_CE("Game"))
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->Attribute(AZ::Edit::Attributes::AddableByUser, false)
->DataElement(
AZ::Edit::UIHandlers::Default, &BlastSliceAssetStorageComponent::m_meshAssetIdList, "Mesh Data",
"Slice data to fill out the mesh list")
->DataElement(
AZ::Edit::UIHandlers::Default, &BlastSliceAssetStorageComponent::m_meshAssetPathList,
"Mesh Paths", "The mesh path list");
}
}
if (AZ::BehaviorContext* behavior = azrtti_cast<AZ::BehaviorContext*>(context))
{
behavior->Class<BlastSliceAssetStorageComponent>("BlastSliceAssetStorageComponent")
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation)
->Attribute(AZ::Script::Attributes::Module, "blast")
->Method("GenerateAssetInfo", &BlastSliceAssetStorageComponent::GenerateAssetInfo)
->Method("WriteMaterialFile", &BlastSliceAssetStorageComponent::WriteMaterialFile);
}
}
bool BlastSliceAssetStorageComponent::GenerateAssetInfo(
const AZStd::vector<AZStd::string>& chunkNames, AZStd::string_view blastFilename,
AZStd::string_view assetinfoFilename)
{
AZ::SerializeContext* serializeContext = nullptr;
AZ::ComponentApplicationBus::BroadcastResult(
serializeContext, &AZ::ComponentApplicationBus::Events::GetSerializeContext);
if (serializeContext == nullptr)
{
return false;
}
using namespace AZ::SceneAPI::Containers;
using namespace AZ::SceneAPI::SceneData;
AZStd::string filename;
AZ::StringFunc::Path::Split(blastFilename.data(), nullptr, nullptr, &filename, nullptr);
AZStd::any sceneManifestPointer(serializeContext->CreateAny(azrtti_typeid<SceneManifest>()));
SceneManifest* sceneManifest = AZStd::any_cast<SceneManifest>(&sceneManifestPointer);
AZStd::vector<AZStd::any> meshGroupData;
meshGroupData.reserve(chunkNames.size());
AZStd::vector<AZStd::any> materialRuleData;
materialRuleData.reserve(chunkNames.size());
for (const AZStd::string& chunkName : chunkNames)
{
meshGroupData.emplace_back(serializeContext->CreateAny(azrtti_typeid<MeshGroup>()));
AZStd::any& meshGroupPointer = meshGroupData.back();
MeshGroup* meshGroup = AZStd::any_cast<MeshGroup>(&meshGroupPointer);
// make selection list
meshGroup->GetSceneNodeSelectionList().RemoveSelectedNode("RootNode");
for (const AZStd::string& node : chunkNames)
{
meshGroup->GetSceneNodeSelectionList().RemoveSelectedNode(
AZStd::string::format("RootNode.%s", node.c_str()));
}
meshGroup->GetSceneNodeSelectionList().AddSelectedNode(
AZStd::string::format("RootNode.%s", chunkName.c_str()));
// create a default material for the mesh group
materialRuleData.emplace_back(serializeContext->CreateAny(azrtti_typeid<MaterialRule>()));
AZStd::any& materialRulePointer = materialRuleData.back();
MaterialRule* materialRule = AZStd::any_cast<MaterialRule>(&materialRulePointer);
// override the deleter since the AZStd::any will clean up later on
AZStd::shared_ptr<MaterialRule> materialRuleEntry = AZStd::shared_ptr<MaterialRule>(
materialRule,
[](auto)
{
});
meshGroup->GetRuleContainer().AddRule(materialRuleEntry);
// construct the asset name for the chunk's mesh group
AZStd::string meshGroupName(filename);
meshGroupName.append("-");
meshGroupName.append(chunkName);
// TODO: Uncomment lines below as part of SPEC-3542
// meshGroup->OverrideId(AZ::Uuid::CreateName(meshGroupName.c_str()));
// meshGroup->SetName(AZStd::move(meshGroupName));
// override the deleter since the AZStd::any will clean up later on
AZStd::shared_ptr<MeshGroup> meshGroupEntry = AZStd::shared_ptr<MeshGroup>(
meshGroup,
[](auto)
{
});
sceneManifest->AddEntry(AZStd::move(meshGroupEntry));
}
return sceneManifest->SaveToFile(assetinfoFilename.data());
}
bool BlastSliceAssetStorageComponent::WriteMaterialFile(
AZStd::string_view materialGroupName, const AZStd::vector<AZStd::string>& materialNames,
AZStd::string_view materialFilename)
{
AZ::GFxFramework::MaterialGroup group;
for (const auto& texture : materialNames)
{
auto mat = AZStd::make_shared<AZ::GFxFramework::Material>();
mat->SetName(texture);
mat->SetTexture(AZ::GFxFramework::TextureMapType::Diffuse, "EngineAssets/Textures/white.dds");
group.AddMaterial(mat);
}
group.SetMtlName(materialGroupName);
return group.WriteMtlFile(materialFilename.data());
}
//
// EditorBlastSliceAssetHandler
//
EditorBlastSliceAssetHandler::~EditorBlastSliceAssetHandler()
{
Unregister();
}
AZ::Data::AssetPtr EditorBlastSliceAssetHandler::CreateAsset(
const AZ::Data::AssetId& id, const AZ::Data::AssetType& type)
{
if (type != GetAssetType())
{
AZ_Error("Blast", type == GetAssetType(), "Invalid asset type! We only handle 'BlastAsset'");
return {};
}
if (!CanHandleAsset(id))
{
return nullptr;
}
return aznew BlastSliceAsset;
}
AZ::Data::AssetHandler::LoadResult EditorBlastSliceAssetHandler::LoadAssetData(
const AZ::Data::Asset<AZ::Data::AssetData>& asset, AZStd::shared_ptr<AZ::Data::AssetDataStream> stream,
const AZ::Data::AssetFilterCB& assetLoadFilterCB)
{
BlastSliceAsset* blastSliceAssetData = asset.GetAs<BlastSliceAsset>();
AZ_Error(
"blast", blastSliceAssetData,
"This should be a BlastSliceAsset type, as this is the only type we process!");
AZ::SerializeContext* serializeContext = nullptr;
AZ::ComponentApplicationBus::BroadcastResult(
serializeContext, &AZ::ComponentApplicationBus::Events::GetSerializeContext);
if (blastSliceAssetData && serializeContext)
{
AZ::ObjectStream::FilterDescriptor filter(assetLoadFilterCB);
AZStd::unique_ptr<AZ::Entity> baseEntity(
AZ::Utils::LoadObjectFromStream<AZ::Entity>(*stream, serializeContext, filter));
AZ_Error("Blast", baseEntity, "Could not load slice root entity {asset id}");
if (!baseEntity)
{
return LoadResult::Error;
}
auto&& sliceComponent = baseEntity->FindComponent<AZ::SliceComponent>();
AZ_Error("Blast", sliceComponent, "blast_slice entity missing SliceComponent!");
if (sliceComponent == nullptr)
{
return LoadResult::Error;
}
AZStd::vector<AZ::Entity*> enityList;
sliceComponent->GetEntities(enityList);
for (auto&& entity : enityList)
{
// the base element type to store Blast mesh data is the BlastSliceAssetStorageComponent
auto&& blastSliceAssetStorage = entity->FindComponent<BlastSliceAssetStorageComponent>();
if (blastSliceAssetStorage)
{
if (blastSliceAssetStorage->GetMeshData().empty() == false)
{
blastSliceAssetData->SetMeshIdList(blastSliceAssetStorage->GetMeshData());
return LoadResult::LoadComplete;
}
else if (blastSliceAssetStorage->GetMeshPathList().empty() == false)
{
AZStd::vector<AZ::Data::AssetId> meshAssetIdList;
meshAssetIdList.reserve(blastSliceAssetStorage->GetMeshPathList().size());
for (auto&& assetPath : blastSliceAssetStorage->GetMeshPathList())
{
AZ::Data::AssetId meshAssetId;
AZ::Data::AssetCatalogRequestBus::BroadcastResult(
meshAssetId, &AZ::Data::AssetCatalogRequestBus::Events::GetAssetIdByPath,
assetPath.c_str(), AZ::Data::s_invalidAssetType, false);
if (meshAssetId.IsValid())
{
meshAssetIdList.emplace_back(meshAssetId);
}
}
blastSliceAssetData->SetMeshIdList(meshAssetIdList);
return LoadResult::LoadComplete;
}
}
// back up logic to load blast data for the EditorBlastMeshDataComponent
auto&& meshDataComponent = entity->FindComponent<EditorBlastMeshDataComponent>();
if (meshDataComponent)
{
auto&& innerBlastSliceAsset = meshDataComponent->GetBlastSliceAsset();
if (innerBlastSliceAsset.IsReady())
{
blastSliceAssetData->SetMeshIdList(innerBlastSliceAsset.Get()->GetMeshIdList());
blastSliceAssetData->SetMaterialId(innerBlastSliceAsset.Get()->GetMaterialId());
return LoadResult::LoadComplete;
}
else
{
auto&& meshDataList = meshDataComponent->GetMeshAssets();
AZStd::vector<AZ::Data::AssetId> meshAssetIdList;
meshAssetIdList.reserve(meshDataList.size());
for (auto&& meshData : meshDataList)
{
AZ::RPI::ModelAsset* meshAsset = meshData.Get();
if (meshAsset)
{
meshAssetIdList.push_back(meshAsset->GetId());
}
}
blastSliceAssetData->SetMeshIdList(meshAssetIdList);
return LoadResult::LoadComplete;
}
}
}
AZ_Error(
"Blast", false, "blast_slice assetId:%s missing EditorBlastMeshDataComponent!",
asset->GetId().ToString<AZStd::string>().c_str());
}
return LoadResult::Error;
}
void EditorBlastSliceAssetHandler::DestroyAsset(AZ::Data::AssetPtr ptr)
{
delete ptr;
}
void EditorBlastSliceAssetHandler::GetHandledAssetTypes(AZStd::vector<AZ::Data::AssetType>& assetTypes)
{
assetTypes.push_back(azrtti_typeid<BlastSliceAsset>());
}
void EditorBlastSliceAssetHandler::Register()
{
AZ_Assert(AZ::Data::AssetManager::IsReady(), "Asset manager isn't ready!");
AZ::Data::AssetManager::Instance().RegisterHandler(this, azrtti_typeid<BlastSliceAsset>());
AZ::AssetTypeInfoBus::Handler::BusConnect(azrtti_typeid<BlastSliceAsset>());
}
void EditorBlastSliceAssetHandler::Unregister()
{
AZ::AssetTypeInfoBus::Handler::BusDisconnect(azrtti_typeid<BlastSliceAsset>());
if (AZ::Data::AssetManager::IsReady())
{
AZ::Data::AssetManager::Instance().UnregisterHandler(this);
}
}
AZ::Data::AssetType EditorBlastSliceAssetHandler::GetAssetType() const
{
return azrtti_typeid<BlastSliceAsset>();
}
const char* EditorBlastSliceAssetHandler::GetAssetTypeDisplayName() const
{
return "Blast Slice Asset";
}
const char* EditorBlastSliceAssetHandler::GetGroup() const
{
return "Blast";
}
const char* EditorBlastSliceAssetHandler::GetBrowserIcon() const
{
return "Icons/Components/Box.png";
}
void EditorBlastSliceAssetHandler::GetAssetTypeExtensions(AZStd::vector<AZStd::string>& extensions)
{
extensions.push_back("blast_slice");
}
} // namespace Blast
@@ -1,101 +0,0 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <Asset/BlastSliceAsset.h>
#include <AzCore/Asset/AssetManager.h>
#include <AzCore/Asset/AssetTypeInfoBus.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzToolsFramework/ToolsComponents/EditorComponentBase.h>
namespace Blast
{
//! Used to create store asset references (i.e. ids) to fill out the EditorBlastMeshDataComponent
class BlastSliceAssetStorageComponent final : public AzToolsFramework::Components::EditorComponentBase
{
public:
AZ_COMPONENT(
BlastSliceAssetStorageComponent, "{696C7E62-1EA4-41E2-B4F6-7BD0D30888DC}",
AzToolsFramework::Components::EditorComponentBase);
~BlastSliceAssetStorageComponent() override = default;
static void Reflect(AZ::ReflectContext* context);
const AZStd::vector<AZ::Data::AssetId>& GetMeshData() const
{
return m_meshAssetIdList;
}
void SetMeshData(const AZStd::vector<AZ::Data::AssetId>& meshAssetIdList)
{
m_meshAssetIdList = meshAssetIdList;
}
const AZStd::vector<AZStd::string>& GetMeshPathList() const
{
return m_meshAssetPathList;
}
void SetMeshPathList(const AZStd::vector<AZStd::string>& meshAssetPathList)
{
m_meshAssetPathList = meshAssetPathList;
}
private:
// AZ::Component interface implementation
void Activate() override {}
void Deactivate() override {}
// EditorComponentBase
void BuildGameEntity([[maybe_unused]] AZ::Entity* gameEntity) override {}
// Script API
bool GenerateAssetInfo(
const AZStd::vector<AZStd::string>& chunkNames,
AZStd::string_view blastFilename,
AZStd::string_view assetinfoFilename);
bool WriteMaterialFile(
AZStd::string_view materialGroupName,
const AZStd::vector<AZStd::string>& materialNames,
AZStd::string_view materialFilename);
AZStd::vector<AZ::Data::AssetId> m_meshAssetIdList;
AZStd::vector<AZStd::string> m_meshAssetPathList;
};
class EditorBlastSliceAssetHandler final
: public AZ::Data::AssetHandler
, public AZ::AssetTypeInfoBus::Handler
{
public:
AZ_CLASS_ALLOCATOR(EditorBlastSliceAssetHandler, AZ::SystemAllocator, 0);
~EditorBlastSliceAssetHandler() override;
// AZ::Data::AssetHandler
AZ::Data::AssetPtr CreateAsset(const AZ::Data::AssetId& id, const AZ::Data::AssetType& type) override;
LoadResult LoadAssetData(
const AZ::Data::Asset<AZ::Data::AssetData>& asset, AZStd::shared_ptr<AZ::Data::AssetDataStream> stream,
const AZ::Data::AssetFilterCB& assetLoadFilterCB) override;
void DestroyAsset(AZ::Data::AssetPtr ptr) override;
void GetHandledAssetTypes(AZStd::vector<AZ::Data::AssetType>& assetTypes) override;
// AZ::AssetTypeInfoBus::Handler
AZ::Data::AssetType GetAssetType() const override;
const char* GetAssetTypeDisplayName() const override;
const char* GetGroup() const override;
const char* GetBrowserIcon() const override;
void GetAssetTypeExtensions(AZStd::vector<AZStd::string>& extensions) override;
void Register();
void Unregister();
};
} // namespace Blast
@@ -6,7 +6,7 @@
*
*/
#include <Asset/BlastSliceAsset.h>
#include <Asset/BlastChunksAsset.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <Editor/EditorSystemComponent.h>
#include <Editor/EditorWindow.h>
@@ -16,7 +16,7 @@ namespace Blast
{
void EditorSystemComponent::Reflect(AZ::ReflectContext* context)
{
BlastSliceAsset::Reflect(context);
BlastChunksAsset::Reflect(context);
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
@@ -26,14 +26,14 @@ namespace Blast
void EditorSystemComponent::Activate()
{
m_editorBlastSliceAssetHandler = AZStd::make_unique<EditorBlastSliceAssetHandler>();
m_editorBlastSliceAssetHandler->Register();
m_editorBlastChunksAssetHandler = AZStd::make_unique<EditorBlastChunksAssetHandler>();
m_editorBlastChunksAssetHandler->Register();
auto assetCatalog = AZ::Data::AssetCatalogRequestBus::FindFirstHandler();
if (assetCatalog)
{
assetCatalog->EnableCatalogForAsset(azrtti_typeid<BlastSliceAsset>());
assetCatalog->AddExtension("blast_slice");
assetCatalog->EnableCatalogForAsset(azrtti_typeid<BlastChunksAsset>());
assetCatalog->AddExtension("blast_chunks");
}
AzToolsFramework::EditorEvents::Bus::Handler::BusConnect();
@@ -46,7 +46,7 @@ namespace Blast
void EditorSystemComponent::Deactivate()
{
AzToolsFramework::EditorEvents::Bus::Handler::BusDisconnect();
m_editorBlastSliceAssetHandler.reset();
m_editorBlastChunksAssetHandler.reset();
}
// This will be called when the IEditor instance is ready
@@ -11,7 +11,7 @@
#include <AzCore/Component/Component.h>
#include <AzCore/Component/TickBus.h>
#include <AzToolsFramework/Entity/EditorEntityContextBus.h>
#include <Editor/EditorBlastSliceAssetHandler.h>
#include <Editor/EditorBlastChunksAssetHandler.h>
namespace Blast
{
@@ -39,7 +39,7 @@ namespace Blast
required.push_back(AZ_CRC("BlastService", 0x75beae2d));
}
AZStd::unique_ptr<EditorBlastSliceAssetHandler> m_editorBlastSliceAssetHandler;
AZStd::unique_ptr<EditorBlastChunksAssetHandler> m_editorBlastChunksAssetHandler;
// AZ::Component
void Activate() override;
@@ -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
*
*/
#include <Editor/EditorBlastChunksAssetHandler.h>
#include <Editor/EditorBlastMeshDataComponent.h>
#include <Asset/BlastChunksAsset.h>
#include <AzTest/AzTest.h>
#include <AzCore/UnitTest/TestTypes.h>
#include <gmock/gmock.h>
#include <AzCore/UnitTest/MockComponentApplication.h>
namespace UnitTest
{
MockComponentApplication::MockComponentApplication()
{
AZ::ComponentApplicationBus::Handler::BusConnect();
AZ::Interface<AZ::ComponentApplicationRequests>::Register(this);
}
MockComponentApplication::~MockComponentApplication()
{
AZ::Interface<AZ::ComponentApplicationRequests>::Unregister(this);
AZ::ComponentApplicationBus::Handler::BusDisconnect();
}
class MockAssetCatalogRequestBusHandler final
: public AZ::Data::AssetCatalogRequestBus::Handler
{
public:
MockAssetCatalogRequestBusHandler()
{
AZ::Data::AssetCatalogRequestBus::Handler::BusConnect();
}
virtual ~MockAssetCatalogRequestBusHandler()
{
AZ::Data::AssetCatalogRequestBus::Handler::BusDisconnect();
}
MOCK_METHOD3(GetAssetIdByPath, AZ::Data::AssetId(const char*, const AZ::Data::AssetType&, bool));
MOCK_METHOD1(GetAssetInfoById, AZ::Data::AssetInfo(const AZ::Data::AssetId&));
MOCK_METHOD1(AddAssetType, void(const AZ::Data::AssetType&));
MOCK_METHOD1(AddDeltaCatalog, bool(AZStd::shared_ptr<AzFramework::AssetRegistry>));
MOCK_METHOD1(AddExtension, void(const char*));
MOCK_METHOD0(ClearCatalog, void());
MOCK_METHOD5(CreateBundleManifest, bool(const AZStd::string&, const AZStd::vector<AZStd::string>&, const AZStd::string&, int, const AZStd::vector<AZStd::string>&));
MOCK_METHOD2(CreateDeltaCatalog, bool(const AZStd::vector<AZStd::string>&, const AZStd::string&));
MOCK_METHOD0(DisableCatalog, void());
MOCK_METHOD1(EnableCatalogForAsset, void(const AZ::Data::AssetType&));
MOCK_METHOD3(EnumerateAssets, void(BeginAssetEnumerationCB, AssetEnumerationCB, EndAssetEnumerationCB));
MOCK_METHOD1(GenerateAssetIdTEMP, AZ::Data::AssetId(const char*));
MOCK_METHOD1(GetAllProductDependencies, AZ::Outcome<AZStd::vector<AZ::Data::ProductDependency>, AZStd::string>(const AZ::Data::AssetId&));
MOCK_METHOD3(GetAllProductDependenciesFilter, AZ::Outcome<AZStd::vector<AZ::Data::ProductDependency>, AZStd::string>(const AZ::Data::AssetId&, const AZStd::unordered_set<AZ::Data::AssetId>&, const AZStd::vector<AZStd::string>&));
MOCK_METHOD1(GetAssetPathById, AZStd::string(const AZ::Data::AssetId&));
MOCK_METHOD1(GetDirectProductDependencies, AZ::Outcome<AZStd::vector<AZ::Data::ProductDependency>, AZStd::string>(const AZ::Data::AssetId&));
MOCK_METHOD1(GetHandledAssetTypes, void(AZStd::vector<AZ::Data::AssetType>&));
MOCK_METHOD0(GetRegisteredAssetPaths, AZStd::vector<AZStd::string>());
MOCK_METHOD2(InsertDeltaCatalog, bool(AZStd::shared_ptr<AzFramework::AssetRegistry>, size_t));
MOCK_METHOD2(InsertDeltaCatalogBefore, bool(AZStd::shared_ptr<AzFramework::AssetRegistry>, AZStd::shared_ptr<AzFramework::AssetRegistry>));
MOCK_METHOD1(LoadCatalog, bool(const char*));
MOCK_METHOD2(RegisterAsset, void(const AZ::Data::AssetId&, AZ::Data::AssetInfo&));
MOCK_METHOD1(RemoveDeltaCatalog, bool(AZStd::shared_ptr<AzFramework::AssetRegistry>));
MOCK_METHOD1(SaveCatalog, bool(const char*));
MOCK_METHOD0(StartMonitoringAssets, void());
MOCK_METHOD0(StopMonitoringAssets, void());
MOCK_METHOD1(UnregisterAsset, void(const AZ::Data::AssetId&));
};
class MockAssetManager
: public AZ::Data::AssetManager
{
public:
MockAssetManager(const AZ::Data::AssetManager::Descriptor& desc) :
AssetManager(desc)
{
}
};
class EditorBlastChunkAssetHandlerTestFixture
: public AllocatorsTestFixture
{
public:
AZStd::unique_ptr<UnitTest::MockComponentApplication> m_mockComponentApplicationBusHandler;
AZStd::unique_ptr<MockAssetCatalogRequestBusHandler> m_mockAssetCatalogRequestBusHandler;
AZStd::unique_ptr<MockAssetManager> m_mockAssetManager;
AZStd::unique_ptr<AZ::SerializeContext> m_serializeContext;
void SetUpChunkComponents()
{
m_serializeContext = AZStd::make_unique<AZ::SerializeContext>();
AZ::Entity::Reflect(m_serializeContext.get());
AzToolsFramework::Components::EditorComponentBase::Reflect(m_serializeContext.get());
}
void TearDownChunkComponents()
{
m_serializeContext.reset();
}
void SetUp() override final
{
AllocatorsTestFixture::SetUp();
AZ::AllocatorInstance<AZ::PoolAllocator>::Create();
AZ::AllocatorInstance<AZ::ThreadPoolAllocator>::Create();
m_mockComponentApplicationBusHandler = AZStd::make_unique<UnitTest::MockComponentApplication>();
m_mockAssetCatalogRequestBusHandler = AZStd::make_unique<MockAssetCatalogRequestBusHandler>();
m_mockAssetManager = AZStd::make_unique<MockAssetManager>(AZ::Data::AssetManager::Descriptor{});
AZ::Data::AssetManager::SetInstance(m_mockAssetManager.get());
}
void TearDown() override final
{
m_mockAssetManager.release();
AZ::Data::AssetManager::Destroy();
m_mockAssetCatalogRequestBusHandler.reset();
m_mockComponentApplicationBusHandler.reset();
AZ::AllocatorInstance<AZ::ThreadPoolAllocator>::Destroy();
AZ::AllocatorInstance<AZ::PoolAllocator>::Destroy();
AllocatorsTestFixture::TearDown();
}
void SaveChunkAssetToStream(AZ::Entity* chunkAssetEntity, AZStd::vector<char>& buffer)
{
buffer.clear();
AZ::IO::ByteContainerStream<AZStd::vector<char>> stream(&buffer);
AZ::ObjectStream* objStream = AZ::ObjectStream::Create(&stream, *m_serializeContext.get(), AZ::ObjectStream::ST_XML);
objStream->WriteClass(chunkAssetEntity);
EXPECT_TRUE(objStream->Finalize());
}
};
TEST_F(EditorBlastChunkAssetHandlerTestFixture, EditorBlastChunkAssetHandler_AssetManager_Registered)
{
Blast::EditorBlastChunksAssetHandler handler;
handler.Register();
EXPECT_NE(nullptr, AZ::Data::AssetManager::Instance().GetHandler(azrtti_typeid<Blast::BlastChunksAsset>()));
handler.Unregister();
}
TEST_F(EditorBlastChunkAssetHandlerTestFixture, EditorBlastChunkAssetHandler_AssetTypeInfoBus_Responds)
{
auto assetId = azrtti_typeid<Blast::BlastChunksAsset>();
Blast::EditorBlastChunksAssetHandler handler;
handler.Register();
AZ::Data::AssetType assetType = AZ::Uuid::CreateNull();
AZ::AssetTypeInfoBus::EventResult(assetType, assetId, &AZ::AssetTypeInfoBus::Events::GetAssetType);
EXPECT_NE(AZ::Uuid::CreateNull(), assetType);
const char* displayName = nullptr;
AZ::AssetTypeInfoBus::EventResult(displayName, assetId, &AZ::AssetTypeInfoBus::Events::GetAssetTypeDisplayName);
EXPECT_STREQ("Blast Chunks Asset", displayName);
const char* group = nullptr;
AZ::AssetTypeInfoBus::EventResult(group, assetId, &AZ::AssetTypeInfoBus::Events::GetGroup);
EXPECT_STREQ("Blast", group);
const char* icon = nullptr;
AZ::AssetTypeInfoBus::EventResult(icon, assetId, &AZ::AssetTypeInfoBus::Events::GetBrowserIcon);
EXPECT_STREQ("Icons/Components/Box.png", icon);
AZStd::vector<AZStd::string> extensions;
AZ::AssetTypeInfoBus::Event(assetId, &AZ::AssetTypeInfoBus::Events::GetAssetTypeExtensions, extensions);
ASSERT_EQ(1, extensions.size());
ASSERT_EQ("blast_chunks", extensions[0]);
handler.Unregister();
}
TEST_F(EditorBlastChunkAssetHandlerTestFixture, EditorBlastChunkAssetHandler_AssetHandler_Ready)
{
auto assetType = azrtti_typeid<Blast::BlastChunksAsset>();
auto&& assetManager = AZ::Data::AssetManager::Instance();
Blast::EditorBlastChunksAssetHandler handler;
handler.Register();
EXPECT_EQ(&handler, assetManager.GetHandler(assetType));
// create and release an instance of the BlastChunkAsset asset type
{
using ::testing::Return;
using ::testing::_;
EXPECT_CALL(*m_mockAssetCatalogRequestBusHandler, GetAssetInfoById(_))
.Times(2)
.WillRepeatedly(Return(AZ::Data::AssetInfo{}));
auto assetPtr = assetManager.CreateAsset<Blast::BlastChunksAsset>(AZ::Data::AssetId(AZ::Uuid::CreateRandom(), 0));
EXPECT_NE(nullptr, assetPtr.Get());
EXPECT_EQ(azrtti_typeid<Blast::BlastChunksAsset>(), assetPtr.GetType());
}
handler.Unregister();
}
}
@@ -1,377 +0,0 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <Editor/EditorBlastSliceAssetHandler.h>
#include <Editor/EditorBlastMeshDataComponent.h>
#include <Asset/BlastSliceAsset.h>
#include <AzCore/UnitTest/MockComponentApplication.h>
#include <AzTest/AzTest.h>
#include <AzCore/UnitTest/TestTypes.h>
#include <gmock/gmock.h>
namespace UnitTest
{
class MockComponentApplicationBusHandler final
//: public MockComponentApplication
: public AZ::ComponentApplicationBus::Handler
{
public:
MockComponentApplicationBusHandler()
{
AZ::ComponentApplicationBus::Handler::BusConnect();
}
virtual ~MockComponentApplicationBusHandler()
{
AZ::ComponentApplicationBus::Handler::BusDisconnect();
}
MOCK_METHOD0(Destroy, void());
MOCK_METHOD1(RegisterComponentDescriptor, void(const AZ::ComponentDescriptor*));
MOCK_METHOD1(UnregisterComponentDescriptor, void(const AZ::ComponentDescriptor*));
MOCK_METHOD1(RemoveEntity, bool(AZ::Entity*));
MOCK_METHOD1(DeleteEntity, bool(const AZ::EntityId&));
MOCK_METHOD1(GetEntityName, AZStd::string(const AZ::EntityId&));
MOCK_METHOD1(AddEntity, bool(AZ::Entity*));
MOCK_METHOD1(FindEntity, AZ::Entity*(const AZ::EntityId&));
MOCK_METHOD1(EnumerateEntities, void(const ComponentApplicationRequests::EntityCallback&));
MOCK_METHOD0(GetApplication, AZ::ComponentApplication* ());
MOCK_METHOD0(GetSerializeContext, AZ::SerializeContext* ());
MOCK_METHOD0(GetBehaviorContext, AZ::BehaviorContext* ());
MOCK_METHOD0(GetJsonRegistrationContext, AZ::JsonRegistrationContext* ());
MOCK_METHOD0(GetAppRoot, const char* ());
MOCK_CONST_METHOD0(GetExecutableFolder, const char* ());
MOCK_METHOD0(GetDrillerManager, AZ::Debug::DrillerManager* ());
MOCK_METHOD0(GetTickDeltaTime, float());
MOCK_METHOD1(Tick, void(float));
MOCK_METHOD0(TickSystem, void());
MOCK_CONST_METHOD0(GetRequiredSystemComponents, AZ::ComponentTypeList());
MOCK_METHOD1(ResolveModulePath, void(AZ::OSString&));
MOCK_METHOD0(CreateSerializeContext, void());
MOCK_METHOD0(DestroySerializeContext, void());
MOCK_METHOD0(CreateBehaviorContext, void());
MOCK_METHOD0(DestroyBehaviorContext, void());
MOCK_METHOD0(RegisterCoreComponents, void());
MOCK_METHOD1(AddSystemComponents, void(AZ::Entity*));
MOCK_METHOD0(ReflectSerialize, void());
MOCK_METHOD1(Reflect, void(AZ::ReflectContext*));
MOCK_CONST_METHOD0(GetBinFolder, const char* ());
};
class MockAssetCatalogRequestBusHandler final
: public AZ::Data::AssetCatalogRequestBus::Handler
{
public:
MockAssetCatalogRequestBusHandler()
{
AZ::Data::AssetCatalogRequestBus::Handler::BusConnect();
}
virtual ~MockAssetCatalogRequestBusHandler()
{
AZ::Data::AssetCatalogRequestBus::Handler::BusDisconnect();
}
MOCK_METHOD3(GetAssetIdByPath, AZ::Data::AssetId(const char*, const AZ::Data::AssetType&, bool));
MOCK_METHOD1(GetAssetInfoById, AZ::Data::AssetInfo(const AZ::Data::AssetId&));
MOCK_METHOD1(AddAssetType, void(const AZ::Data::AssetType&));
MOCK_METHOD1(AddDeltaCatalog, bool(AZStd::shared_ptr<AzFramework::AssetRegistry>));
MOCK_METHOD1(AddExtension, void(const char*));
MOCK_METHOD0(ClearCatalog, void());
MOCK_METHOD5(CreateBundleManifest, bool(const AZStd::string&, const AZStd::vector<AZStd::string>&, const AZStd::string&, int, const AZStd::vector<AZStd::string>&));
MOCK_METHOD2(CreateDeltaCatalog, bool(const AZStd::vector<AZStd::string>&, const AZStd::string&));
MOCK_METHOD0(DisableCatalog, void());
MOCK_METHOD1(EnableCatalogForAsset, void(const AZ::Data::AssetType&));
MOCK_METHOD3(EnumerateAssets, void(BeginAssetEnumerationCB, AssetEnumerationCB, EndAssetEnumerationCB));
MOCK_METHOD1(GenerateAssetIdTEMP, AZ::Data::AssetId(const char*));
MOCK_METHOD1(GetAllProductDependencies, AZ::Outcome<AZStd::vector<AZ::Data::ProductDependency>, AZStd::string>(const AZ::Data::AssetId&));
MOCK_METHOD3(GetAllProductDependenciesFilter, AZ::Outcome<AZStd::vector<AZ::Data::ProductDependency>, AZStd::string>(const AZ::Data::AssetId&, const AZStd::unordered_set<AZ::Data::AssetId>&, const AZStd::vector<AZStd::string>&));
MOCK_METHOD1(GetAssetPathById, AZStd::string(const AZ::Data::AssetId&));
MOCK_METHOD1(GetDirectProductDependencies, AZ::Outcome<AZStd::vector<AZ::Data::ProductDependency>, AZStd::string>(const AZ::Data::AssetId&));
MOCK_METHOD1(GetHandledAssetTypes, void(AZStd::vector<AZ::Data::AssetType>&));
MOCK_METHOD0(GetRegisteredAssetPaths, AZStd::vector<AZStd::string>());
MOCK_METHOD2(InsertDeltaCatalog, bool(AZStd::shared_ptr<AzFramework::AssetRegistry>, size_t));
MOCK_METHOD2(InsertDeltaCatalogBefore, bool(AZStd::shared_ptr<AzFramework::AssetRegistry>, AZStd::shared_ptr<AzFramework::AssetRegistry>));
MOCK_METHOD1(LoadCatalog, bool(const char*));
MOCK_METHOD2(RegisterAsset, void(const AZ::Data::AssetId&, AZ::Data::AssetInfo&));
MOCK_METHOD1(RemoveDeltaCatalog, bool(AZStd::shared_ptr<AzFramework::AssetRegistry>));
MOCK_METHOD1(SaveCatalog, bool(const char*));
MOCK_METHOD0(StartMonitoringAssets, void());
MOCK_METHOD0(StopMonitoringAssets, void());
MOCK_METHOD1(UnregisterAsset, void(const AZ::Data::AssetId&));
};
class MockAssetManager
: public AZ::Data::AssetManager
{
public:
MockAssetManager(const AZ::Data::AssetManager::Descriptor& desc) :
AssetManager(desc)
{
}
};
class EditorBlastSliceAssetHandlerTestFixture
: public AllocatorsTestFixture
{
public:
AZStd::unique_ptr<MockComponentApplicationBusHandler> m_mockComponentApplicationBusHandler;
//AZStd::unique_ptr<UnitTest::MockComponentApplication> m_mockComponentApplicationBusHandler;
AZStd::unique_ptr<MockAssetCatalogRequestBusHandler> m_mockAssetCatalogRequestBusHandler;
AZStd::unique_ptr<MockAssetManager> m_mockAssetManager;
AZStd::unique_ptr<AZ::SerializeContext> m_serializeContext;
const AZ::ComponentDescriptor* m_sliceComponentDescriptor = nullptr;
void SetUpSliceComponents()
{
m_serializeContext = AZStd::make_unique<AZ::SerializeContext>();
AZ::Entity::Reflect(m_serializeContext.get());
Blast::BlastSliceAssetStorageComponent::Reflect(m_serializeContext.get());
AzToolsFramework::Components::EditorComponentBase::Reflect(m_serializeContext.get());
m_sliceComponentDescriptor = AZ::SliceComponent::CreateDescriptor();
m_sliceComponentDescriptor->Reflect(m_serializeContext.get());
}
void TearDownSliceComponents()
{
delete m_sliceComponentDescriptor;
m_serializeContext.reset();
}
void SetUp() override final
{
AllocatorsTestFixture::SetUp();
AZ::AllocatorInstance<AZ::PoolAllocator>::Create();
AZ::AllocatorInstance<AZ::ThreadPoolAllocator>::Create();
m_mockComponentApplicationBusHandler = AZStd::make_unique<MockComponentApplicationBusHandler>();
m_mockAssetCatalogRequestBusHandler = AZStd::make_unique<MockAssetCatalogRequestBusHandler>();
m_mockAssetManager = AZStd::make_unique<MockAssetManager>(AZ::Data::AssetManager::Descriptor{});
AZ::Data::AssetManager::SetInstance(m_mockAssetManager.get());
}
void TearDown() override final
{
AZ::Data::AssetManager::SetInstance(nullptr);
m_mockAssetManager.reset();
m_mockAssetCatalogRequestBusHandler.reset();
m_mockComponentApplicationBusHandler.reset();
AZ::AllocatorInstance<AZ::ThreadPoolAllocator>::Destroy();
AZ::AllocatorInstance<AZ::PoolAllocator>::Destroy();
AllocatorsTestFixture::TearDown();
}
void SaveSliceAssetToStream(AZ::Entity* sliceAssetEntity, AZStd::vector<char>& buffer)
{
buffer.clear();
AZ::IO::ByteContainerStream<AZStd::vector<char>> stream(&buffer);
AZ::ObjectStream* objStream = AZ::ObjectStream::Create(&stream, *m_serializeContext.get(), AZ::ObjectStream::ST_XML);
objStream->WriteClass(sliceAssetEntity);
EXPECT_TRUE(objStream->Finalize());
}
};
TEST_F(EditorBlastSliceAssetHandlerTestFixture, EditorBlastSliceAssetHandler_AssetManager_Registered)
{
Blast::EditorBlastSliceAssetHandler handler;
handler.Register();
EXPECT_NE(nullptr, AZ::Data::AssetManager::Instance().GetHandler(azrtti_typeid<Blast::BlastSliceAsset>()));
handler.Unregister();
}
TEST_F(EditorBlastSliceAssetHandlerTestFixture, BlastSliceAssetStorageComponent_Behavior_Registered)
{
AZ::BehaviorContext behaviorContext;
Blast::BlastSliceAssetStorageComponent::Reflect(&behaviorContext);
auto classEntry = behaviorContext.m_classes.find("BlastSliceAssetStorageComponent");
EXPECT_NE(behaviorContext.m_classes.end(), classEntry);
AZ::BehaviorClass* behaviorClass = classEntry->second;
auto methodEntry = behaviorClass->m_methods.find("GenerateAssetInfo");
EXPECT_NE(behaviorClass->m_methods.end(), methodEntry);
AZ::BehaviorMethod* behaviorMethod = methodEntry->second;
EXPECT_EQ(4, behaviorMethod->GetNumArguments());
EXPECT_EQ(behaviorMethod->GetArgument(0)->m_typeId, azrtti_typeid<Blast::BlastSliceAssetStorageComponent>());
EXPECT_EQ(behaviorMethod->GetArgument(1)->m_typeId, azrtti_typeid<AZStd::vector<AZStd::string>>());
EXPECT_EQ(behaviorMethod->GetArgument(2)->m_typeId, azrtti_typeid<AZStd::string_view>());
EXPECT_EQ(behaviorMethod->GetArgument(3)->m_typeId, azrtti_typeid<AZStd::string_view>());
}
TEST_F(EditorBlastSliceAssetHandlerTestFixture, BlastSliceAsset_Behavior_Registered)
{
AZ::BehaviorContext behaviorContext;
Blast::BlastSliceAsset::Reflect(&behaviorContext);
auto classEntry = behaviorContext.m_classes.find("BlastSliceAsset");
EXPECT_NE(behaviorContext.m_classes.end(), classEntry);
AZ::BehaviorClass* behaviorClass = classEntry->second;
auto setMeshIdListEntry = behaviorClass->m_methods.find("SetMeshIdList");
EXPECT_NE(behaviorClass->m_methods.end(), setMeshIdListEntry);
{
AZ::BehaviorMethod* behaviorMethod = setMeshIdListEntry->second;
EXPECT_EQ(2, behaviorMethod->GetNumArguments());
EXPECT_EQ(behaviorMethod->GetArgument(0)->m_typeId, azrtti_typeid<Blast::BlastSliceAsset>());
EXPECT_EQ(behaviorMethod->GetArgument(1)->m_typeId, azrtti_typeid<AZStd::vector<AZ::Data::AssetId>>());
}
auto getMeshIdListEntry = behaviorClass->m_methods.find("GetMeshIdList");
EXPECT_NE(behaviorClass->m_methods.end(), getMeshIdListEntry);
{
AZ::BehaviorMethod* behaviorMethod = getMeshIdListEntry->second;
EXPECT_EQ(1, behaviorMethod->GetNumArguments());
EXPECT_EQ(behaviorMethod->GetArgument(0)->m_typeId, azrtti_typeid<Blast::BlastSliceAsset>());
EXPECT_EQ(behaviorMethod->GetResult()->m_typeId, azrtti_typeid<AZStd::vector<AZ::Data::AssetId>>());
}
auto setMaterialIdEntry = behaviorClass->m_methods.find("SetMaterialId");
EXPECT_NE(behaviorClass->m_methods.end(), setMaterialIdEntry);
{
AZ::BehaviorMethod* behaviorMethod = setMaterialIdEntry->second;
EXPECT_EQ(2, behaviorMethod->GetNumArguments());
EXPECT_EQ(behaviorMethod->GetArgument(0)->m_typeId, azrtti_typeid<Blast::BlastSliceAsset>());
EXPECT_EQ(behaviorMethod->GetArgument(1)->m_typeId, azrtti_typeid<AZ::Data::AssetId>());
}
auto getMaterialIdEntry = behaviorClass->m_methods.find("GetMaterialId");
EXPECT_NE(behaviorClass->m_methods.end(), getMaterialIdEntry);
{
AZ::BehaviorMethod* behaviorMethod = getMaterialIdEntry->second;
EXPECT_EQ(1, behaviorMethod->GetNumArguments());
EXPECT_EQ(behaviorMethod->GetArgument(0)->m_typeId, azrtti_typeid<Blast::BlastSliceAsset>());
EXPECT_EQ(behaviorMethod->GetResult()->m_typeId, azrtti_typeid<AZ::Data::AssetId>());
}
auto getAssetTypeIdEntry = behaviorClass->m_methods.find("GetAssetTypeId");
EXPECT_NE(behaviorClass->m_methods.end(), getAssetTypeIdEntry);
{
AZ::BehaviorMethod* behaviorMethod = getAssetTypeIdEntry->second;
EXPECT_EQ(1, behaviorMethod->GetNumArguments());
EXPECT_EQ(behaviorMethod->GetArgument(0)->m_typeId, azrtti_typeid<Blast::BlastSliceAsset>());
EXPECT_EQ(behaviorMethod->GetResult()->m_typeId, azrtti_typeid<AZ::TypeId>());
}
}
TEST_F(EditorBlastSliceAssetHandlerTestFixture, EditorBlastSliceAssetHandler_AssetTypeInfoBus_Responds)
{
auto assetId = azrtti_typeid<Blast::BlastSliceAsset>();
Blast::EditorBlastSliceAssetHandler handler;
handler.Register();
AZ::Data::AssetType assetType = AZ::Uuid::CreateNull();
AZ::AssetTypeInfoBus::EventResult(assetType, assetId, &AZ::AssetTypeInfoBus::Events::GetAssetType);
EXPECT_NE(AZ::Uuid::CreateNull(), assetType);
const char* displayName = nullptr;
AZ::AssetTypeInfoBus::EventResult(displayName, assetId, &AZ::AssetTypeInfoBus::Events::GetAssetTypeDisplayName);
EXPECT_STREQ("Blast Slice Asset", displayName);
const char* group = nullptr;
AZ::AssetTypeInfoBus::EventResult(group, assetId, &AZ::AssetTypeInfoBus::Events::GetGroup);
EXPECT_STREQ("Blast", group);
const char* icon = nullptr;
AZ::AssetTypeInfoBus::EventResult(icon, assetId, &AZ::AssetTypeInfoBus::Events::GetBrowserIcon);
EXPECT_STREQ("Editor/Icons/Components/Box.png", icon);
AZStd::vector<AZStd::string> extensions;
AZ::AssetTypeInfoBus::Event(assetId, &AZ::AssetTypeInfoBus::Events::GetAssetTypeExtensions, extensions);
ASSERT_EQ(1, extensions.size());
ASSERT_EQ("blast_slice", extensions[0]);
handler.Unregister();
}
TEST_F(EditorBlastSliceAssetHandlerTestFixture, EditorBlastSliceAssetHandler_AssetHandler_Ready)
{
auto assetType = azrtti_typeid<Blast::BlastSliceAsset>();
auto&& assetManager = AZ::Data::AssetManager::Instance();
Blast::EditorBlastSliceAssetHandler handler;
handler.Register();
EXPECT_EQ(&handler, assetManager.GetHandler(assetType));
// create and release an instance of the BlastSliceAsset asset type
{
using ::testing::Return;
using ::testing::_;
EXPECT_CALL(*m_mockAssetCatalogRequestBusHandler, GetAssetInfoById(_))
.Times(1)
.WillRepeatedly(Return(AZ::Data::AssetInfo{}));
auto assetPtr = assetManager.CreateAsset<Blast::BlastSliceAsset>(AZ::Data::AssetId(AZ::Uuid::CreateRandom(), 0));
EXPECT_NE(nullptr, assetPtr.Get());
EXPECT_EQ(azrtti_typeid<Blast::BlastSliceAsset>(), assetPtr.GetType());
}
handler.Unregister();
}
TEST_F(EditorBlastSliceAssetHandlerTestFixture, EditorBlastSliceAssetHandler_AssetHandler_LoadsAssetData)
{
SetUpSliceComponents();
AZStd::vector<AZStd::string> meshAssetPathList = { "/foo/path/thing.cgf", "/foo/path/that.cgf" };
AZ::Entity* storageEntity = aznew AZ::Entity();
auto* blastStorage = storageEntity->CreateComponent<Blast::BlastSliceAssetStorageComponent>();
blastStorage->SetMeshPathList(meshAssetPathList);
AZ::Entity sliceEntity;
AZ::SliceComponent* slice = sliceEntity.CreateComponent<AZ::SliceComponent>();
slice->AddEntity(storageEntity);
AZStd::vector<char> buffer;
SaveSliceAssetToStream(&sliceEntity, buffer);
// Load a slice with the BlastSliceAssetStorageComponent
Blast::EditorBlastSliceAssetHandler handler;
handler.Register();
{
using ::testing::Return;
using ::testing::_;
EXPECT_CALL(*m_mockComponentApplicationBusHandler, GetSerializeContext)
.Times(1)
.WillOnce(Return(m_serializeContext.get()));
EXPECT_CALL(*m_mockComponentApplicationBusHandler, FindEntity(_))
.Times(1)
.WillOnce(Return(&sliceEntity));
EXPECT_CALL(*m_mockAssetCatalogRequestBusHandler, GetAssetIdByPath(_,_,_))
.Times(2)
.WillRepeatedly(Return(AZ::Data::AssetId(AZ::Uuid::CreateRandom(), 0)));
EXPECT_CALL(*m_mockAssetCatalogRequestBusHandler, GetAssetInfoById(_))
.Times(2)
.WillRepeatedly(Return(AZ::Data::AssetInfo{}));
auto&& assetManager = AZ::Data::AssetManager::Instance();
auto assetPtr = assetManager.CreateAsset<Blast::BlastSliceAsset>(AZ::Data::AssetId(AZ::Uuid::CreateRandom(), 0));
AZ::IO::ByteContainerStream<AZStd::vector<char>> stream(&buffer);
stream.Seek(0, AZ::IO::GenericStream::ST_SEEK_BEGIN);
const AZ::Data::AssetFilterCB assetLoadFilterCB{};
bool loaded = handler.LoadAssetData(assetPtr, &stream, assetLoadFilterCB);
EXPECT_TRUE(loaded);
}
handler.Unregister();
TearDownSliceComponents();
}
}
+2 -2
View File
@@ -11,8 +11,8 @@ set(FILES
Source/Editor/EditorBlastFamilyComponent.cpp
Source/Editor/EditorBlastMeshDataComponent.cpp
Source/Editor/EditorBlastMeshDataComponent.h
Source/Editor/EditorBlastSliceAssetHandler.h
Source/Editor/EditorBlastSliceAssetHandler.cpp
Source/Editor/EditorBlastChunksAssetHandler.h
Source/Editor/EditorBlastChunksAssetHandler.cpp
Source/Editor/EditorSystemComponent.h
Source/Editor/EditorSystemComponent.cpp
Editor/ConfigurationWidget.h
@@ -7,6 +7,6 @@
#
set(FILES
# Tests/Editor/EditorBlastSliceAssetHandlerTest.cpp
Tests/Editor/EditorBlastChunksAssetHandlerTest.cpp
Tests/Editor/EditorTestMain.cpp
)
+2 -2
View File
@@ -26,8 +26,8 @@ set(FILES
Source/Asset/BlastAsset.cpp
Source/Asset/BlastAssetHandler.h
Source/Asset/BlastAssetHandler.cpp
Source/Asset/BlastSliceAsset.h
Source/Asset/BlastSliceAsset.cpp
Source/Asset/BlastChunksAsset.h
Source/Asset/BlastChunksAsset.cpp
Source/Components/BlastFamilyComponent.h
Source/Components/BlastFamilyComponent.cpp
Source/Components/BlastFamilyComponentNotificationBusHandler.h
@@ -1,323 +0,0 @@
"""
Copyright (c) Contributors to the Open 3D Engine Project.
For complete copyright and license terms please see the LICENSE at the root of this distribution.
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
def install_user_site():
import os
import sys
import azlmbr.paths
executableBinFolder = azlmbr.paths.executableFolder
# the PyAssImp module checks the Windows PATH for the assimp DLL file
if os.name == "nt":
os.environ['PATH'] = os.environ['PATH'] + os.pathsep + executableBinFolder
# PyAssImp module needs to find the shared library for assimp to load; "posix" handles Mac and Linux
if os.name == "posix":
if 'LD_LIBRARY_PATH' in os.environ:
os.environ['LD_LIBRARY_PATH'] = os.environ['LD_LIBRARY_PATH'] + os.pathsep + executableBinFolder
else:
os.environ['LD_LIBRARY_PATH'] = executableBinFolder
# add the user site packages folder to find the pyassimp egg link
import site
for item in sys.path:
if (item.find('site-packages') != -1):
site.addsitedir(item)
install_user_site()
import pyassimp
import azlmbr.asset
import azlmbr.asset.builder
import azlmbr.asset.entity
import azlmbr.blast
import azlmbr.bus as bus
import azlmbr.editor as editor
import azlmbr.entity
import azlmbr.math
import os
import traceback
import binascii
import sys
# the UUID must be unique amongst all the asset builders in Python or otherwise
# a collision of builders will happen preventing one from running
busIdString = '{CF5C74D1-9ED4-4851-85B1-9B15090DBEC7}'
busId = azlmbr.math.Uuid_CreateString(busIdString, 0)
handler = None
jobKeyName = 'Blast Chunk Assets'
sceneManifestType = azlmbr.math.Uuid_CreateString('{9274AD17-3212-4651-9F3B-7DCCB080E467}', 0)
dccMaterialType = azlmbr.math.Uuid_CreateString('{C88469CF-21E7-41EB-96FD-BF14FBB05EDC}', 0)
def log_exception_traceback():
exc_type, exc_value, exc_tb = sys.exc_info()
data = traceback.format_exception(exc_type, exc_value, exc_tb)
print(str(data))
def get_source_fbx_filename(request):
fullPath = os.path.join(request.watchFolder, request.sourceFile)
basePath, filePart = os.path.split(fullPath)
filename = os.path.splitext(filePart)[0] + '.fbx'
filename = os.path.join(basePath, filename)
return filename
def raise_error(message):
raise RuntimeError(f'[ERROR]: {message}')
def generate_asset_info(chunkNames, request):
import azlmbr.blast
# write out an object stream with the extension of .fbx.assetinfo.generated
basePath, sceneFile = os.path.split(request.sourceFile)
assetinfoFilename = os.path.splitext(sceneFile)[0] + '.fbx.assetinfo.generated'
assetinfoFilename = os.path.join(basePath, assetinfoFilename)
assetinfoFilename = assetinfoFilename.replace('\\', '/').lower()
outputFilename = os.path.join(request.tempDirPath, assetinfoFilename)
storage = azlmbr.blast.BlastSliceAssetStorageComponent()
if (storage.GenerateAssetInfo(chunkNames, request.sourceFile, outputFilename)):
product = azlmbr.asset.builder.JobProduct(assetinfoFilename, sceneManifestType, 1)
product.dependenciesHandled = True
return product
raise_error('Failed to generate assetinfo.generated')
def export_fbx_manifest(request):
output = []
fbxFilename = get_source_fbx_filename(request)
sceneAsset = pyassimp.load(fbxFilename)
with sceneAsset as scene:
rootNode = scene.mRootNode.contents
for index in range(0, rootNode.mNumChildren):
child = rootNode.mChildren[index]
childNode = child.contents
childNodeName = bytes.decode(childNode.mName.data)
output.append(str(childNodeName))
return output
def convert_to_asset_paths(fbxFilename, gameRoot, chunkNameList):
realtivePath = fbxFilename[len(gameRoot) + 1:]
realtivePath = os.path.splitext(realtivePath)[0]
output = []
for chunk in chunkNameList:
assetPath = realtivePath + '-' + chunk + '.cgf'
assetPath = assetPath.replace('\\', '/')
assetPath = assetPath.lower()
output.append(assetPath)
return output
# creates a single job to compile for each platform
def create_jobs(request):
fbxSidecarFilename = get_source_fbx_filename(request)
if (os.path.exists(fbxSidecarFilename) is False):
print('[WARN] Sidecar FBX file {} is missing for blast file {}'.format(fbxSidecarFilename, request.sourceFile))
return azlmbr.asset.builder.CreateJobsResponse()
# see if the FBX file already has a .assetinfo source asset, if so then do not create a job
if (os.path.exists(f'{fbxSidecarFilename}.assetinfo')):
response = azlmbr.asset.builder.CreateJobsResponse()
response.result = azlmbr.asset.builder.CreateJobsResponse_ResultSuccess
return response
# create job descriptor for each platform
jobDescriptorList = []
for platformInfo in request.enabledPlatforms:
jobDesc = azlmbr.asset.builder.JobDescriptor()
jobDesc.jobKey = jobKeyName
jobDesc.priority = 12 # higher than the 'Scene compilation' or 'fbx'
jobDesc.set_platform_identifier(platformInfo.identifier)
jobDescriptorList.append(jobDesc)
response = azlmbr.asset.builder.CreateJobsResponse()
response.result = azlmbr.asset.builder.CreateJobsResponse_ResultSuccess
response.createJobOutputs = jobDescriptorList
return response
# handler to create jobs for a source asset
def on_create_jobs(args):
try:
request = args[0]
return create_jobs(request)
except:
log_exception_traceback()
return azlmbr.asset.builder.CreateJobsResponse()
def generate_blast_slice_asset(chunkNameList, request):
# get list of relative chunk paths
fbxFilename = get_source_fbx_filename(request)
assetPaths = convert_to_asset_paths(fbxFilename, request.watchFolder, chunkNameList)
outcome = azlmbr.asset.entity.PythonBuilderRequestBus(bus.Broadcast, 'CreateEditorEntity', 'BlastData')
if (outcome.IsSuccess() is False):
raise_error('could not create an editor entity')
blastDataEntityId = outcome.GetValue()
# create a component for the editor entity
gameType = azlmbr.entity.EntityType().Game
blastMeshDataTypeIdList = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["Blast Slice Storage Component"], gameType)
componentOutcome = editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentOfType', blastDataEntityId, blastMeshDataTypeIdList[0])
if (componentOutcome.IsSuccess() is False):
raise_error('failed to add component (Blast Slice Storage Component) to the blast_slice')
# build the blast slice using the chunk asset paths
blastMeshComponentId = componentOutcome.GetValue()[0]
outcome = editor.EditorComponentAPIBus(bus.Broadcast, 'BuildComponentPropertyTreeEditor', blastMeshComponentId)
if(outcome.IsSuccess() is False):
raise_error(f'failed to create Property Tree Editor for component ({blastMeshComponentId})')
pte = outcome.GetValue()
pte.set_visible_enforcement(True)
pte.set_value('Mesh Paths', assetPaths)
# write out an object stream with the extension of .blast_slice
basePath, sceneFile = os.path.split(request.sourceFile)
blastFilename = os.path.splitext(sceneFile)[0] + '.blast_slice'
blastFilename = os.path.join(basePath, blastFilename)
blastFilename = blastFilename.replace('\\', '/').lower()
tempFilename = os.path.join(request.tempDirPath, blastFilename)
entityList = [blastDataEntityId]
makeDynamic = False
outcome = azlmbr.asset.entity.PythonBuilderRequestBus(bus.Broadcast, 'WriteSliceFile', tempFilename, entityList, makeDynamic)
if (outcome.IsSuccess() is False):
raise_error(f'WriteSliceFile failed for blast_slice file ({blastFilename})')
# return a job product
blastSliceAsset = azlmbr.blast.BlastSliceAsset()
subId = binascii.crc32(blastFilename.encode('utf8'))
product = azlmbr.asset.builder.JobProduct(blastFilename, blastSliceAsset.GetAssetTypeId(), subId)
product.dependenciesHandled = True
return product
def read_in_string(data, dataLength):
stringData = ''
for idx in range(4, dataLength - 1):
char = bytes.decode(data[idx])
if (str.isascii(char)):
stringData += char
return stringData
def import_material_info(fbxFilename):
_, group_name = os.path.split(fbxFilename)
group_name = os.path.splitext(group_name)[0]
output = {}
output['group_name'] = group_name
output['material_name_list'] = []
sceneAsset = pyassimp.load(fbxFilename)
with sceneAsset as scene:
for materialIndex in range(0, scene.mNumMaterials):
material = scene.mMaterials[materialIndex].contents
for materialPropertyIdx in range(0, material.mNumProperties):
materialProperty = material.mProperties[materialPropertyIdx].contents
materialPropertyName = bytes.decode(materialProperty.mKey.data)
if (materialPropertyName.endswith('mat.name') and materialProperty.mType is 3):
stringData = read_in_string(materialProperty.mData, materialProperty.mDataLength)
output['material_name_list'].append(stringData)
return output
def write_material_file(sourceFile, destFolder):
# preserve source MTL files
rootPath, materialSourceFile = os.path.split(sourceFile)
materialSourceFile = os.path.splitext(materialSourceFile)[0] + '.mtl'
materialSourceFile = os.path.join(rootPath, materialSourceFile)
if (os.path.exists(materialSourceFile)):
print(f'{materialSourceFile} source already exists')
return None
# auto-generate a DCC material file
info = import_material_info(sourceFile)
materialGroupName = info['group_name']
materialNames = info['material_name_list']
materialFilename = materialGroupName + '.dccmtl.generated'
subId = binascii.crc32(materialFilename.encode('utf8'))
materialFilename = os.path.join(destFolder, materialFilename)
storage = azlmbr.blast.BlastSliceAssetStorageComponent()
storage.WriteMaterialFile(materialGroupName, materialNames, materialFilename)
product = azlmbr.asset.builder.JobProduct(materialFilename, dccMaterialType, subId)
product.dependenciesHandled = True
return product
def process_fbx_file(request):
# fill out response object
response = azlmbr.asset.builder.ProcessJobResponse()
productOutputs = []
# write out DCCMTL file as a product (if needed)
materialProduct = write_material_file(get_source_fbx_filename(request), request.tempDirPath)
if (materialProduct is not None):
productOutputs.append(materialProduct)
# prepare output folder
basePath, _ = os.path.split(request.sourceFile)
outputPath = os.path.join(request.tempDirPath, basePath)
os.makedirs(outputPath)
# parse FBX for chunk names
chunkNameList = export_fbx_manifest(request)
# create assetinfo generated (is product)
productOutputs.append(generate_asset_info(chunkNameList, request))
# write out the blast_slice object stream
productOutputs.append(generate_blast_slice_asset(chunkNameList, request))
response.outputProducts = productOutputs
response.resultCode = azlmbr.asset.builder.ProcessJobResponse_Success
response.dependenciesHandled = True
return response
# using the incoming 'request' find the type of job via 'jobKey' to determine what to do
def on_process_job(args):
try:
request = args[0]
if (request.jobDescription.jobKey.startswith(jobKeyName)):
return process_fbx_file(request)
return azlmbr.asset.builder.ProcessJobResponse()
except:
log_exception_traceback()
return azlmbr.asset.builder.ProcessJobResponse()
# register asset builder
def register_asset_builder():
assetPattern = azlmbr.asset.builder.AssetBuilderPattern()
assetPattern.pattern = '*.blast'
assetPattern.type = azlmbr.asset.builder.AssetBuilderPattern_Wildcard
builderDescriptor = azlmbr.asset.builder.AssetBuilderDesc()
builderDescriptor.name = "Blast Gem"
builderDescriptor.patterns = [assetPattern]
builderDescriptor.busId = busId
builderDescriptor.version = 5
outcome = azlmbr.asset.builder.PythonAssetBuilderRequestBus(azlmbr.bus.Broadcast, 'RegisterAssetBuilder', builderDescriptor)
if outcome.IsSuccess():
# created the asset builder to hook into the notification bus
handler = azlmbr.asset.builder.PythonBuilderNotificationBusHandler()
handler.connect(busId)
handler.add_callback('OnCreateJobsRequest', on_create_jobs)
handler.add_callback('OnProcessJobRequest', on_process_job)
return handler
# create the asset builder handler
try:
handler = register_asset_builder()
except:
handler = None
log_exception_traceback()
@@ -0,0 +1,290 @@
"""
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
"""
"""
This a Python Asset Builder script examines each .blast file to see if an
associated .fbx file needs to be processed by exporting all of its chunks
into a scene manifest
This is also a SceneAPI script that executes from a foo.fbx.assetinfo scene
manifest that writes out asset chunk data for .blast files
"""
import os, traceback, binascii, sys, json, pathlib
import azlmbr.math
import azlmbr.asset
import azlmbr.asset.entity
import azlmbr.asset.builder
import azlmbr.bus
#
# Python Asset Builder
#
busId = azlmbr.math.Uuid_CreateString('{D4FA20E3-8EF4-44A3-A045-AAE6C1CCAAAB}', 0)
jobKeyName = 'Blast Chunk Assets'
def log_exception_traceback():
exc_type, exc_value, exc_tb = sys.exc_info()
data = traceback.format_exception(exc_type, exc_value, exc_tb)
print(str(data))
def raise_error(message):
print (f'ERROR - {message}');
raise RuntimeError(f'[ERROR]: {message}');
# creates a single job to compile for each platform
def get_source_fbx_filename(request):
fullPath = os.path.join(request.watchFolder, request.sourceFile)
basePath, filePart = os.path.split(fullPath)
filename = os.path.splitext(filePart)[0] + '.fbx'
filename = os.path.join(basePath, filename)
return filename
def create_jobs(request):
fbxSidecarFilename = get_source_fbx_filename(request)
if (os.path.exists(fbxSidecarFilename) is False):
print('[WARN] Sidecar FBX file {} is missing for blast file {}'.format(fbxSidecarFilename, request.sourceFile))
return azlmbr.asset.builder.CreateJobsResponse()
# see if the FBX file already has a .assetinfo source asset, if so then do not create a job
establishedAssetInfo = f'{fbxSidecarFilename}.assetinfo';
if (os.path.exists(establishedAssetInfo)):
response = azlmbr.asset.builder.CreateJobsResponse()
response.result = azlmbr.asset.builder.CreateJobsResponse_ResultSuccess
return response
# create job descriptor for each platform
jobDescriptorList = []
for platformInfo in request.enabledPlatforms:
sourceFileDependency = azlmbr.asset.builder.SourceFileDependency()
sourceFileDependency.sourceFileDependencyPath = fbxSidecarFilename
jobDependency = azlmbr.asset.builder.JobDependency()
jobDependency.sourceFile = sourceFileDependency
jobDependency.jobKey = jobKeyName
jobDependency.platformIdentifier = platformInfo.identifier
jobDesc = azlmbr.asset.builder.JobDescriptor()
jobDesc.jobKey = jobKeyName
jobDesc.set_platform_identifier(platformInfo.identifier)
jobDesc.jobDependencyList = [jobDependency]
jobDescriptorList.append(jobDesc)
response = azlmbr.asset.builder.CreateJobsResponse()
response.result = azlmbr.asset.builder.CreateJobsResponse_ResultSuccess
response.createJobOutputs = jobDescriptorList
return response
# to create jobs for a source asset
def on_create_jobs(args):
try:
request = args[0]
return create_jobs(request)
except:
log_exception_traceback()
return azlmbr.asset.builder.CreateJobsResponse()
def generate_assetinfo_product(request):
# write out a product asset file with the extension of .fbx.assetinfo.generated
basePath, sceneFile = os.path.split(request.sourceFile)
assetinfoFilename = os.path.splitext(sceneFile)[0] + '.fbx.assetinfo.generated'
assetinfoFilename = os.path.join(basePath, assetinfoFilename)
assetinfoFilename = assetinfoFilename.replace('\\', '/').lower()
outputFilename = os.path.join(request.tempDirPath, assetinfoFilename)
# the only rule in it is to run this file again as a scene processor
currentScript = pathlib.Path(__file__).resolve()
aDict = {"values": [{"$type": "ScriptProcessorRule", "scriptFilename": f"{currentScript}"}]}
jsonString = json.dumps(aDict)
jsonFile = open(outputFilename, "w")
jsonFile.write(jsonString)
jsonFile.close()
# return a job product for the generated assetinfo file
sceneManifestType = azlmbr.math.Uuid_CreateString('{9274AD17-3212-4651-9F3B-7DCCB080E467}', 0)
subId = 1
product = azlmbr.asset.builder.JobProduct(outputFilename, sceneManifestType, subId)
product.dependenciesHandled = True
return product
def process_fbx_file(request):
# fill out response object
response = azlmbr.asset.builder.ProcessJobResponse()
productOutputs = []
# prepare output folder
basePath, _ = os.path.split(request.sourceFile)
outputPath = os.path.join(request.tempDirPath, basePath)
os.makedirs(outputPath)
# create assetinfo generated file
productOutputs.append(generate_assetinfo_product(request))
response.outputProducts = productOutputs
response.resultCode = azlmbr.asset.builder.ProcessJobResponse_Success
response.dependenciesHandled = True
return response
# using the incoming 'request' find the type of job via 'jobKey' to determine what to do
def on_process_job(args):
try:
request = args[0]
if (request.jobDescription.jobKey.startswith(jobKeyName)):
return process_fbx_file(request)
return azlmbr.asset.builder.ProcessJobResponse()
except:
log_exception_traceback()
return azlmbr.asset.builder.ProcessJobResponse()
# register asset builder
def register_asset_builder():
assetPattern = azlmbr.asset.builder.AssetBuilderPattern()
assetPattern.pattern = '*.blast'
assetPattern.type = azlmbr.asset.builder.AssetBuilderPattern_Wildcard
builderDescriptor = azlmbr.asset.builder.AssetBuilderDesc()
builderDescriptor.name = "Blast Scene Builder"
builderDescriptor.patterns = [assetPattern]
builderDescriptor.busId = busId
builderDescriptor.version = 1
outcome = azlmbr.asset.builder.PythonAssetBuilderRequestBus(azlmbr.bus.Broadcast, 'RegisterAssetBuilder', builderDescriptor)
if outcome.IsSuccess():
# created the asset builder to hook into the notification bus
handler = azlmbr.asset.builder.PythonBuilderNotificationBusHandler()
handler.connect(busId)
handler.add_callback('OnCreateJobsRequest', on_create_jobs)
handler.add_callback('OnProcessJobRequest', on_process_job)
return handler
# create the asset builder handler
pythonAssetBuilderHandler = None
try:
if (pythonAssetBuilderHandler == None):
pythonAssetBuilderHandler = register_asset_builder()
except:
pythonAssetBuilderHandler = None
#
# SceneAPI Processor
#
blastChunksAssetType = azlmbr.math.Uuid_CreateString('{993F0B0F-37D9-48C6-9CC2-E27D3F3E343E}', 0)
def export_chunk_asset(scene, outputDirectory, platformIdentifier, productList):
import azlmbr.scene
import azlmbr.object
import azlmbr.paths
import json, os
jsonFilename = os.path.basename(scene.sourceFilename)
jsonFilename = os.path.join(outputDirectory, jsonFilename + '.blast_chunks')
# prepare output folder
basePath, _ = os.path.split(jsonFilename)
outputPath = os.path.join(outputDirectory, basePath)
if not os.path.exists(outputPath):
os.makedirs(outputPath, False)
# write out a JSON file with the chunk file info
with open(jsonFilename, "w") as jsonFile:
jsonFile.write(scene.manifest.ExportToJson())
exportProduct = azlmbr.scene.ExportProduct()
exportProduct.filename = jsonFilename
exportProduct.sourceId = scene.sourceGuid
exportProduct.assetType = blastChunksAssetType
exportProduct.subId = 101
exportProductList = azlmbr.scene.ExportProductList()
exportProductList.AddProduct(exportProduct)
return exportProductList
def on_prepare_for_export(args):
try:
scene = args[0] # azlmbr.scene.Scene
outputDirectory = args[1] # string
platformIdentifier = args[2] # string
productList = args[3] # azlmbr.scene.ExportProductList
return export_chunk_asset(scene, outputDirectory, platformIdentifier, productList)
except:
log_exception_traceback()
def get_mesh_node_names(sceneGraph):
import azlmbr.scene as sceneApi
import azlmbr.scene.graph
from scene_api import scene_data as sceneData
meshDataList = []
node = sceneGraph.get_root()
children = []
while node.IsValid():
# store children to process after siblings
if sceneGraph.has_node_child(node):
children.append(sceneGraph.get_node_child(node))
# store any node that has mesh data content
nodeContent = sceneGraph.get_node_content(node)
if nodeContent is not None and nodeContent.CastWithTypeName('MeshData'):
if sceneGraph.is_node_end_point(node) is False:
nodeName = sceneData.SceneGraphName(sceneGraph.get_node_name(node))
nodePath = nodeName.get_path()
if (len(nodeName.get_path())):
meshDataList.append(sceneData.SceneGraphName(sceneGraph.get_node_name(node)))
# advance to next node
if sceneGraph.has_node_sibling(node):
node = sceneGraph.get_node_sibling(node)
elif children:
node = children.pop()
else:
node = azlmbr.scene.graph.NodeIndex()
return meshDataList
def update_manifest(scene):
import uuid, os
import azlmbr.scene as sceneApi
import azlmbr.scene.graph
from scene_api import scene_data as sceneData
graph = sceneData.SceneGraph(scene.graph)
meshNameList = get_mesh_node_names(graph)
sceneManifest = sceneData.SceneManifest()
sourceFilenameOnly = os.path.basename(scene.sourceFilename)
sourceFilenameOnly = sourceFilenameOnly.replace('.','_')
for activeMeshIndex in range(len(meshNameList)):
chunkName = meshNameList[activeMeshIndex]
chunkPath = chunkName.get_path()
meshGroupName = '{}_{}'.format(sourceFilenameOnly, chunkName.get_name())
meshGroup = sceneManifest.add_mesh_group(meshGroupName)
meshGroup['id'] = '{' + str(uuid.uuid5(uuid.NAMESPACE_DNS, sourceFilenameOnly + chunkPath)) + '}'
sceneManifest.mesh_group_select_node(meshGroup, chunkPath)
return sceneManifest.export()
sceneJobHandler = None
def on_update_manifest(args):
try:
scene = args[0]
return update_manifest(scene)
except:
global sceneJobHandler
sceneJobHandler = None
log_exception_traceback()
# try to create SceneAPI handler for processing
try:
import azlmbr.scene as sceneApi
if (sceneJobHandler == None):
sceneJobHandler = sceneApi.ScriptBuildingNotificationBusHandler()
sceneJobHandler.connect()
sceneJobHandler.add_callback('OnUpdateManifest', on_update_manifest)
sceneJobHandler.add_callback('OnPrepareForExport', on_prepare_for_export)
except:
sceneJobHandler = None
+10 -3
View File
@@ -4,6 +4,13 @@ For complete copyright and license terms please see the LICENSE at the root of t
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
# LYN-652 to re-enable once the Blast gem tests are stable
# import asset_builder_blast
try:
import azlmbr.asset
import azlmbr.asset.entity
import azlmbr.asset.builder
import blast_asset_builder
except:
# this script only runs in an asset processing environment
# like the AssetProcessor or an AssetBuilder
# plus the Blast gem needs to be enabled for the project
pass
@@ -11,6 +11,7 @@
#include "CommandManager.h"
#include <EMotionFX/CommandSystem/Source/AnimGraphTriggerActionCommands.h>
#include <EMotionFX/CommandSystem/Source/AnimGraphNodeGroupCommands.h>
#include <EMotionFX/Source/ActorInstance.h>
#include <EMotionFX/Source/ActorManager.h>
#include <EMotionFX/Source/AnimGraph.h>
@@ -858,8 +859,16 @@ namespace CommandSystem
// add it to the old node group if it was assigned to one before
if (!mNodeGroupName.empty())
{
commandString = AZStd::string::format("AnimGraphAdjustNodeGroup -animGraphID %i -name \"%s\" -nodeNames \"%s\" -nodeAction \"add\"", animGraph->GetID(), mNodeGroupName.c_str(), mName.c_str());
if (GetCommandManager()->ExecuteCommandInsideCommand(commandString.c_str(), outResult) == false)
auto* command = aznew CommandSystem::CommandAnimGraphAdjustNodeGroup(
GetCommandManager()->FindCommand(CommandSystem::CommandAnimGraphAdjustNodeGroup::s_commandName),
/*animGraphId = */ animGraph->GetID(),
/*name = */ mNodeGroupName,
/*visible = */ AZStd::nullopt,
/*newName = */ AZStd::nullopt,
/*nodeNames = */ {{mName}},
/*nodeAction = */ CommandSystem::CommandAnimGraphAdjustNodeGroup::NodeAction::Add
);
if (GetCommandManager()->ExecuteCommandInsideCommand(command, outResult) == false)
{
if (outResult.size() > 0)
{
@@ -1363,11 +1372,16 @@ namespace CommandSystem
EMotionFX::AnimGraphNodeGroup* nodeGroup = node->GetAnimGraph()->FindNodeGroupForNode(node);
if (nodeGroup && !cutMode)
{
commandString = AZStd::string::format("AnimGraphAdjustNodeGroup -animGraphID %d -name \"%s\" -nodeNames \"%s\" -nodeAction \"add\"",
targetAnimGraph->GetID(),
nodeGroup->GetName(),
nodeName.c_str());
commandGroup->AddCommandString(commandString);
auto* command = aznew CommandSystem::CommandAnimGraphAdjustNodeGroup(
GetCommandManager()->FindCommand(CommandSystem::CommandAnimGraphAdjustNodeGroup::s_commandName),
/*animGraphId = */ targetAnimGraph->GetID(),
/*name = */ nodeGroup->GetNameString(),
/*visible = */ AZStd::nullopt,
/*newName = */ AZStd::nullopt,
/*nodeNames = */ {{nodeName}},
/*nodeAction = */ CommandSystem::CommandAnimGraphAdjustNodeGroup::NodeAction::Add
);
commandGroup->AddCommand(command);
}
// Recurse through the child nodes.
@@ -6,6 +6,7 @@
*
*/
#include <AzCore/std/optional.h>
#include <AzFramework/StringFunc/StringFunc.h>
#include "AnimGraphNodeGroupCommands.h"
#include "AnimGraphConnectionCommands.h"
@@ -22,45 +23,46 @@
namespace CommandSystem
{
AZ_CLASS_ALLOCATOR_IMPL(CommandAnimGraphAdjustNodeGroup, EMotionFX::CommandAllocator, 0)
//--------------------------------------------------------------------------------
// CommandAnimGraphAdjustNodeGroup
//--------------------------------------------------------------------------------
CommandAnimGraphAdjustNodeGroup::CommandAnimGraphAdjustNodeGroup(MCore::Command* orgCommand)
: MCore::Command("AnimGraphAdjustNodeGroup", orgCommand)
CommandAnimGraphAdjustNodeGroup::CommandAnimGraphAdjustNodeGroup(
MCore::Command* orgCommand,
AZ::u32 animGraphId,
AZStd::string name,
AZStd::optional<bool> visible,
AZStd::optional<AZStd::string> newName,
AZStd::optional<AZStd::vector<AZStd::string>> nodeNames,
AZStd::optional<NodeAction> nodeAction,
AZStd::optional<AZ::u32> color,
AZStd::optional<bool> updateUI
)
: MCore::Command(s_commandName, orgCommand)
, ParameterMixinAnimGraphId(animGraphId)
, m_name(AZStd::move(name))
, m_isVisible(visible)
, m_newName(AZStd::move(newName))
, m_nodeNames(AZStd::move(nodeNames))
, m_nodeAction(nodeAction)
, m_color(color)
, m_updateUI(updateUI)
{
}
CommandAnimGraphAdjustNodeGroup::~CommandAnimGraphAdjustNodeGroup()
AZStd::vector<AZStd::string> CommandAnimGraphAdjustNodeGroup::GenerateNodeNameVector(EMotionFX::AnimGraph* animGraph, const AZStd::vector<EMotionFX::AnimGraphNodeId>& nodeIDs)
{
}
AZStd::string CommandAnimGraphAdjustNodeGroup::GenerateNodeNameString(EMotionFX::AnimGraph* animGraph, const AZStd::vector<EMotionFX::AnimGraphNodeId>& nodeIDs)
{
if (nodeIDs.empty())
AZStd::vector<AZStd::string> result;
for (const auto& nodeID : nodeIDs)
{
return "";
}
AZStd::string result;
const size_t numNodes = nodeIDs.size();
for (size_t i = 0; i < numNodes; ++i)
{
EMotionFX::AnimGraphNode* animGraphNode = animGraph->RecursiveFindNodeById(nodeIDs[i]);
const EMotionFX::AnimGraphNode* animGraphNode = animGraph->RecursiveFindNodeById(nodeID);
if (!animGraphNode)
{
continue;
}
result += animGraphNode->GetName();
if (i < numNodes - 1)
{
result += ';';
}
result.emplace_back(animGraphNode->GetName());
}
return result;
}
@@ -80,78 +82,51 @@ namespace CommandSystem
}
bool CommandAnimGraphAdjustNodeGroup::Execute(const MCore::CommandLine& parameters, AZStd::string& outResult)
bool CommandAnimGraphAdjustNodeGroup::Execute(const MCore::CommandLine&, AZStd::string& outResult)
{
EMotionFX::AnimGraph* animGraph = CommandsGetAnimGraph(parameters, this, outResult);
EMotionFX::AnimGraph* animGraph = EMotionFX::GetAnimGraphManager().FindAnimGraphByID(m_animGraphId);
if (!animGraph)
{
return false;
}
// get the node group name
AZStd::string groupName;
parameters.GetValue("name", this, groupName);
// find the node group index
const uint32 groupIndex = animGraph->FindNodeGroupIndexByName(groupName.c_str());
const uint32 groupIndex = animGraph->FindNodeGroupIndexByName(m_name.c_str());
if (groupIndex == MCORE_INVALIDINDEX32)
{
outResult = AZStd::string::format("Node group \"%s\" can not be found.", groupName.c_str());
outResult = AZStd::string::format("Node group \"%s\" can not be found.", m_name.c_str());
return false;
}
// get a pointer to the node group and keep the old name
EMotionFX::AnimGraphNodeGroup* nodeGroup = animGraph->GetNodeGroup(groupIndex);
mOldName = nodeGroup->GetName();
// is visible?
if (parameters.CheckIfHasParameter("isVisible"))
if (m_isVisible.has_value())
{
const bool isVisible = parameters.GetValueAsBool("isVisible", this);
mOldIsVisible = nodeGroup->GetIsVisible();
nodeGroup->SetIsVisible(isVisible);
m_oldIsVisible = nodeGroup->GetIsVisible();
nodeGroup->SetIsVisible(*m_isVisible);
}
// background color
if (parameters.CheckIfHasParameter("color"))
if (m_color.has_value())
{
const AZ::Vector4 colorVector4 = parameters.GetValueAsVector4("color", this);
const AZ::u32 color = AZ::Color(static_cast<float>(colorVector4.GetX()), static_cast<float>(colorVector4.GetY()), static_cast<float>(colorVector4.GetZ()), static_cast<float>(colorVector4.GetW())).ToU32();
mOldColor = nodeGroup->GetColor();
nodeGroup->SetColor(color);
m_oldColor = nodeGroup->GetColor();
nodeGroup->SetColor(*m_color);
}
// set the new name
// if the new name is empty, the name is not changed
AZStd::string newGroupName;
parameters.GetValue("newName", this, newGroupName);
if (!newGroupName.empty())
if (m_newName.has_value())
{
nodeGroup->SetName(newGroupName.c_str());
nodeGroup->SetName(m_newName->c_str());
}
// check if parametes nodeNames is set
if (parameters.CheckIfHasParameter("nodeNames"))
if (m_nodeNames.has_value())
{
// keep the old nodes IDs
mOldNodeIds = CollectNodeIdsFromGroup(nodeGroup);
// get the node action
AZStd::string nodeAction;
parameters.GetValue("nodeAction", this, nodeAction);
// get the node names and split the string
AZStd::string nodeNamesString;
parameters.GetValue("nodeNames", this, nodeNamesString);
AZStd::vector<AZStd::string> nodeNames;
AzFramework::StringFunc::Tokenize(nodeNamesString.c_str(), nodeNames, ";", false, true);
m_oldNodeIds = CollectNodeIdsFromGroup(nodeGroup);
// remove the selected nodes from the given node group
if (AzFramework::StringFunc::Equal(nodeAction.c_str(), "remove"))
if (*m_nodeAction == NodeAction::Remove)
{
for (const AZStd::string& nodeName : nodeNames)
for (const AZStd::string& nodeName : *m_nodeNames)
{
EMotionFX::AnimGraphNode* animGraphNode = animGraph->RecursiveFindNodeByName(nodeName.c_str());
if (!animGraphNode)
@@ -163,9 +138,9 @@ namespace CommandSystem
nodeGroup->RemoveNodeById(animGraphNode->GetId());
}
}
else if (AzFramework::StringFunc::Equal(nodeAction.c_str(), "add")) // add the selected nodes to the given node group
else if (*m_nodeAction == NodeAction::Add)
{
for (const AZStd::string& nodeName : nodeNames)
for (const AZStd::string& nodeName : *m_nodeNames)
{
EMotionFX::AnimGraphNode* animGraphNode = animGraph->RecursiveFindNodeByName(nodeName.c_str());
if (!animGraphNode)
@@ -184,12 +159,12 @@ namespace CommandSystem
nodeGroup->AddNode(animGraphNode->GetId());
}
}
else if (AzFramework::StringFunc::Equal(nodeAction.c_str(), "replace")) // clear the node group and then add the selected nodes to the given node group
else if (*m_nodeAction == NodeAction::Replace)
{
// clear the node group upfront
nodeGroup->RemoveAllNodes();
for (const AZStd::string& nodeName : nodeNames)
for (const AZStd::string& nodeName : *m_nodeNames)
{
EMotionFX::AnimGraphNode* animGraphNode = animGraph->RecursiveFindNodeByName(nodeName.c_str());
if (!animGraphNode)
@@ -211,68 +186,40 @@ namespace CommandSystem
}
// save the current dirty flag and tell the anim graph that something got changed
mOldDirtyFlag = animGraph->GetDirtyFlag();
m_oldDirtyFlag = animGraph->GetDirtyFlag();
animGraph->SetDirtyFlag(true);
return true;
}
// undo the command
bool CommandAnimGraphAdjustNodeGroup::Undo(const MCore::CommandLine& parameters, AZStd::string& outResult)
bool CommandAnimGraphAdjustNodeGroup::Undo(const MCore::CommandLine&, AZStd::string& outResult)
{
EMotionFX::AnimGraph* animGraph = CommandsGetAnimGraph(parameters, this, outResult);
EMotionFX::AnimGraph* animGraph = EMotionFX::GetAnimGraphManager().FindAnimGraphByID(m_animGraphId);
if (!animGraph)
{
return false;
}
AZStd::string commandString = AZStd::string::format("AnimGraphAdjustNodeGroup -animGraphID %i", animGraph->GetID());
// set the old name or simply set the name if the name is not changed
if (parameters.CheckIfHasParameter("newName"))
{
AZStd::string newName;
parameters.GetValue("newName", this, newName);
commandString += AZStd::string::format(" -name \"%s\"", newName.c_str());
commandString += AZStd::string::format(" -newName \"%s\"", mOldName.c_str());
}
else
{
commandString += AZStd::string::format(" -name \"%s\"", mOldName.c_str());
}
// set the old visible flag
if (parameters.CheckIfHasParameter("isVisible"))
{
commandString += AZStd::string::format(" -isVisible %i", mOldIsVisible);
}
// set the old color
if (parameters.CheckIfHasParameter("color"))
{
AZ::Color oldColor;
oldColor.FromU32(mOldColor);
const AZStd::string oldColorString = AZStd::string::format("%.8f,%.8f,%.8f,%.8f", static_cast<float>(oldColor.GetR()), static_cast<float>(oldColor.GetG()), static_cast<float>(oldColor.GetB()), static_cast<float>(oldColor.GetA()));
commandString += AZStd::string::format(" -color \"%s\"", oldColorString.c_str());
}
// set the old nodes
if (parameters.CheckIfHasParameter("nodeNames"))
{
const AZStd::string nodeNamesString = CommandAnimGraphAdjustNodeGroup::GenerateNodeNameString(animGraph, mOldNodeIds);
commandString += AZStd::string::format(" -nodeNames \"%s\" -nodeAction \"replace\"", nodeNamesString.c_str());
}
CommandAnimGraphAdjustNodeGroup* command = aznew CommandAnimGraphAdjustNodeGroup(
GetCommandManager()->FindCommand(CommandAnimGraphAdjustNodeGroup::s_commandName),
/*animGraphId = */ m_animGraphId,
/*name = */ m_newName.has_value() ? *m_newName : m_name,
/*visible = */ m_isVisible.has_value() ? AZStd::optional<bool>(m_oldIsVisible) : AZStd::nullopt,
/*newName = */ m_newName.has_value() ? AZStd::optional<AZStd::string>(m_name) : AZStd::nullopt,
/*nodeNames = */ m_nodeNames.has_value() ? AZStd::optional<AZStd::vector<AZStd::string>>(GenerateNodeNameVector(animGraph, m_oldNodeIds)) : AZStd::nullopt,
/*nodeAction = */ m_nodeNames.has_value() ? AZStd::optional<NodeAction>(NodeAction::Replace) : AZStd::nullopt,
/*color = */ m_color.has_value() ? AZStd::optional<AZ::u32>(m_oldColor) : AZStd::nullopt
);
// execute the command
if (!GetCommandManager()->ExecuteCommandInsideCommand(commandString, outResult))
if (!GetCommandManager()->ExecuteCommandInsideCommand(command, outResult))
{
AZ_Error("EMotionFX", false, outResult.c_str());
}
// set the dirty flag back to the old value
animGraph->SetDirtyFlag(mOldDirtyFlag);
animGraph->SetDirtyFlag(m_oldDirtyFlag);
return true;
}
@@ -282,7 +229,7 @@ namespace CommandSystem
{
GetSyntax().ReserveParameters(8);
GetSyntax().AddRequiredParameter("name", "The name of the node group to adjust.", MCore::CommandSyntax::PARAMTYPE_STRING);
GetSyntax().AddParameter("animGraphID", "The id of the blend set the node group belongs to.", MCore::CommandSyntax::PARAMTYPE_INT, "-1");
EMotionFX::ParameterMixinAnimGraphId::InitSyntax(GetSyntax(), /*isParameterRequired=*/ false);
GetSyntax().AddParameter("isVisible", "The visibility flag of the node group.", MCore::CommandSyntax::PARAMTYPE_BOOLEAN, "true");
GetSyntax().AddParameter("newName", "The new name of the node group.", MCore::CommandSyntax::PARAMTYPE_STRING, "");
GetSyntax().AddParameter("nodeNames", "A list of node names that should be added/removed to/from the node group.", MCore::CommandSyntax::PARAMTYPE_STRING, "");
@@ -291,6 +238,51 @@ namespace CommandSystem
GetSyntax().AddParameter("updateUI", "Setting this to true will trigger a refresh of the node groups UI.", MCore::CommandSyntax::PARAMTYPE_BOOLEAN, "true");
}
bool CommandAnimGraphAdjustNodeGroup::SetCommandParameters(const MCore::CommandLine& parameters)
{
EMotionFX::ParameterMixinAnimGraphId::SetCommandParameters(parameters);
m_name = parameters.GetValue("name", this);
if (parameters.CheckIfHasParameter("isVisible"))
{
m_isVisible = parameters.GetValueAsBool("isVisible", this);
}
if (parameters.CheckIfHasParameter("newName"))
{
m_newName = parameters.GetValue("newName", this);
}
if (parameters.CheckIfHasParameter("nodeNames"))
{
m_nodeNames.emplace();
AzFramework::StringFunc::Tokenize(parameters.GetValue("nodeNames", this), m_nodeNames.value(), ";", false, true);
}
if (parameters.CheckIfHasValue("nodeAction"))
{
const AZStd::string& nodeActionStr = parameters.GetValue("nodeAction", this);
if (nodeActionStr == "add")
{
m_nodeAction = NodeAction::Add;
}
else if (nodeActionStr == "remove")
{
m_nodeAction = NodeAction::Remove;
}
else if (nodeActionStr == "replace")
{
m_nodeAction = NodeAction::Replace;
}
}
if (parameters.CheckIfHasParameter("color"))
{
m_color = AZ::Color(parameters.GetValueAsVector4("color", this)).ToU32();
}
if (parameters.CheckIfHasParameter("updateUI"))
{
m_updateUI = parameters.GetValueAsBool("updateUI", this);
}
return true;
}
const char* CommandAnimGraphAdjustNodeGroup::GetDescription() const
{
@@ -447,21 +439,20 @@ namespace CommandSystem
MCore::CommandGroup commandGroup;
AZStd::string commandString = AZStd::string::format("AnimGraphAddNodeGroup -animGraphID %i -name \"%s\" -updateUI %s",animGraph->GetID(), mOldName.c_str(), updateWindow.c_str());
commandGroup.AddCommandString(commandString);
commandGroup.AddCommandString(AZStd::string::format("AnimGraphAddNodeGroup -animGraphID %i -name \"%s\" -updateUI %s",animGraph->GetID(), mOldName.c_str(), updateWindow.c_str()));
const AZStd::string nodeNamesString = CommandAnimGraphAdjustNodeGroup::GenerateNodeNameString(animGraph, mOldNodeIds);
auto* command = aznew CommandAnimGraphAdjustNodeGroup(
GetCommandManager()->FindCommand(CommandAnimGraphAdjustNodeGroup::s_commandName),
/*animGraphId = */ animGraph->GetID(),
/*name = */ mOldName,
/*visible = */ mOldIsVisible,
/*newName = */ AZStd::nullopt,
/*nodeNames = */ CommandAnimGraphAdjustNodeGroup::GenerateNodeNameVector(animGraph, mOldNodeIds),
/*nodeAction = */ CommandAnimGraphAdjustNodeGroup::NodeAction::Add,
/*color = */ mOldColor
);
AZ::Color oldColor;
oldColor.FromU32(mOldColor);
const AZStd::string oldColorString = AZStd::string::format("%.8f,%.8f,%.8f,%.8f",
static_cast<float>(oldColor.GetR()), static_cast<float>(oldColor.GetG()), static_cast<float>(oldColor.GetB()), static_cast<float>(oldColor.GetA()));
commandString = AZStd::string::format(
"AnimGraphAdjustNodeGroup -animGraphID %i -name \"%s\" -isVisible %s -color \"%s\" -nodeNames \"%s\" -nodeAction \"add\" -updateUI %s",
animGraph->GetID(), mOldName.c_str(), AZStd::to_string(mOldIsVisible).c_str(), oldColorString.c_str(), nodeNamesString.c_str(), updateWindow.c_str());
commandGroup.AddCommandString(commandString);
commandGroup.AddCommand(command);
AZStd::string result;
if (!GetCommandManager()->ExecuteCommandGroupInsideCommand(commandGroup, result))
@@ -13,22 +13,73 @@
#include <MCore/Source/CommandGroup.h>
#include <EMotionFX/Source/AnimGraphNodeGroup.h>
#include <EMotionFX/Source/AnimGraph.h>
#include <EMotionFX/CommandSystem/Source/ParameterMixins.h>
namespace CommandSystem
{
// adjust a node group
MCORE_DEFINECOMMAND_START(CommandAnimGraphAdjustNodeGroup, "Adjust anim graph node group", true)
public:
static AZStd::string GenerateNodeNameString(EMotionFX::AnimGraph* animGraph, const AZStd::vector<EMotionFX::AnimGraphNodeId>& nodeIDs);
static AZStd::vector<EMotionFX::AnimGraphNodeId> CollectNodeIdsFromGroup(EMotionFX::AnimGraphNodeGroup* nodeGroup);
class CommandAnimGraphAdjustNodeGroup
: public MCore::Command
, public EMotionFX::ParameterMixinAnimGraphId
{
public:
AZ_CLASS_ALLOCATOR_DECL
AZStd::string mOldName;
bool mOldIsVisible;
AZ::u32 mOldColor;
AZStd::vector<EMotionFX::AnimGraphNodeId> mOldNodeIds;
bool mOldDirtyFlag;
MCORE_DEFINECOMMAND_END
static constexpr inline AZStd::string_view s_commandName = "AnimGraphAdjustNodeGroup";
enum class NodeAction
{
Add,
Remove,
Replace
};
explicit CommandAnimGraphAdjustNodeGroup(
MCore::Command* orgCommand = nullptr,
AZ::u32 animGraphId = MCORE_INVALIDINDEX32,
AZStd::string name = AZStd::string{},
AZStd::optional<bool> visible = AZStd::nullopt,
AZStd::optional<AZStd::string> newName = AZStd::nullopt,
AZStd::optional<AZStd::vector<AZStd::string>> nodeNames = AZStd::nullopt,
AZStd::optional<NodeAction> nodeAction = AZStd::nullopt,
AZStd::optional<AZ::u32> color = AZStd::nullopt,
AZStd::optional<bool> updateUI = AZStd::nullopt
);
bool Execute(const MCore::CommandLine& parameters, AZStd::string& outResult) override;
bool Undo(const MCore::CommandLine& parameters, AZStd::string& outResult) override;
void InitSyntax() override;
bool SetCommandParameters(const MCore::CommandLine& parameters) override;
bool GetIsUndoable() const override
{
return true;
}
const char* GetHistoryName() const override
{
return "Adjust anim graph node group";
}
const char* GetDescription() const override;
MCore::Command* Create() override
{
return new CommandAnimGraphAdjustNodeGroup(this);
}
static AZStd::vector<AZStd::string> GenerateNodeNameVector(EMotionFX::AnimGraph* animGraph, const AZStd::vector<EMotionFX::AnimGraphNodeId>& nodeIDs);
static AZStd::vector<EMotionFX::AnimGraphNodeId> CollectNodeIdsFromGroup(EMotionFX::AnimGraphNodeGroup* nodeGroup);
private:
AZStd::string m_name;
AZStd::optional<bool> m_isVisible;
AZStd::optional<AZStd::string> m_newName;
AZStd::optional<AZStd::vector<AZStd::string>> m_nodeNames;
AZStd::optional<NodeAction> m_nodeAction;
AZStd::optional<AZ::u32> m_color;
AZStd::optional<bool> m_updateUI;
bool m_oldIsVisible;
AZ::u32 m_oldColor;
AZStd::vector<EMotionFX::AnimGraphNodeId> m_oldNodeIds;
bool m_oldDirtyFlag;
};
// add node group
MCORE_DEFINECOMMAND_START(CommandAnimGraphAddNodeGroup, "Add anim graph node group", true)
@@ -9,7 +9,7 @@
// include the required headers
#include "NodeGroupCommands.h"
#include "CommandManager.h"
#include <EMotionFX/Source/NodeGroup.h>
#include <EMotionFX/Source/Allocators.h>
#include <EMotionFX/Source/ActorManager.h>
#include <MCore/Source/LogManager.h>
#include <MCore/Source/StringConversions.h>
@@ -20,230 +20,147 @@ namespace CommandSystem
//--------------------------------------------------------------------------------
// CommandAdjustNodeGroup
//--------------------------------------------------------------------------------
AZ_CLASS_ALLOCATOR_IMPL(CommandAdjustNodeGroup, EMotionFX::CommandAllocator, 0)
// constructor
CommandAdjustNodeGroup::CommandAdjustNodeGroup(MCore::Command* orgCommand)
: MCore::Command("AdjustNodeGroup", orgCommand)
, mOldNodeGroup(nullptr)
CommandAdjustNodeGroup::CommandAdjustNodeGroup(
MCore::Command* orgCommand,
uint32 actorId,
const AZStd::string& name,
AZStd::optional<AZStd::string> newName,
AZStd::optional<bool> enabledOnDefault,
AZStd::optional<AZStd::vector<AZStd::string>> nodeNames,
AZStd::optional<NodeAction> nodeAction
)
: MCore::Command(s_commandName.data(), orgCommand)
, EMotionFX::ParameterMixinActorId(actorId)
, m_name(name)
, m_newName(AZStd::move(newName))
, m_enabledOnDefault(enabledOnDefault)
, m_nodeNames(AZStd::move(nodeNames))
, m_nodeAction(nodeAction)
{
}
// destructor
CommandAdjustNodeGroup::~CommandAdjustNodeGroup()
{
if (mOldNodeGroup)
{
mOldNodeGroup->Destroy();
}
}
// execute
bool CommandAdjustNodeGroup::Execute(const MCore::CommandLine& parameters, AZStd::string& outResult)
bool CommandAdjustNodeGroup::Execute(const MCore::CommandLine&, AZStd::string& outResult)
{
AZStd::string valueString;
// get the motion id and the corresponding motion pointer
const int32 actorID = parameters.GetValueAsInt("actorID", this);
parameters.GetValue("name", this, &valueString);
// get the actor
EMotionFX::Actor* actor = EMotionFX::GetActorManager().FindActorByID(actorID);
EMotionFX::Actor* actor = EMotionFX::GetActorManager().FindActorByID(m_actorId);
if (actor == nullptr)
{
outResult = AZStd::string::format("Cannot adjust node group. Actor with id='%i' does not exist.", actorID);
outResult = AZStd::string::format("Cannot adjust node group. Actor with id='%i' does not exist.", m_actorId);
return false;
}
// get the node group
EMotionFX::NodeGroup* nodeGroup = actor->FindNodeGroupByNameNoCase(valueString.c_str());
EMotionFX::NodeGroup* nodeGroup = actor->FindNodeGroupByNameNoCase(m_name.c_str());
if (nodeGroup == nullptr)
{
outResult = AZStd::string::format("Cannot adjust node group. Node group with name='%s' does not exist.", valueString.c_str());
outResult = AZStd::string::format("Cannot adjust node group. Node group with name='%s' does not exist.", m_name.c_str());
return false;
}
// copy the old node group for undo
if (mOldNodeGroup)
{
mOldNodeGroup->Destroy();
}
mOldNodeGroup = aznew EMotionFX::NodeGroup(*nodeGroup);
m_oldNodeGroup = AZStd::make_unique<EMotionFX::NodeGroup>(*nodeGroup);
// check if newName is set and apply new name
if (parameters.CheckIfHasParameter("newName"))
if (m_newName.has_value())
{
parameters.GetValue("newName", this, &valueString);
nodeGroup->SetName(valueString.c_str());
nodeGroup->SetName(*m_newName);
}
// check if parameter disabledOnDefault is set and adjust it
if (parameters.CheckIfHasParameter("enabledOnDefault"))
if (m_enabledOnDefault.has_value())
{
const bool enabledOnDefault = parameters.GetValueAsBool("enabledOnDefault", this);
nodeGroup->SetIsEnabledOnDefault(enabledOnDefault);
nodeGroup->SetIsEnabledOnDefault(*m_enabledOnDefault);
}
// check if parametes nodeNames is set
if (parameters.CheckIfHasParameter("nodeNames"))
if (m_nodeNames.has_value())
{
// get the node action
AZStd::string nodeAction;
parameters.GetValue("nodeAction", this, &valueString);
// get the node names and split the string
AZStd::string nodeNameString;
parameters.GetValue("nodeNames", this, &nodeNameString);
// get the individual node names
AZStd::vector<AZStd::string> nodeNames;
AzFramework::StringFunc::Tokenize(nodeNameString.c_str(), nodeNames, MCore::CharacterConstants::semiColon, true /* keep empty strings */, true /* keep space strings */);
// get the number of nodes
const size_t numNodes = nodeNames.size();
// remove the selected nodes from the node group
if (AzFramework::StringFunc::Equal(valueString.c_str(), "remove", false /* no case */))
if (*m_nodeAction == NodeAction::Replace)
{
for (size_t i = 0; i < numNodes; ++i)
{
// get the node
EMotionFX::Node* node = actor->GetSkeleton()->FindNodeByName(nodeNames[i].c_str());
if (node == nullptr)
{
continue;
}
// remove the node
nodeGroup->RemoveNodeByNodeIndex((uint16)node->GetNodeIndex());
}
}
else if (AzFramework::StringFunc::Equal(valueString.c_str(), "add", false /* no case */)) // add the selected nodes to the node group
{
for (size_t i = 0; i < numNodes; ++i)
{
// get the node
EMotionFX::Node* node = actor->GetSkeleton()->FindNodeByName(nodeNames[i].c_str());
if (node == nullptr)
{
continue;
}
// add the node
uint16 nodeIndex = (uint16)node->GetNodeIndex();
nodeGroup->RemoveNodeByNodeIndex(nodeIndex);
nodeGroup->AddNode(nodeIndex);
}
}
else // selected nodes form the new node group
{
// clear previous nodes
nodeGroup->GetNodeArray().Clear();
// add all nodes to the group
for (size_t i = 0; i < numNodes; ++i)
}
for (const AZStd::string& nodeName : *m_nodeNames)
{
EMotionFX::Node* node = actor->GetSkeleton()->FindNodeByName(nodeName);
if (!node)
{
// get the node
EMotionFX::Node* node = actor->GetSkeleton()->FindNodeByName(nodeNames[i].c_str());
continue;
}
// check if node exists
if (node == nullptr)
{
continue;
}
// add the node
nodeGroup->AddNode((uint16)node->GetNodeIndex());
uint16 nodeIndex = (uint16)node->GetNodeIndex();
nodeGroup->RemoveNodeByNodeIndex(nodeIndex);
if (*m_nodeAction == NodeAction::Add || *m_nodeAction == NodeAction::Replace)
{
nodeGroup->AddNode(nodeIndex);
}
}
}
// save the current dirty flag and tell the actor that something got changed
mOldDirtyFlag = actor->GetDirtyFlag();
m_oldDirtyFlag = actor->GetDirtyFlag();
actor->SetDirtyFlag(true);
return true;
}
// undo the command
bool CommandAdjustNodeGroup::Undo(const MCore::CommandLine& parameters, AZStd::string& outResult)
bool CommandAdjustNodeGroup::Undo(const MCore::CommandLine&, AZStd::string& outResult)
{
// return if no information about the previous node group was stored
if (!mOldNodeGroup)
if (!m_oldNodeGroup)
{
return false;
}
// get the motion id and the corresponding motion pointer
int32 actorID = parameters.GetValueAsInt("actorID", this);
// get the name
AZStd::string name;
if (parameters.CheckIfHasParameter("newName"))
{
parameters.GetValue("newName", this, &name);
}
else
{
parameters.GetValue("name", this, &name);
}
// get the actor
EMotionFX::Actor* actor = EMotionFX::GetActorManager().FindActorByID(actorID);
EMotionFX::Actor* actor = EMotionFX::GetActorManager().FindActorByID(m_actorId);
// return error if actor was not found
if (actor == nullptr)
{
outResult = AZStd::string::format("Cannot adjust node group. Actor with id='%i' does not exist.", actorID);
outResult = AZStd::string::format("Cannot adjust node group. Actor with id='%i' does not exist.", m_actorId);
return false;
}
// get the node group
EMotionFX::NodeGroup* nodeGroup = actor->FindNodeGroupByNameNoCase(name.c_str());
EMotionFX::NodeGroup* nodeGroup = actor->FindNodeGroupByNameNoCase(m_newName.has_value() ? m_newName->c_str() : m_name.c_str());
// return error if node group name is not set
if (nodeGroup == nullptr)
if (!nodeGroup)
{
outResult = AZStd::string::format("Cannot adjust node group. Node group with name='%s' does not exist.", name.c_str());
outResult = AZStd::string::format("Cannot adjust node group. Node group with name='%s' does not exist.", m_newName.has_value() ? m_newName->c_str() : m_name.c_str());
return false;
}
// reset the old values
if (parameters.CheckIfHasParameter("enabledOnDefault"))
if (m_enabledOnDefault.has_value())
{
nodeGroup->SetIsEnabledOnDefault(mOldNodeGroup->GetIsEnabledOnDefault());
nodeGroup->SetIsEnabledOnDefault(m_oldNodeGroup->GetIsEnabledOnDefault());
}
if (parameters.CheckIfHasParameter("newName"))
if (m_newName.has_value())
{
nodeGroup->SetName(mOldNodeGroup->GetName());
nodeGroup->SetName(m_oldNodeGroup->GetName());
}
if (parameters.CheckIfHasParameter("nodeNames"))
if (m_nodeNames.has_value())
{
// clear previous nodes
nodeGroup->GetNodeArray().Clear();
const uint32 numNodes = mOldNodeGroup->GetNumNodes();
nodeGroup->SetNumNodes(static_cast<uint16>(numNodes));
const uint16 numNodes = m_oldNodeGroup->GetNumNodes();
nodeGroup->SetNumNodes(numNodes);
// add all nodes to the group
for (uint32 i = 0; i < numNodes; ++i)
for (uint16 i = 0; i < numNodes; ++i)
{
nodeGroup->SetNode(static_cast<uint16>(i), mOldNodeGroup->GetNode(static_cast<uint16>(i)));
nodeGroup->SetNode(i, m_oldNodeGroup->GetNode(i));
}
}
// delete the old node group
if (mOldNodeGroup)
{
mOldNodeGroup->Destroy();
}
mOldNodeGroup = nullptr;
m_oldNodeGroup = nullptr;
// set the dirty flag back to the old value
actor->SetDirtyFlag(mOldDirtyFlag);
actor->SetDirtyFlag(m_oldDirtyFlag);
return true;
}
@@ -252,7 +169,7 @@ namespace CommandSystem
void CommandAdjustNodeGroup::InitSyntax()
{
GetSyntax().ReserveParameters(6);
GetSyntax().AddRequiredParameter("actorID", "The id of the actor the node group belongs to.", MCore::CommandSyntax::PARAMTYPE_INT);
EMotionFX::ParameterMixinActorId::InitSyntax(GetSyntax(), /*isParameterRequired=*/ true);
GetSyntax().AddRequiredParameter("name", "The name of the node group to adjust.", MCore::CommandSyntax::PARAMTYPE_STRING);
GetSyntax().AddParameter("newName", "The new name of the node group.", MCore::CommandSyntax::PARAMTYPE_STRING, "");
GetSyntax().AddParameter("enabledOnDefault", "The enabled on default flag.", MCore::CommandSyntax::PARAMTYPE_BOOLEAN, "false");
@@ -261,6 +178,45 @@ namespace CommandSystem
}
bool CommandAdjustNodeGroup::SetCommandParameters(const MCore::CommandLine& parameters)
{
EMotionFX::ParameterMixinActorId::SetCommandParameters(parameters);
m_name = parameters.GetValue("name", this);
if (parameters.CheckIfHasParameter("newName"))
{
m_newName = parameters.GetValue("newName", this);
}
if (parameters.CheckIfHasParameter("enabledOnDefault"))
{
m_enabledOnDefault = parameters.GetValueAsBool("enabledOnDefault", this);
}
if (parameters.CheckIfHasParameter("nodeNames"))
{
m_nodeNames.emplace();
AzFramework::StringFunc::Tokenize(parameters.GetValue("nodeNames", this), m_nodeNames.value(), ";", false, true);
}
if (parameters.CheckIfHasParameter("nodeAction"))
{
const AZStd::string& nodeActionStr = parameters.GetValue("nodeAction", this);
if (nodeActionStr == "add")
{
m_nodeAction = NodeAction::Add;
}
else if (nodeActionStr == "remove")
{
m_nodeAction = NodeAction::Remove;
}
else if (nodeActionStr == "replace")
{
m_nodeAction = NodeAction::Replace;
}
}
return true;
}
// get the description
const char* CommandAdjustNodeGroup::GetDescription() const
{
@@ -304,7 +260,7 @@ namespace CommandSystem
}
// add new node group to the actor
EMotionFX::NodeGroup* nodeGroup = EMotionFX::NodeGroup::Create(name.c_str());
EMotionFX::NodeGroup* nodeGroup = aznew EMotionFX::NodeGroup(name);
actor->AddNodeGroup(nodeGroup);
// save the current dirty flag and tell the actor that something got changed
@@ -374,10 +330,7 @@ namespace CommandSystem
// destructor
CommandRemoveNodeGroup::~CommandRemoveNodeGroup()
{
if (mOldNodeGroup)
{
mOldNodeGroup->Destroy();
}
delete mOldNodeGroup;
}
@@ -407,11 +360,7 @@ namespace CommandSystem
}
// copy the old node group for undo
if (mOldNodeGroup)
{
mOldNodeGroup->Destroy();
}
delete mOldNodeGroup;
mOldNodeGroup = aznew EMotionFX::NodeGroup(*nodeGroup);
// remove the node group
@@ -9,10 +9,13 @@
#pragma once
// include the required headers
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include "CommandSystemConfig.h"
#include <MCore/Source/Command.h>
#include <MCore/Source/CommandGroup.h>
#include <EMotionFX/Source/EMotionFXConfig.h>
#include <EMotionFX/Source/NodeGroup.h>
#include <EMotionFX/CommandSystem/Source/ParameterMixins.h>
EMFX_FORWARD_DECLARE(Actor);
EMFX_FORWARD_DECLARE(NodeGroup);
@@ -20,20 +23,68 @@ EMFX_FORWARD_DECLARE(NodeGroup);
namespace CommandSystem
{
// adjust a node group
MCORE_DEFINECOMMAND_START(CommandAdjustNodeGroup, "Adjust node group", true)
bool mOldDirtyFlag;
EMotionFX::NodeGroup* mOldNodeGroup;
MCORE_DEFINECOMMAND_END
class CommandAdjustNodeGroup
: public MCore::Command
, public EMotionFX::ParameterMixinActorId
{
public:
AZ_CLASS_ALLOCATOR_DECL
enum class NodeAction
{
Add,
Remove,
Replace
};
static constexpr inline AZStd::string_view s_commandName = "AdjustNodeGroup";
CommandAdjustNodeGroup(
MCore::Command* orgCommand = nullptr,
uint32 actorId = MCORE_INVALIDINDEX32,
const AZStd::string& name = {},
AZStd::optional<AZStd::string> newName = AZStd::nullopt,
AZStd::optional<bool> enabledOnDefault = AZStd::nullopt,
AZStd::optional<AZStd::vector<AZStd::string>> nodeNames = AZStd::nullopt,
AZStd::optional<NodeAction> nodeAction = AZStd::nullopt
);
bool Execute(const MCore::CommandLine& parameters, AZStd::string& outResult) override;
bool Undo(const MCore::CommandLine& parameters, AZStd::string& outResult) override;
void InitSyntax() override;
bool SetCommandParameters(const MCore::CommandLine& parameters) override;
bool GetIsUndoable() const override
{
return true;
}
const char* GetHistoryName() const override
{
return "Adjust node group";
}
const char* GetDescription() const override;
MCore::Command* Create() override
{
return new CommandAdjustNodeGroup(this);
}
private:
AZStd::string m_name;
AZStd::optional<AZStd::string> m_newName;
AZStd::optional<bool> m_enabledOnDefault;
AZStd::optional<AZStd::vector<AZStd::string>> m_nodeNames;
AZStd::optional<NodeAction> m_nodeAction;
bool m_oldDirtyFlag = false;
AZStd::unique_ptr<EMotionFX::NodeGroup> m_oldNodeGroup = nullptr;
};
// add node group
MCORE_DEFINECOMMAND_START(CommandAddNodeGroup, "Add node group", true)
MCORE_DEFINECOMMAND_START(CommandAddNodeGroup, "Add node group", true)
bool mOldDirtyFlag;
MCORE_DEFINECOMMAND_END
// remove a node group
MCORE_DEFINECOMMAND_START(CommandRemoveNodeGroup, "Remove node group", true)
MCORE_DEFINECOMMAND_START(CommandRemoveNodeGroup, "Remove node group", true)
EMotionFX::NodeGroup * mOldNodeGroup;
bool mOldDirtyFlag;
MCORE_DEFINECOMMAND_END
@@ -80,6 +80,8 @@ namespace EMotionFX
AZ_RTTI(ParameterMixinAnimGraphId, "{3F48199E-6566-471F-A7EA-ADF67CAC4DCD}")
AZ_CLASS_ALLOCATOR_DECL
ParameterMixinAnimGraphId() = default;
ParameterMixinAnimGraphId(AZ::u32 id) : m_animGraphId(id) {}
virtual ~ParameterMixinAnimGraphId() = default;
static void Reflect(AZ::ReflectContext* context);
@@ -1015,7 +1015,7 @@ namespace EMotionFX
const uint32 numGroups = mNodeGroups.GetLength();
for (uint32 i = 0; i < numGroups; ++i)
{
mNodeGroups[i]->Destroy();
delete mNodeGroups[i];
}
mNodeGroups.Clear();
}
@@ -2085,7 +2085,7 @@ namespace EMotionFX
{
if (delFromMem)
{
mNodeGroups[index]->Destroy();
delete mNodeGroups[index];
}
mNodeGroups.Remove(index);
@@ -2097,7 +2097,7 @@ namespace EMotionFX
mNodeGroups.RemoveByValue(group);
if (delFromMem)
{
group->Destroy();
delete group;
}
}
@@ -1469,7 +1469,7 @@ namespace EMotionFX
}
// create the new group inside the actor
NodeGroup* newGroup = NodeGroup::Create(groupName, fileGroup.mNumNodes, fileGroup.mDisabledOnDefault ? false : true);
NodeGroup* newGroup = aznew NodeGroup(groupName, fileGroup.mNumNodes, fileGroup.mDisabledOnDefault ? false : true);
// read the node numbers
uint16 nodeIndex;
@@ -17,63 +17,16 @@ namespace EMotionFX
AZ_CLASS_ALLOCATOR_IMPL(NodeGroup, NodeAllocator, 0)
// default constructor
NodeGroup::NodeGroup()
: BaseObject()
NodeGroup::NodeGroup(const AZStd::string& groupName, uint16 numNodes, bool enabledOnDefault)
: mName(groupName)
, mNodes(numNodes)
, mEnabledOnDefault(enabledOnDefault)
{
SetIsEnabledOnDefault(true);
}
// extended constructor
NodeGroup::NodeGroup(const char* groupName, bool enabledOnDefault)
: BaseObject()
{
SetName(groupName);
SetIsEnabledOnDefault(enabledOnDefault);
}
// another extended constructor
NodeGroup::NodeGroup(const char* groupName, uint16 numNodes, bool enabledOnDefault)
: BaseObject()
{
SetName(groupName);
SetNumNodes(numNodes);
SetIsEnabledOnDefault(enabledOnDefault);
}
// destructor
NodeGroup::~NodeGroup()
{
mNodes.Clear();
}
// create
NodeGroup* NodeGroup::Create()
{
return aznew NodeGroup();
}
// create
NodeGroup* NodeGroup::Create(const char* groupName, bool enabledOnDefault)
{
return aznew NodeGroup(groupName, enabledOnDefault);
}
// create
NodeGroup* NodeGroup::Create(const char* groupName, uint16 numNodes, bool enabledOnDefault)
{
return aznew NodeGroup(groupName, numNodes, enabledOnDefault);
}
// set the name of the group
void NodeGroup::SetName(const char* groupName)
void NodeGroup::SetName(const AZStd::string& groupName)
{
mName = groupName;
}
@@ -30,38 +30,19 @@ namespace EMotionFX
* might contain incorrect or even uninitialized data.
*/
class EMFX_API NodeGroup
: public BaseObject
{
public:
AZ_CLASS_ALLOCATOR_DECL
/**
* The default creation method.
* This does not assign a name and there will be nodes inside this group on default.
* Also the default enabled state is set to true.
*/
static NodeGroup* Create();
/**
* Extended creation.
* @param groupName The name of the group. Please keep in mind that it is not allowed to have two groups with the same name inside an Actor.
* @param enabledOnDefault Set to true (default) when the nodes inside this group should be enabled on default.
*/
static NodeGroup* Create(const char* groupName, bool enabledOnDefault = true);
/**
* Another extended constructor.
* @param groupName The name of the group. Please keep in mind that it is not allowed to have two groups with the same name inside an Actor.
* @param numNodes The number of nodes to create inside the group. This will have all uninitialized values for the node indices in the group, so be sure that you
* set them all to some valid node index using the NodeGroup::SetNode(...) method. This method automatically calls the SetNumNodes(...) method.
* @param enabledOnDefault Set to true (default) when the nodes inside this group should be enabled on default.
*/
static NodeGroup* Create(const char* groupName, uint16 numNodes, bool enabledOnDefault = true);
NodeGroup(const AZStd::string& groupName = {}, uint16 numNodes = 0, bool enabledOnDefault = true);
NodeGroup(const NodeGroup& aOther);
NodeGroup& operator=(const NodeGroup& aOther);
/**
* Set the name of the group. Please keep in mind that group names must be unique inside the Actor objects. So you should not have two or more groups with the same name.
* @param groupName The name of the group.
*/
void SetName(const char* groupName);
void SetName(const AZStd::string& groupName);
/**
* Get the name of the group as null terminated character buffer.
@@ -172,37 +153,6 @@ namespace EMotionFX
*/
void SetIsEnabledOnDefault(bool enabledOnDefault);
/**
* The default constructor.
* This does not assign a name and there will be nodes inside this group on default.
* Also the default enabled state is set to true.
*/
NodeGroup();
/**
* Extended constructor.
* @param groupName The name of the group. Please keep in mind that it is not allowed to have two groups with the same name inside an Actor.
* @param enabledOnDefault Set to true (default) when the nodes inside this group should be enabled on default.
*/
NodeGroup(const char* groupName, bool enabledOnDefault = true);
/**
* Another extended constructor.
* @param groupName The name of the group. Please keep in mind that it is not allowed to have two groups with the same name inside an Actor.
* @param numNodes The number of nodes to create inside the group. This will have all uninitialized values for the node indices in the group, so be sure that you
* set them all to some valid node index using the NodeGroup::SetNode(...) method. This method automatically calls the SetNumNodes(...) method.
* @param enabledOnDefault Set to true (default) when the nodes inside this group should be enabled on default.
*/
NodeGroup(const char* groupName, uint16 numNodes, bool enabledOnDefault = true);
/**
* The destructor.
*/
~NodeGroup();
NodeGroup(const NodeGroup& aOther);
NodeGroup& operator=(const NodeGroup& aOther);
private:
AZStd::string mName; /**< The name of the group. */
MCore::SmallArray<uint16> mNodes; /**< The node index numbers that are inside this group. */
@@ -9,6 +9,7 @@
#include <AzQtComponents/Utilities/Conversions.h>
#include <EMotionFX/CommandSystem/Source/AnimGraphConnectionCommands.h>
#include <EMotionFX/CommandSystem/Source/AnimGraphNodeCommands.h>
#include <EMotionFX/CommandSystem/Source/AnimGraphNodeGroupCommands.h>
#include <EMotionFX/CommandSystem/Source/MotionSetCommands.h>
#include <EMotionFX/Source/AnimGraphExitNode.h>
#include <EMotionFX/Source/AnimGraphMotionNode.h>
@@ -1208,7 +1209,7 @@ namespace EMStudio
MCore::CommandGroup commandGroup("Adjust anim graph node group");
AZStd::string nodeNames;
AZStd::vector<AZStd::string> nodeNames;
for (const QModelIndex& selectedIndex : selectionList)
{
// Skip transitions and blend tree connections.
@@ -1221,12 +1222,19 @@ namespace EMStudio
EMotionFX::AnimGraphNodeGroup* nodeGroup = animGraph->FindNodeGroupForNode(selectedNode);
if (nodeGroup)
{
const AZStd::string command = AZStd::string::format("AnimGraphAdjustNodeGroup -animGraphID %i -name \"%s\" -nodeNames \"%s\" -nodeAction \"remove\"", animGraph->GetID(), nodeGroup->GetName(), selectedNode->GetName());
commandGroup.AddCommandString(command);
auto* command = aznew CommandSystem::CommandAnimGraphAdjustNodeGroup(
GetCommandManager()->FindCommand(CommandSystem::CommandAnimGraphAdjustNodeGroup::s_commandName),
/*animGraphId = */ animGraph->GetID(),
/*name = */ nodeGroup->GetNameString(),
/*visible = */ AZStd::nullopt,
/*newName = */ AZStd::nullopt,
/*nodeNames = */ {{selectedNode->GetNameString()}},
/*nodeAction = */ CommandSystem::CommandAnimGraphAdjustNodeGroup::NodeAction::Remove
);
commandGroup.AddCommand(command);
}
nodeNames += selectedNode->GetName();
nodeNames += ";";
nodeNames.emplace_back(selectedNode->GetName());
}
if (!nodeNames.empty())
{
@@ -1235,8 +1243,16 @@ namespace EMStudio
if (newNodeGroup)
{
const AZStd::string command = AZStd::string::format("AnimGraphAdjustNodeGroup -animGraphID %i -name \"%s\" -nodeNames \"%s\" -nodeAction \"add\"", animGraph->GetID(), newNodeGroup->GetName(), nodeNames.c_str());
commandGroup.AddCommandString(command);
auto* command = aznew CommandSystem::CommandAnimGraphAdjustNodeGroup(
GetCommandManager()->FindCommand(CommandSystem::CommandAnimGraphAdjustNodeGroup::s_commandName),
/*animGraphId = */ animGraph->GetID(),
/*name = */ newNodeGroup->GetNameString(),
/*visible = */ AZStd::nullopt,
/*newName = */ AZStd::nullopt,
/*nodeNames = */ nodeNames,
/*nodeAction = */ CommandSystem::CommandAnimGraphAdjustNodeGroup::NodeAction::Add
);
commandGroup.AddCommand(command);
}
AZStd::string outResult;
@@ -74,11 +74,6 @@ namespace EMStudio
mLineEdit->setText(nodeGroup.c_str());
mLineEdit->selectAll();
// create add the error message
/*mErrorMsg = new QLabel("<font color='red'>Error: Duplicate name found</font>");
mErrorMsg->setAlignment(Qt::AlignVCenter | Qt::AlignLeft);
mErrorMsg->setVisible(false);*/
// create the button layout
QHBoxLayout* buttonLayout = new QHBoxLayout();
mOKButton = new QPushButton("OK");
@@ -139,10 +134,16 @@ namespace EMStudio
void NodeGroupRenameWindow::Accepted()
{
// Execute the command
AZStd::string commandString, outResult;
AZStd::string outResult;
const AZStd::string convertedNewName = FromQtString(mLineEdit->text());
commandString = AZStd::string::format("AnimGraphAdjustNodeGroup -animGraphID %i -name \"%s\" -newName \"%s\"", mAnimGraph->GetID(), mNodeGroup.c_str(), convertedNewName.c_str());
if (GetCommandManager()->ExecuteCommand(commandString.c_str(), outResult) == false)
auto* command = aznew CommandSystem::CommandAnimGraphAdjustNodeGroup(
GetCommandManager()->FindCommand(CommandSystem::CommandAnimGraphAdjustNodeGroup::s_commandName),
mAnimGraph->GetID(),
/*name = */ mNodeGroup,
/*visible = */ AZStd::nullopt,
/*newName = */ convertedNewName
);
if (!GetCommandManager()->ExecuteCommand(command, outResult))
{
MCore::LogError(outResult.c_str());
}
@@ -167,7 +168,7 @@ namespace EMStudio
mAdjustCallback = new CommandAnimGraphAdjustNodeGroupCallback(false);
GetCommandManager()->RegisterCommandCallback("AnimGraphAddNodeGroup", mCreateCallback);
GetCommandManager()->RegisterCommandCallback("AnimGraphRemoveNodeGroup", mRemoveCallback);
GetCommandManager()->RegisterCommandCallback("AnimGraphAdjustNodeGroup", mAdjustCallback);
GetCommandManager()->RegisterCommandCallback(CommandSystem::CommandAnimGraphAdjustNodeGroup::s_commandName.data(), mAdjustCallback);
// add the add button
mAddAction = new QAction(MysticQt::GetMysticQt()->FindIcon("Images/Icons/Plus.svg"), tr("Add new node group"), this);
@@ -486,13 +487,16 @@ namespace EMStudio
bool isVisible = state == Qt::Checked;
// construct the command
AZStd::string commandString;
commandString = AZStd::string::format("AnimGraphAdjustNodeGroup -animGraphID %i -name \"%s\" -isVisible %s", animGraph->GetID(), nodeGroup->GetName(), AZStd::to_string(isVisible).c_str());
auto* command = aznew CommandSystem::CommandAnimGraphAdjustNodeGroup(
GetCommandManager()->FindCommand(CommandSystem::CommandAnimGraphAdjustNodeGroup::s_commandName),
/*animGraphId = */ animGraph->GetID(),
/*name = */ nodeGroup->GetNameString(),
/*visible = */ isVisible
);
// execute the command
AZStd::string resultString;
if (GetCommandManager()->ExecuteCommand(commandString.c_str(), resultString) == false)
if (GetCommandManager()->ExecuteCommand(command, resultString) == false)
{
if (resultString.size() > 0)
{
@@ -519,16 +523,21 @@ namespace EMStudio
// get a pointer to the node group
EMotionFX::AnimGraphNodeGroup* nodeGroup = animGraph->GetNodeGroup(groupIndex);
// get the color
AZ::Vector4 finalColor = color.GetAsVector4();
// construct the command
AZStd::string commandString;
commandString = AZStd::string::format("AnimGraphAdjustNodeGroup -animGraphID %i -name \"%s\" -color \"%s\"", animGraph->GetID(), nodeGroup->GetName(), AZStd::to_string(finalColor).c_str());
auto* command = aznew CommandSystem::CommandAnimGraphAdjustNodeGroup(
GetCommandManager()->FindCommand(CommandSystem::CommandAnimGraphAdjustNodeGroup::s_commandName),
/*animGraphId = */ animGraph->GetID(),
/*name = */ nodeGroup->GetName(),
/*visible = */ AZStd::nullopt,
/*newName = */ AZStd::nullopt,
/*nodeNames = */ AZStd::nullopt,
/*nodeAction = */ AZStd::nullopt,
/*color = */ color.ToU32()
);
// execute the command
AZStd::string resultString;
if (GetCommandManager()->ExecuteCommand(commandString.c_str(), resultString) == false)
if (GetCommandManager()->ExecuteCommand(command, resultString) == false)
{
if (resultString.size() > 0)
{
@@ -112,8 +112,13 @@ namespace EMStudio
// execute the command
AZStd::string outResult;
AZStd::string command = AZStd::string::format("AdjustNodeGroup -actorID %i -name \"%s\" -newName \"%s\"", mActor->GetID(), mNodeGroupName.c_str(), convertedNewName.c_str());
if (GetCommandManager()->ExecuteCommand(command.c_str(), outResult) == false)
auto* command = aznew CommandSystem::CommandAdjustNodeGroup(
GetCommandManager()->FindCommand(CommandSystem::CommandAdjustNodeGroup::s_commandName),
/*actorId=*/ mActor->GetID(),
/*name=*/ mNodeGroupName,
/*newName=*/ convertedNewName
);
if (GetCommandManager()->ExecuteCommand(command, outResult) == false)
{
AZ_Error("EMotionFX", false, outResult.c_str());
}
@@ -362,98 +367,6 @@ namespace EMStudio
mSelectedRow = MCORE_INVALIDINDEX32;
}
}
/*void NodeGroupManagementWidget::UpdateNodeGroupWidget(QTableWidgetItem* current, QTableWidgetItem* previous)
{
MCORE_UNUSED(previous);
// return if no node group widget is set
if (mNodeGroupWidget == nullptr)
return;
// set the node group widget to the actual selection
mNodeGroupWidget->SetActor( mActor );
if (current)
{
// set the current row
mSelectedRow = current->row();
// set the node group
NodeGroup* nodeGroup = mActor->FindNodeGroupByName( FromQtString(mNodeGroupsTable->item(current->row(), 1)->text()).c_str() );
mNodeGroupWidget->SetNodeGroup( nodeGroup );
}
else
{
mNodeGroupWidget->SetNodeGroup( nullptr );
mSelectedRow = MCORE_INVALIDINDEX32;
}
}*/
// called whenever a cell is changed
/*void NodeGroupManagementWidget::NodeGroupNamesChanged(const QString& text)
{
// get the sender widget
QWidget* senderWidget = (QWidget*)sender();
// check for duplicates
const int duplicateFound = SearchTableForString( mNodeGroupsTable, text );
// mark edit field in red, if entry already exists
if (duplicateFound >= 0)
GetManager()->SetWidgetAsInvalidInput( senderWidget );
else
senderWidget->setStyleSheet("");
}*/
// starts editing
/*void NodeGroupManagementWidget::NodeGroupeNameDoubleClicked(QTableWidgetItem* item)
{
// add new line edit for the selected widget
QLineEdit* lineEdit = new QLineEdit( mNodeGroupsTable->item(item->row(), 0)->text() );
mNodeGroupsTable->setCellWidget( item->row(), 0, lineEdit );
// jump into the edit field
lineEdit->selectAll();
lineEdit->setFocus();
mNodeGroupsTable->setCurrentCell( item->row(), 0 );
// connect slots for edit finishing and text change
connect( lineEdit, SIGNAL(editingFinished()), this, SLOT(NodeGroupNameEditingFinished()) );
connect( lineEdit, SIGNAL(textChanged(QString)), this, SLOT(NodeGroupNamesChanged(QString)) );
}*/
// called when editing is finished
/*void NodeGroupManagementWidget::NodeGroupNameEditingFinished()
{
// get the current item
QTableWidgetItem* item = mNodeGroupsTable->currentItem();
// get the sender widget
QLineEdit* senderWidget = (QLineEdit*)sender();
// return if one of the widgets does not exist
if (item == nullptr || senderWidget == nullptr)
return;
// call commands for name change if name does not exist yet
if (senderWidget->styleSheet() == "")
{
// call command for adding a new node group
String outResult;
String command;
command.Format( "AdjustNodeGroup -actorID %i -name \"%s\" -newName \"%s\"", mActor->GetID(), FromQtString(item->text()).c_str(), FromQtString(senderWidget->text()).c_str() );
if (EMStudio::GetCommandManager()->ExecuteCommand( command.c_str(), outResult ) == false)
LogError( outResult.c_str() );
}
else
{
// delete the line edit
mNodeGroupsTable->setCellWidget(item->row(), item->column(), nullptr);
}
}*/
// function to add a new node group with the specified name
@@ -562,16 +475,20 @@ namespace EMStudio
if (rowChechbox == senderCheckbox)
{
nodeGroupName = mNodeGroupsTable->item(i, 1)->text().toUtf8().data();
break;
}
}
// execute the command
AZStd::string outResult;
const AZStd::string command = AZStd::string::format("AdjustNodeGroup -actorID %i -name \"%s\" -enabledOnDefault \"%s\"",
mActor->GetID(),
nodeGroupName.c_str(),
AZStd::to_string(checked).c_str());
if (EMStudio::GetCommandManager()->ExecuteCommand(command.c_str(), outResult) == false)
auto* command = aznew CommandSystem::CommandAdjustNodeGroup(
GetCommandManager()->FindCommand(CommandSystem::CommandAdjustNodeGroup::s_commandName),
/*actorId=*/ mActor->GetID(),
/*name=*/ nodeGroupName,
/*newName=*/ AZStd::nullopt,
/*enabledOnDefault=*/ checked
);
if (EMStudio::GetCommandManager()->ExecuteCommand(command, outResult) == false)
{
AZ_Error("EMotionFX", false, outResult.c_str());
}
@@ -35,7 +35,6 @@ namespace EMStudio
mNodeTable = nullptr;
mSelectNodesButton = nullptr;
mNodeGroup = nullptr;
mNodeAction = "";
// init the widget
Init();
@@ -254,11 +253,11 @@ namespace EMStudio
QWidget* senderWidget = (QWidget*)sender();
if (senderWidget == mAddNodesButton)
{
mNodeAction = "add";
mNodeAction = CommandSystem::CommandAdjustNodeGroup::NodeAction::Add;
}
else
{
mNodeAction = "select";
mNodeAction = CommandSystem::CommandAdjustNodeGroup::NodeAction::Replace;
}
// get the selected actorinstance
@@ -293,46 +292,37 @@ namespace EMStudio
// remove nodes
void NodeGroupWidget::RemoveNodesButtonPressed()
{
// generate node list string
AZStd::string nodeList;
uint32 lowestSelectedRow = MCORE_INVALIDINDEX32;
const uint32 numTableRows = mNodeTable->rowCount();
for (uint32 i = 0; i < numTableRows; ++i)
{
// get the current table item
QTableWidgetItem* item = mNodeTable->item(i, 0);
if (item == nullptr)
{
continue;
}
// add the item to remove list, if it's selected
if (item->isSelected())
{
nodeList += AZStd::string::format("%s;", item->text().toUtf8().data());
if ((uint32)item->row() < lowestSelectedRow)
{
lowestSelectedRow = (uint32)item->row();
}
}
}
// stop here if nothing selected
if (nodeList.empty())
if (mNodeTable->selectedItems().empty())
{
return;
}
// call command for adjusting disable on default flag
// generate node list string
AZStd::vector<AZStd::string> nodeList;
int lowestSelectedRow = AZStd::numeric_limits<int>::max();
for (const QTableWidgetItem* item : mNodeTable->selectedItems())
{
nodeList.emplace_back(FromQtString(item->text()));
lowestSelectedRow = AZStd::min(lowestSelectedRow, item->row());
}
AZStd::string outResult;
AZStd::string command = AZStd::string::format("AdjustNodeGroup -actorID %i -name \"%s\" -nodeAction \"remove\" -nodeNames \"%s\"", mActor->GetID(), mNodeGroup->GetName(), nodeList.c_str());
auto* command = aznew CommandSystem::CommandAdjustNodeGroup(
GetCommandManager()->FindCommand(CommandSystem::CommandAdjustNodeGroup::s_commandName),
/*actorId=*/ mActor->GetID(),
/*name=*/ mNodeGroup->GetName(),
/*newName=*/ AZStd::nullopt,
/*enabledOnDefault=*/ AZStd::nullopt,
/*nodeNames=*/ AZStd::move(nodeList),
/*nodeAction=*/ CommandSystem::CommandAdjustNodeGroup::NodeAction::Remove
);
if (EMStudio::GetCommandManager()->ExecuteCommand(command, outResult) == false)
{
AZ_Error("EMotionFX", false, outResult.c_str());
}
// selected the next row
if (lowestSelectedRow > ((uint32)mNodeTable->rowCount() - 1))
if (lowestSelectedRow > (mNodeTable->rowCount() - 1))
{
mNodeTable->selectRow(lowestSelectedRow - 1);
}
@@ -353,19 +343,23 @@ namespace EMStudio
}
// generate node list string
AZStd::string nodeList;
nodeList.reserve(16448);
const uint32 numSelectedNodes = selectionList.GetLength();
for (uint32 i = 0; i < numSelectedNodes; ++i)
AZStd::vector<AZStd::string> nodeList;
const uint32 selectionListSize = selectionList.GetLength();
for (uint32 i = 0; i < selectionListSize; ++i)
{
nodeList += selectionList[i].GetNodeName();
nodeList += ";";
nodeList.emplace_back(selectionList[i].GetNodeName());
}
AzFramework::StringFunc::Strip(nodeList, MCore::CharacterConstants::semiColon, true /* case sensitive */, false /* beginning */, true /* ending */);
// call command for adjusting disable on default flag
AZStd::string outResult;
AZStd::string command = AZStd::string::format("AdjustNodeGroup -actorID %i -name \"%s\" -nodeAction \"%s\" -nodeNames \"%s\"", mActor->GetID(), mNodeGroup->GetName(), mNodeAction.c_str(), nodeList.c_str());
auto* command = aznew CommandSystem::CommandAdjustNodeGroup(
GetCommandManager()->FindCommand(CommandSystem::CommandAdjustNodeGroup::s_commandName),
/*actorId=*/ mActor->GetID(),
/*name=*/ mNodeGroup->GetName(),
/*newName=*/ AZStd::nullopt,
/*enabledOnDefault=*/ AZStd::nullopt,
/*nodeNames=*/ AZStd::move(nodeList),
/*nodeAction=*/ mNodeAction
);
if (EMStudio::GetCommandManager()->ExecuteCommand(command, outResult) == false)
{
AZ_Error("EMotionFX", false, outResult.c_str());
@@ -13,6 +13,7 @@
#include <MysticQt/Source/DialogStack.h>
#include "../../../../EMStudioSDK/Source/DockWidgetPlugin.h"
#include "../../../../EMStudioSDK/Source/NodeSelectionWindow.h"
#include <EMotionFX/CommandSystem/Source/NodeGroupCommands.h>
#endif
QT_FORWARD_DECLARE_CLASS(QLineEdit)
@@ -58,7 +59,7 @@ namespace EMStudio
CommandSystem::SelectionList mNodeSelectionList;
EMotionFX::NodeGroup* mNodeGroup;
uint16 mNodeGroupIndex;
AZStd::string mNodeAction;
CommandSystem::CommandAdjustNodeGroup::NodeAction mNodeAction;
// widgets
QTableWidget* mNodeTable;
@@ -11,6 +11,7 @@
#include "../../../../EMStudioSDK/Source/EMStudioCore.h"
#include <MCore/Source/LogManager.h>
#include <EMotionFX/CommandSystem/Source/CommandManager.h>
#include <EMotionFX/CommandSystem/Source/NodeGroupCommands.h>
#include "../../../../EMStudioSDK/Source/EMStudioManager.h"
// include qt headers
@@ -99,7 +100,7 @@ namespace EMStudio
GetCommandManager()->RegisterCommandCallback("Select", mSelectCallback);
GetCommandManager()->RegisterCommandCallback("Unselect", mUnselectCallback);
GetCommandManager()->RegisterCommandCallback("ClearSelection", mClearSelectionCallback);
GetCommandManager()->RegisterCommandCallback("AdjustNodeGroup", mAdjustNodeGroupCallback);
GetCommandManager()->RegisterCommandCallback(CommandSystem::CommandAdjustNodeGroup::s_commandName.data(), mAdjustNodeGroupCallback);
GetCommandManager()->RegisterCommandCallback("AddNodeGroup", mAddNodeGroupCallback);
GetCommandManager()->RegisterCommandCallback("RemoveNodeGroup", mRemoveNodeGroupCallback);
+3 -3
View File
@@ -28,10 +28,10 @@ namespace MCore
// constructor
Command::Command(const char* commandName, Command* originalCommand)
Command::Command(AZStd::string commandName, Command* originalCommand)
: mOrgCommand(originalCommand)
, mCommandName(AZStd::move(commandName))
{
mCommandName = commandName;
mOrgCommand = originalCommand;
}
+1 -1
View File
@@ -185,7 +185,7 @@ namespace MCore
* @param commandName The unique identifier for the command.
* @param originalCommand The original command, or nullptr when this is the original command.
*/
Command(const char* commandName, Command* originalCommand);
Command(AZStd::string commandName, Command* originalCommand);
/**
* Destructor.
@@ -0,0 +1,22 @@
{
"Type": "JsonSerialization",
"Version": 1,
"ClassName": "PassAsset",
"ClassData": {
"PassTemplate": {
"Name": "LyShineParentTemplate",
"PassClass": "LyShinePass",
"Slots": [
{
"Name": "ColorInputOutput",
"SlotType": "InputOutput"
},
{
"Name": "DepthInputOutput",
"SlotType": "InputOutput",
"ScopeAttachmentUsage": "DepthStencil"
}
]
}
}
}
@@ -0,0 +1,13 @@
{
"Type": "JsonSerialization",
"Version": 1,
"ClassName": "AssetAliasesSourceData",
"ClassData": {
"AssetPaths": [
{
"Name": "LyShineParentTemplate",
"Path": "Passes/LyShineParent.pass"
}
]
}
}
+3
View File
@@ -193,6 +193,9 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED)
FILES_CMAKE
lyshine_common_module_files.cmake
lyshine_tests_files.cmake
COMPILE_DEFINITIONS
PRIVATE
LYSHINE_TESTS
INCLUDE_DIRECTORIES
PRIVATE
Tests
+14
View File
@@ -1547,6 +1547,20 @@ AssetTreeEntry* EditorWindow::GetSliceLibraryTree()
return m_sliceLibraryTree;
}
AZ::EntityId EditorWindow::GetCanvasForCurrentEditorMode()
{
AZ::EntityId canvasEntityId;
if (GetEditorMode() == UiEditorMode::Edit)
{
canvasEntityId = GetCanvas();
}
else
{
canvasEntityId = GetPreviewModeCanvas();
}
return canvasEntityId;
}
void EditorWindow::ToggleEditorMode()
{
m_editorMode = (m_editorMode == UiEditorMode::Edit) ? UiEditorMode::Preview : UiEditorMode::Edit;
+3
View File
@@ -143,6 +143,9 @@ public: // member functions
//! Returns the current mode of the editor (Edit or Preview)
UiEditorMode GetEditorMode() { return m_editorMode; }
//! Returns the UI canvas for the current mode (Edit or Preview)
AZ::EntityId GetCanvasForCurrentEditorMode();
//! Toggle the editor mode between Edit and Preview
void ToggleEditorMode();
+109 -38
View File
@@ -7,6 +7,8 @@
*/
#include "EditorCommon.h"
#include "UiCanvasComponent.h"
#include "EditorDefs.h"
#include "Settings.h"
#include <AzCore/std/containers/map.h>
@@ -245,6 +247,7 @@ ViewportWidget::ViewportWidget(EditorWindow* parent)
FontNotificationBus::Handler::BusConnect();
AZ::TickBus::Handler::BusConnect();
AZ::RPI::ViewportContextNotificationBus::Handler::BusConnect(GetCurrentContextName());
}
ViewportWidget::~ViewportWidget()
@@ -252,6 +255,8 @@ ViewportWidget::~ViewportWidget()
AzToolsFramework::EditorPickModeNotificationBus::Handler::BusDisconnect();
FontNotificationBus::Handler::BusDisconnect();
AZ::TickBus::Handler::BusDisconnect();
LyShinePassDataRequestBus::Handler::BusDisconnect();
AZ::RPI::ViewportContextNotificationBus::Handler::BusDisconnect();
m_uiRenderer.reset();
@@ -272,6 +277,8 @@ void ViewportWidget::InitUiRenderer()
lyShine->SetUiRendererForEditor(m_uiRenderer);
m_draw2d = AZStd::make_shared<CDraw2d>(GetViewportContext());
LyShinePassDataRequestBus::Handler::BusConnect(GetViewportContext()->GetRenderScene()->GetId());
}
ViewportInteraction* ViewportWidget::GetViewportInteraction()
@@ -487,30 +494,44 @@ void ViewportWidget::EnableCanvasRender()
}
void ViewportWidget::OnTick(float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint time)
{
// Update
UiEditorMode editorMode = m_editorWindow->GetEditorMode();
if (editorMode == UiEditorMode::Edit)
{
UpdateEditMode(deltaTime);
}
else // if (editorMode == UiEditorMode::Preview)
{
UpdatePreviewMode(deltaTime);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
int ViewportWidget::GetTickOrder()
{
return AZ::TICK_PRE_RENDER;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void ViewportWidget::OnRenderTick()
{
if (!m_uiRenderer->IsReady() || !m_canvasRenderIsEnabled)
{
return;
}
#ifdef LYSHINE_ATOM_TODO
gEnv->pRenderer->SetSrgbWrite(true);
#endif
const float dpiScale = QtHelpers::GetHighDpiScaleFactor(*this);
ViewportIcon::SetDpiScaleFactor(dpiScale);
// Set up to render a frame to this viewport's window
GetViewportContext()->RenderTick();
UiEditorMode editorMode = m_editorWindow->GetEditorMode();
if (editorMode == UiEditorMode::Edit)
{
RenderEditMode(deltaTime);
RenderEditMode();
}
else // if (editorMode == UiEditorMode::Preview)
{
RenderPreviewMode(deltaTime);
RenderPreviewMode();
}
}
@@ -884,17 +905,37 @@ void ViewportWidget::OnFontTextureUpdated([[maybe_unused]] IFFont* font)
m_fontTextureHasChanged = true;
}
LyShine::AttachmentImagesAndDependencies ViewportWidget::GetRenderTargets()
{
LyShine::AttachmentImagesAndDependencies canvasTargets;
AZ::EntityId canvasEntityId = m_editorWindow->GetCanvasForCurrentEditorMode();
if (canvasEntityId.IsValid())
{
AZ::Entity* canvasEntity = nullptr;
EBUS_EVENT_RESULT(canvasEntity, AZ::ComponentApplicationBus, FindEntity, canvasEntityId);
AZ_Assert(canvasEntity, "Canvas entity not found by ID");
if (canvasEntity)
{
UiCanvasComponent* canvasComponent = canvasEntity->FindComponent<UiCanvasComponent>();
AZ_Assert(canvasComponent, "Canvas entity has no canvas component");
if (canvasComponent)
{
canvasComponent->GetRenderTargets(canvasTargets);
}
}
}
return canvasTargets;
}
QPointF ViewportWidget::WidgetToViewport(const QPointF & point) const
{
return point * WidgetToViewportFactor();
}
void ViewportWidget::RenderEditMode(float deltaTime)
void ViewportWidget::UpdateEditMode(float deltaTime)
{
// sort keys for different layers
static const int64_t backgroundKey = -0x1000;
static const int64_t topLayerKey = 0x1000000;
if (m_fontTextureHasChanged)
{
// A font texture has changed since we last rendered. Force a render graph update for each loaded canvas
@@ -908,6 +949,28 @@ void ViewportWidget::RenderEditMode(float deltaTime)
return; // this can happen if a render happens during a restart
}
AZ::Vector2 canvasSize;
EBUS_EVENT_ID_RESULT(canvasSize, canvasEntityId, UiCanvasBus, GetCanvasSize);
// Set the target size of the canvas
EBUS_EVENT_ID(canvasEntityId, UiCanvasBus, SetTargetCanvasSize, false, canvasSize);
// Update this canvas (must be done after SetTargetCanvasSize)
EBUS_EVENT_ID(canvasEntityId, UiEditorCanvasBus, UpdateCanvasInEditorViewport, deltaTime, false);
}
void ViewportWidget::RenderEditMode()
{
// sort keys for different layers
static const int64_t backgroundKey = -0x1000;
static const int64_t topLayerKey = 0x1000000;
AZ::EntityId canvasEntityId = m_editorWindow->GetCanvas();
if (!canvasEntityId.IsValid())
{
return; // this can happen if a render happens during a restart
}
Draw2dHelper draw2d(m_draw2d.get()); // sets and resets 2D draw mode in constructor/destructor
QTreeWidgetItemRawPtrQList selection = m_editorWindow->GetHierarchy()->selectedItems();
@@ -936,9 +999,6 @@ void ViewportWidget::RenderEditMode(float deltaTime)
// Set the target size of the canvas
EBUS_EVENT_ID(canvasEntityId, UiCanvasBus, SetTargetCanvasSize, false, canvasSize);
// Update this canvas (must be done after SetTargetCanvasSize)
EBUS_EVENT_ID(canvasEntityId, UiEditorCanvasBus, UpdateCanvasInEditorViewport, deltaTime, false);
// Render this canvas
QSize scaledViewportSize = QtHelpers::GetDpiScaledViewportSize(*this);
AZ::Vector2 viewportSize(scaledViewportSize.width(), scaledViewportSize.height());
@@ -1037,11 +1097,8 @@ void ViewportWidget::RenderEditMode(float deltaTime)
}
}
void ViewportWidget::RenderPreviewMode(float deltaTime)
void ViewportWidget::UpdatePreviewMode(float deltaTime)
{
// sort keys for different layers
static const int64_t backgroundKey = -0x1000;
AZ::EntityId canvasEntityId = m_editorWindow->GetPreviewModeCanvas();
if (m_fontTextureHasChanged)
@@ -1051,6 +1108,37 @@ void ViewportWidget::RenderPreviewMode(float deltaTime)
m_fontTextureHasChanged = false;
}
if (canvasEntityId.IsValid())
{
QSize scaledViewportSize = QtHelpers::GetDpiScaledViewportSize(*this);
AZ::Vector2 viewportSize(scaledViewportSize.width(), scaledViewportSize.height());
// Get the canvas size
AZ::Vector2 canvasSize = m_editorWindow->GetPreviewCanvasSize();
if (canvasSize.GetX() == 0.0f && canvasSize.GetY() == 0.0f)
{
// special value of (0,0) means use the viewport size
canvasSize = viewportSize;
}
// Set the target size of the canvas
EBUS_EVENT_ID(canvasEntityId, UiCanvasBus, SetTargetCanvasSize, true, canvasSize);
// Update this canvas (must be done after SetTargetCanvasSize)
EBUS_EVENT_ID(canvasEntityId, UiEditorCanvasBus, UpdateCanvasInEditorViewport, deltaTime, true);
// Execute events that have been queued during the canvas update
gEnv->pLyShine->ExecuteQueuedEvents();
}
}
void ViewportWidget::RenderPreviewMode()
{
// sort keys for different layers
static const int64_t backgroundKey = -0x1000;
AZ::EntityId canvasEntityId = m_editorWindow->GetPreviewModeCanvas();
// Rather than scaling to exactly fit we try to draw at one of these preset scale factors
// to make it it bit more obvious that the canvas size is changing
float zoomScales[] = {
@@ -1096,15 +1184,6 @@ void ViewportWidget::RenderPreviewMode(float deltaTime)
}
}
// Set the target size of the canvas
EBUS_EVENT_ID(canvasEntityId, UiCanvasBus, SetTargetCanvasSize, true, canvasSize);
// Update this canvas (must be done after SetTargetCanvasSize)
EBUS_EVENT_ID(canvasEntityId, UiEditorCanvasBus, UpdateCanvasInEditorViewport, deltaTime, true);
// Execute events that have been queued during the canvas update
gEnv->pLyShine->ExecuteQueuedEvents();
// match scale to one of the predefined scales. If the scale is so small
// that it is less than the smallest scale then leave it as it is
for (int i = 0; i < AZ_ARRAY_SIZE(zoomScales); ++i)
@@ -1131,14 +1210,6 @@ void ViewportWidget::RenderPreviewMode(float deltaTime)
canvasToViewportMatrix.SetTranslation(translation);
EBUS_EVENT_ID(canvasEntityId, UiCanvasBus, SetCanvasToViewportMatrix, canvasToViewportMatrix);
#ifdef LYSHINE_ATOM_TODO // mask support with Atom
// clear the stencil buffer before rendering each canvas - required for masking
// NOTE: the FRT_CLEAR_IMMEDIATE is required since we will not be setting the render target
// We also clear the color to a mid grey so that we can see the bounds of the canvas
ColorF viewportBackgroundColor(0.5f, 0.5f, 0.5f, 0); // if clearing color we want to set alpha to zero also
gEnv->pRenderer->ClearTargetsImmediately(FRT_CLEAR, viewportBackgroundColor);
#endif
m_draw2d->SetSortKey(backgroundKey);
RenderViewportBackground();
+20 -2
View File
@@ -9,9 +9,11 @@
#if !defined(Q_MOC_RUN)
#include "EditorCommon.h"
#include "LyShinePassDataBus.h"
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
#include <AtomToolsFramework/Viewport/RenderViewportWidget.h>
#include <Atom/RPI.Public/ViewportContextBus.h>
#include <IFont.h>
@@ -27,6 +29,8 @@ class ViewportWidget
: public AtomToolsFramework::RenderViewportWidget
, private AzToolsFramework::EditorPickModeNotificationBus::Handler
, private FontNotificationBus::Handler
, private LyShinePassDataRequestBus::Handler
, public AZ::RPI::ViewportContextNotificationBus::Handler
{
Q_OBJECT
@@ -138,15 +142,29 @@ private: // member functions
void OnFontTextureUpdated(IFFont* font) override;
// ~FontNotifications
// LyShinePassDataRequestBus
LyShine::AttachmentImagesAndDependencies GetRenderTargets() override;
// ~LyShinePassDataRequestBus
// AZ::TickBus::Handler
void OnTick(float deltaTime, AZ::ScriptTimePoint time) override;
int GetTickOrder() override;
// ~AZ::TickBus::Handler
// AZ::RPI::ViewportContextNotificationBus::Handler overrides...
void OnRenderTick() override;
//! Update UI canvases when in edit mode
void UpdateEditMode(float deltaTime);
//! Render the viewport when in edit mode
void RenderEditMode(float deltaTime);
void RenderEditMode();
//! Update UI canvases when in preview mode
void UpdatePreviewMode(float deltaTime);
//! Render the viewport when in preview mode
void RenderPreviewMode(float deltaTime);
void RenderPreviewMode();
//! Fill the entire viewport area with a background color
void RenderViewportBackground();
+17 -1
View File
@@ -9,6 +9,7 @@
#include <IRenderer.h> // for SVF_P3F_C4B_T2F which will be removed in a coming PR
#include <LyShine/Draw2d.h>
#include "LyShinePassDataBus.h"
#include <AzCore/Math/Matrix3x3.h>
#include <AzCore/Math/MatrixUtils.h>
@@ -95,6 +96,12 @@ void CDraw2d::OnBootstrapSceneReady([[maybe_unused]] AZ::RPI::Scene* bootstrapSc
AZ_Assert(scene != nullptr, "Attempting to create a DynamicDrawContext for a viewport context that has not been associated with a scene yet.");
// Create and initialize a DynamicDrawContext for 2d drawing
// Get the pass for the dynamic draw context to render to
AZ::RPI::RasterPass* uiCanvasPass = nullptr;
AZ::RPI::SceneId sceneId = scene->GetId();
LyShinePassRequestBus::EventResult(uiCanvasPass, sceneId, &LyShinePassRequestBus::Events::GetUiCanvasPass);
m_dynamicDraw = AZ::RPI::DynamicDrawInterface::Get()->CreateDynamicDrawContext();
AZ::RPI::ShaderOptionList shaderOptions;
shaderOptions.push_back(AZ::RPI::ShaderOption(AZ::Name("o_useColorChannels"), AZ::Name("true")));
@@ -106,7 +113,15 @@ void CDraw2d::OnBootstrapSceneReady([[maybe_unused]] AZ::RPI::Scene* bootstrapSc
{"TEXCOORD0", AZ::RHI::Format::R32G32_FLOAT} });
m_dynamicDraw->AddDrawStateOptions(AZ::RPI::DynamicDrawContext::DrawStateOptions::PrimitiveType
| AZ::RPI::DynamicDrawContext::DrawStateOptions::BlendMode);
m_dynamicDraw->SetOutputScope(scene.get());
if (uiCanvasPass)
{
m_dynamicDraw->SetOutputScope(uiCanvasPass);
}
else
{
// Render target support is disabled
m_dynamicDraw->SetOutputScope(scene.get());
}
m_dynamicDraw->EndInit();
AZ::RHI::TargetBlendState targetBlendState;
@@ -491,6 +506,7 @@ bool CDraw2d::GetDeferPrimitives()
return m_deferCalls;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void CDraw2d::SetSortKey(int64_t key)
{
m_dynamicDraw->SetSortKey(key);
+22 -4
View File
@@ -163,6 +163,8 @@ CLyShine::CLyShine(ISystem* system)
AzFramework::InputTextEventListener::Connect();
UiCursorBus::Handler::BusConnect();
AZ::TickBus::Handler::BusConnect();
AZ::RPI::ViewportContextNotificationBus::Handler::BusConnect(
AZ::RPI::ViewportContextRequests::Get()->GetDefaultViewportContextName());
AZ::Render::Bootstrap::NotificationBus::Handler::BusConnect();
// These are internal Amazon components, so register them so that we can send back their names to our metrics collection
@@ -240,9 +242,11 @@ CLyShine::~CLyShine()
{
UiCursorBus::Handler::BusDisconnect();
AZ::TickBus::Handler::BusDisconnect();
AZ::RPI::ViewportContextNotificationBus::Handler::BusDisconnect();
AzFramework::InputTextEventListener::Disconnect();
AzFramework::InputChannelEventListener::Disconnect();
AZ::Render::Bootstrap::NotificationBus::Handler::BusDisconnect();
LyShinePassDataRequestBus::Handler::BusDisconnect();
UiCanvasComponent::Shutdown();
@@ -642,15 +646,19 @@ void CLyShine::OnTick(float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint time
{
// Update the loaded UI canvases
Update(deltaTime);
// Recreate dirty render graphs and send primitive data to the dynamic draw context
Render();
}
////////////////////////////////////////////////////////////////////////////////////////////////////
int CLyShine::GetTickOrder()
{
return AZ::TICK_UI;
return AZ::TICK_PRE_RENDER;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void CLyShine::OnRenderTick()
{
// Recreate dirty render graphs and send primitive data to the dynamic draw context
Render();
}
////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -658,6 +666,16 @@ void CLyShine::OnBootstrapSceneReady([[maybe_unused]] AZ::RPI::Scene* bootstrapS
{
// Load cursor if its path was set before RPI was initialized
LoadUiCursor();
LyShinePassDataRequestBus::Handler::BusConnect(AZ::RPI::RPISystemInterface::Get()->GetDefaultScene()->GetId());
}
////////////////////////////////////////////////////////////////////////////////////////////////////
LyShine::AttachmentImagesAndDependencies CLyShine::GetRenderTargets()
{
LyShine::AttachmentImagesAndDependencies attachmentImagesAndDependencies;
m_uiCanvasManager->GetRenderTargets(attachmentImagesAndDependencies);
return attachmentImagesAndDependencies;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
+12
View File
@@ -16,8 +16,11 @@
#include <AzFramework/Input/Events/InputTextEventListener.h>
#include <Atom/Bootstrap/BootstrapNotificationBus.h>
#include <Atom/RPI.Public/ViewportContextBus.h>
#include <Atom/RPI.Reflect/Image/Image.h>
#include "LyShinePassDataBus.h"
#if !defined(_RELEASE)
#define LYSHINE_INTERNAL_UNIT_TEST
#endif
@@ -40,7 +43,9 @@ class CLyShine
, public AzFramework::InputChannelEventListener
, public AzFramework::InputTextEventListener
, public AZ::TickBus::Handler
, public AZ::RPI::ViewportContextNotificationBus::Handler
, protected AZ::Render::Bootstrap::NotificationBus::Handler
, protected LyShinePassDataRequestBus::Handler
{
public:
@@ -111,10 +116,17 @@ public:
int GetTickOrder() override;
// ~TickEvents
// AZ::RPI::ViewportContextNotificationBus::Handler overrides...
void OnRenderTick() override;
// AZ::Render::Bootstrap::NotificationBus
void OnBootstrapSceneReady(AZ::RPI::Scene* bootstrapScene) override;
// ~AZ::Render::Bootstrap::NotificationBus
// LyShinePassDataRequestBus
LyShine::AttachmentImagesAndDependencies GetRenderTargets() override;
// ~LyShinePassDataRequestBus
// Get the UIRenderer for the game (which is owned by CLyShine). This is not exposed outside the gem.
UiRenderer* GetUiRenderer();
+274
View File
@@ -0,0 +1,274 @@
/*
* 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/Debug/Trace.h>
#include <Atom/RHI/DrawListTagRegistry.h>
#include <Atom/RHI/RHISystemInterface.h>
#include <Atom/RPI.Public/Pass/PassAttachment.h>
#include <Atom/RPI.Public/Image/ImageSystemInterface.h>
#include <Atom/RPI.Public/Image/AttachmentImagePool.h>
#include <Atom/RPI.Public/Scene.h>
#include <Atom/RPI.Reflect/Pass/RasterPassData.h>
#include <AzCore/std/iterator.h>
#include "LyShinePass.h"
namespace LyShine
{
AZ::RPI::Ptr<LyShinePass> LyShinePass::Create(const AZ::RPI::PassDescriptor& descriptor)
{
return aznew LyShinePass(descriptor);
}
LyShinePass::LyShinePass(const AZ::RPI::PassDescriptor& descriptor)
: Base(descriptor)
{
}
LyShinePass::~LyShinePass()
{
LyShinePassRequestBus::Handler::BusDisconnect();
}
void LyShinePass::ResetInternal()
{
LyShinePassRequestBus::Handler::BusDisconnect();
Base::ResetInternal();
}
void LyShinePass::BuildInternal()
{
AZ::RPI::Scene* scene = GetScene();
if (scene)
{
// Listen for rebuild requests
LyShinePassRequestBus::Handler::BusConnect(scene->GetId());
RemoveChildren();
// Get the current list of render targets being used across all loaded UI Canvases
LyShine::AttachmentImagesAndDependencies attachmentImagesAndDependencies;
LyShinePassDataRequestBus::EventResult(
attachmentImagesAndDependencies,
scene->GetId(),
&LyShinePassDataRequestBus::Events::GetRenderTargets
);
AddRttChildPasses(attachmentImagesAndDependencies);
AddUiCanvasChildPass(attachmentImagesAndDependencies);
}
Base::BuildInternal();
}
void LyShinePass::RebuildRttChildren()
{
QueueForBuildAndInitialization();
}
AZ::RPI::RasterPass* LyShinePass::GetRttPass(const AZStd::string& name)
{
for (auto child:m_children)
{
if (child->GetName() == AZ::Name(name))
{
return azrtti_cast<AZ::RPI::RasterPass*>(child.get());
}
}
return nullptr;
}
AZ::RPI::RasterPass* LyShinePass::GetUiCanvasPass()
{
return m_uiCanvasChildPass.get();
}
void LyShinePass::AddRttChildPasses(LyShine::AttachmentImagesAndDependencies attachmentImagesAndDependencies)
{
for (const auto& attachmentImageAndDependencies : attachmentImagesAndDependencies)
{
AddRttChildPass(attachmentImageAndDependencies.first, attachmentImageAndDependencies.second);
}
}
void LyShinePass::AddRttChildPass(AZ::Data::Instance<AZ::RPI::AttachmentImage> attachmentImage, AttachmentImages attachmentImageDependencies)
{
// Add a pass that renders to the specified texture
// Create a pass template
auto passTemplate = AZStd::make_shared<AZ::RPI::PassTemplate>();
passTemplate->m_name = "RttChildPass";
passTemplate->m_passClass = AZ::Name("RttChildPass");
// Slots
passTemplate->m_slots.resize(2);
AZ::RPI::PassSlot& depthInOutSlot = passTemplate->m_slots[0];
depthInOutSlot.m_name = "DepthInputOutput";
depthInOutSlot.m_slotType = AZ::RPI::PassSlotType::InputOutput;
depthInOutSlot.m_scopeAttachmentUsage = AZ::RHI::ScopeAttachmentUsage::DepthStencil;
depthInOutSlot.m_loadStoreAction.m_clearValue = AZ::RHI::ClearValue::CreateDepthStencil(0.0f, 0);
depthInOutSlot.m_loadStoreAction.m_loadActionStencil = AZ::RHI::AttachmentLoadAction::Clear;
AZ::RPI::PassSlot& outSlot = passTemplate->m_slots[1];
outSlot.m_name = AZ::Name("RenderTargetOutput");
outSlot.m_slotType = AZ::RPI::PassSlotType::Output;
outSlot.m_scopeAttachmentUsage = AZ::RHI::ScopeAttachmentUsage::RenderTarget;
outSlot.m_loadStoreAction.m_clearValue = AZ::RHI::ClearValue::CreateVector4Float(0.0f, 0.0f, 0.0f, 0.0f);
outSlot.m_loadStoreAction.m_loadAction = AZ::RHI::AttachmentLoadAction::Clear;
// Connections
passTemplate->m_connections.resize(1);
AZ::RPI::PassConnection& depthInOutConnection = passTemplate->m_connections[0];
depthInOutConnection.m_localSlot = "DepthInputOutput";
depthInOutConnection.m_attachmentRef.m_pass = "Parent";
depthInOutConnection.m_attachmentRef.m_attachment = "DepthInputOutput";
// Pass data
AZStd::shared_ptr<AZ::RPI::RasterPassData> passData = AZStd::make_shared<AZ::RPI::RasterPassData>();
passData->m_drawListTag = AZ::Name("uicanvas");
passData->m_pipelineViewTag = AZ::Name("MainCamera");
auto size = attachmentImage->GetRHIImage()->GetDescriptor().m_size;
passData->m_overrideScissor = AZ::RHI::Scissor(0, 0, size.m_width, size.m_height);
passData->m_overrideViewport = AZ::RHI::Viewport(0, size.m_width, 0, size.m_height);
passTemplate->m_passData = AZStd::move(passData);
// Create a pass descriptor for the new child pass
AZ::RPI::PassDescriptor childDesc;
childDesc.m_passTemplate = passTemplate;
childDesc.m_passName = attachmentImage->GetAttachmentId();
AZ::RPI::PassSystemInterface* passSystem = AZ::RPI::PassSystemInterface::Get();
AZ::RPI::Ptr<RttChildPass> rttChildPass = passSystem->CreatePass<RttChildPass>(childDesc);
AZ_Assert(rttChildPass, "[LyShinePass] Unable to create %s.", passTemplate->m_name.GetCStr());
// Store the info needed to attach to slots and set up frame graph dependencies
rttChildPass->m_attachmentImage = attachmentImage;
rttChildPass->m_attachmentImageDependencies = attachmentImageDependencies;
AddChild(rttChildPass);
}
void LyShinePass::AddUiCanvasChildPass(LyShine::AttachmentImagesAndDependencies AttachmentImagesAndDependencies)
{
if (!m_uiCanvasChildPass)
{
// Create a pass template
auto passTemplate = AZStd::make_shared<AZ::RPI::PassTemplate>();
passTemplate->m_name = AZ::Name("LyShineChildPass");
passTemplate->m_passClass = AZ::Name("LyShineChildPass");
// Slots
passTemplate->m_slots.resize(2);
AZ::RPI::PassSlot& depthInOutSlot = passTemplate->m_slots[0];
depthInOutSlot.m_name = "DepthInputOutput";
depthInOutSlot.m_slotType = AZ::RPI::PassSlotType::InputOutput;
depthInOutSlot.m_scopeAttachmentUsage = AZ::RHI::ScopeAttachmentUsage::DepthStencil;
depthInOutSlot.m_loadStoreAction.m_clearValue = AZ::RHI::ClearValue::CreateDepthStencil(0.0f, 0);
depthInOutSlot.m_loadStoreAction.m_loadActionStencil = AZ::RHI::AttachmentLoadAction::Clear;
AZ::RPI::PassSlot& inOutSlot = passTemplate->m_slots[1];
inOutSlot.m_name = "ColorInputOutput";
inOutSlot.m_slotType = AZ::RPI::PassSlotType::InputOutput;
inOutSlot.m_scopeAttachmentUsage = AZ::RHI::ScopeAttachmentUsage::RenderTarget;
// Connections
passTemplate->m_connections.resize(2);
AZ::RPI::PassConnection& depthInOutConnection = passTemplate->m_connections[0];
depthInOutConnection.m_localSlot = "DepthInputOutput";
depthInOutConnection.m_attachmentRef.m_pass = "Parent";
depthInOutConnection.m_attachmentRef.m_attachment = "DepthInputOutput";
AZ::RPI::PassConnection& inOutConnection = passTemplate->m_connections[1];
inOutConnection.m_localSlot = "ColorInputOutput";
inOutConnection.m_attachmentRef.m_pass = "Parent";
inOutConnection.m_attachmentRef.m_attachment = "ColorInputOutput";
// Pass data
AZStd::shared_ptr<AZ::RPI::RasterPassData> passData = AZStd::make_shared<AZ::RPI::RasterPassData>();
passData->m_drawListTag = AZ::Name("uicanvas");
passData->m_pipelineViewTag = AZ::Name("MainCamera");
passTemplate->m_passData = AZStd::move(passData);
// Create a pass descriptor for the new child pass
AZ::RPI::PassDescriptor childDesc;
childDesc.m_passTemplate = passTemplate;
childDesc.m_passName = AZ::Name("LyShineChildPass");
AZ::RPI::PassSystemInterface* passSystem = AZ::RPI::PassSystemInterface::Get();
m_uiCanvasChildPass = passSystem->CreatePass<LyShineChildPass>(childDesc);
AZ_Assert(m_uiCanvasChildPass, "[LyShinePass] Unable to create %s.", passTemplate->m_name.GetCStr());
}
// Store the info needed to set up frame graph dependencies
m_uiCanvasChildPass->m_attachmentImageDependencies.clear();
for (const auto& attachmentImageAndDescendents : AttachmentImagesAndDependencies)
{
m_uiCanvasChildPass->m_attachmentImageDependencies.emplace_back(attachmentImageAndDescendents.first);
}
AddChild(m_uiCanvasChildPass);
}
AZ::RPI::Ptr<LyShineChildPass> LyShineChildPass::Create(const AZ::RPI::PassDescriptor& descriptor)
{
return aznew LyShineChildPass(descriptor);
}
LyShineChildPass::LyShineChildPass(const AZ::RPI::PassDescriptor& descriptor)
: RasterPass(descriptor)
{
}
LyShineChildPass::~LyShineChildPass()
{
}
void LyShineChildPass::SetupFrameGraphDependencies(AZ::RHI::FrameGraphInterface frameGraph)
{
AZ::RPI::RasterPass::SetupFrameGraphDependencies(frameGraph);
for (auto attachmentImage : m_attachmentImageDependencies)
{
// Ensure that the image is imported into the attachment database.
// The image may not be imported if the owning pass has been disabled.
auto attachmentImageId = attachmentImage->GetAttachmentId();
if (!frameGraph.GetAttachmentDatabase().IsAttachmentValid(attachmentImageId))
{
frameGraph.GetAttachmentDatabase().ImportImage(attachmentImageId, attachmentImage->GetRHIImage());
}
AZ::RHI::ImageScopeAttachmentDescriptor desc;
desc.m_attachmentId = attachmentImageId;
desc.m_imageViewDescriptor = attachmentImage->GetImageView()->GetDescriptor();
desc.m_loadStoreAction.m_loadAction = AZ::RHI::AttachmentLoadAction::Load;
frameGraph.UseShaderAttachment(desc, AZ::RHI::ScopeAttachmentAccess::Read);
}
}
AZ::RPI::Ptr<RttChildPass> RttChildPass::Create(const AZ::RPI::PassDescriptor& descriptor)
{
return aznew RttChildPass(descriptor);
}
RttChildPass::RttChildPass(const AZ::RPI::PassDescriptor& descriptor)
: LyShineChildPass(descriptor)
{
}
RttChildPass::~RttChildPass()
{
}
void RttChildPass::BuildInternal()
{
AttachImageToSlot(AZ::Name("RenderTargetOutput"), m_attachmentImage);
}
} // namespace LyShine
+110
View File
@@ -0,0 +1,110 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <Atom/RPI.Public/Pass/ParentPass.h>
#include <Atom/RPI.Public/Pass/RasterPass.h>
#include <AtomCore/std/containers/array_view.h>
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/std/containers/vector.h>
#include "LyShinePassDataBus.h"
namespace LyShine
{
class LyShineChildPass;
//! Manages child passes at runtime that render to render targets
class LyShinePass final
: public AZ::RPI::ParentPass
, protected LyShinePassRequestBus::Handler
{
AZ_RPI_PASS(LyShinePass);
using Base = AZ::RPI::ParentPass;
public:
AZ_CLASS_ALLOCATOR(LyShinePass, AZ::SystemAllocator, 0);
AZ_RTTI(LyShinePass, "C3B812ED-3771-42F4-A96F-EBD94B4D54CA", Base);
virtual ~LyShinePass();
static AZ::RPI::Ptr<LyShinePass> Create(const AZ::RPI::PassDescriptor& descriptor);
protected:
// Pass behavior overrides
void ResetInternal() override;
void BuildInternal() override;
// LyShinePassRequestBus overrides
void RebuildRttChildren() override;
AZ::RPI::RasterPass* GetRttPass(const AZStd::string& name) override;
AZ::RPI::RasterPass* GetUiCanvasPass() override;
private:
LyShinePass() = delete;
explicit LyShinePass(const AZ::RPI::PassDescriptor& descriptor);
// Build the render to texture child passes
void AddRttChildPasses(LyShine::AttachmentImagesAndDependencies AttachmentImagesAndDependencies);
// Add a render to texture child pass
void AddRttChildPass(AZ::Data::Instance<AZ::RPI::AttachmentImage> attachmentImage, AttachmentImages dependentAttachmentImages);
// Append the final pass to render UI Canvas elements to the screen
void AddUiCanvasChildPass(LyShine::AttachmentImagesAndDependencies AttachmentImagesAndDependencies);
// Pass that renders the UI Canvas elements to the screen
AZ::RPI::Ptr<LyShineChildPass> m_uiCanvasChildPass;
};
// Child pass with potential attachment dependencies
class LyShineChildPass
: public AZ::RPI::RasterPass
{
AZ_RPI_PASS(LyShineChildPass);
friend class LyShinePass;
public:
AZ_RTTI(LyShineChildPass, "{41D525F9-09EB-4004-97DC-082078FF8DD2}", RasterPass);
AZ_CLASS_ALLOCATOR(LyShineChildPass, AZ::SystemAllocator, 0);
virtual ~LyShineChildPass();
//! Creates a LyShineChildPass
static AZ::RPI::Ptr<LyShineChildPass> Create(const AZ::RPI::PassDescriptor& descriptor);
protected:
LyShineChildPass(const AZ::RPI::PassDescriptor& descriptor);
// Scope producer Overrides...
void SetupFrameGraphDependencies(AZ::RHI::FrameGraphInterface frameGraph) override;
AttachmentImages m_attachmentImageDependencies;
};
// Child pass that renders UI elements to a render target
class RttChildPass
: public LyShineChildPass
{
AZ_RPI_PASS(RttChildPass);
friend class LyShinePass;
public:
AZ_RTTI(RttChildPass, "{54B0574D-2EB3-4054-9E1D-0E0D9C8CB09A}", LyShineChildPass);
AZ_CLASS_ALLOCATOR(RttChildPass, AZ::SystemAllocator, 0);
virtual ~RttChildPass();
//! Creates a RttChildPass
static AZ::RPI::Ptr<RttChildPass> Create(const AZ::RPI::PassDescriptor& descriptor);
protected:
RttChildPass(const AZ::RPI::PassDescriptor& descriptor);
// Pass behavior overrides
void BuildInternal() override;
AZ::Data::Instance<AZ::RPI::AttachmentImage> m_attachmentImage;
};
} // namespace LyShine
@@ -0,0 +1,61 @@
/*
* 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/Memory/SystemAllocator.h>
#include <AzCore/std/containers/vector.h>
#include <AtomCore/Instance/Instance.h>
#include <Atom/RPI.Public/Base.h>
namespace AZ
{
namespace RPI
{
class AttachmentImage;
class RasterPass;
}
}
namespace LyShine
{
using AttachmentImages = AZStd::vector<AZ::Data::Instance<AZ::RPI::AttachmentImage>>;
using AttachmentImageAndDependentsPair = AZStd::pair<AZ::Data::Instance<AZ::RPI::AttachmentImage>, AttachmentImages>;
using AttachmentImagesAndDependencies = AZStd::vector<AttachmentImageAndDependentsPair>;
}
class LyShinePassRequests
: public AZ::EBusTraits
{
public:
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById;
using BusIdType = AZ::RPI::SceneId;
//! Called when the number of render targets has changed and the LyShine pass needs to rebuild
virtual void RebuildRttChildren() = 0;
//! Returns a render to texture pass based on render target name
virtual AZ::RPI::RasterPass* GetRttPass(const AZStd::string& name) = 0;
//! Returns the final pass that renders the UI canvas contents
virtual AZ::RPI::RasterPass* GetUiCanvasPass() = 0;
};
using LyShinePassRequestBus = AZ::EBus<LyShinePassRequests>;
class LyShinePassDataRequests
: public AZ::EBusTraits
{
public:
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById;
using BusIdType = AZ::RPI::SceneId;
//! Get a list of render targets that require a render to texture pass, and any
//! other render targets that are drawn on them
virtual LyShine::AttachmentImagesAndDependencies GetRenderTargets() = 0;
};
using LyShinePassDataRequestBus = AZ::EBus<LyShinePassDataRequests>;
@@ -49,6 +49,7 @@
#include "UiDynamicLayoutComponent.h"
#include "UiDynamicScrollBoxComponent.h"
#include "UiNavigationSettings.h"
#include "LyShinePass.h"
namespace LyShine
{
@@ -113,9 +114,11 @@ namespace LyShine
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void LyShineSystemComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required)
void LyShineSystemComponent::GetRequiredServices([[maybe_unused]] AZ::ComponentDescriptor::DependencyArrayType& required)
{
(void)required;
#if !defined(LYSHINE_BUILDER) && !defined(LYSHINE_TESTS)
required.push_back(AZ_CRC("RPISystem", 0xf2add773));
#endif
}
////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -186,6 +189,17 @@ namespace LyShine
RegisterComponentTypeForMenuOrdering(UiDynamicScrollBoxComponent::RTTI_Type());
RegisterComponentTypeForMenuOrdering(UiParticleEmitterComponent::RTTI_Type());
RegisterComponentTypeForMenuOrdering(UiFlipbookAnimationComponent::RTTI_Type());
#if !defined(LYSHINE_BUILDER) && !defined(LYSHINE_TESTS)
// Add LyShine pass
auto* passSystem = AZ::RPI::PassSystemInterface::Get();
AZ_Assert(passSystem, "Cannot get the pass system.");
passSystem->AddPassCreator(AZ::Name("LyShinePass"), &LyShine::LyShinePass::Create);
// Setup handler for load pass template mappings
m_loadTemplatesHandler = AZ::RPI::PassSystemInterface::OnReadyLoadTemplatesEvent::Handler([this]() { this->LoadPassTemplateMappings(); });
AZ::RPI::PassSystemInterface::Get()->ConnectEvent(m_loadTemplatesHandler);
#endif
}
////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -386,4 +400,13 @@ namespace LyShine
{
UiCursorBus::Broadcast(&UiCursorInterface::SetUiCursor, m_cursorImagePathname.GetAssetPath().c_str());
}
#if !defined(LYSHINE_BUILDER) && !defined(LYSHINE_TESTS)
////////////////////////////////////////////////////////////////////////////////////////////////////
void LyShineSystemComponent::LoadPassTemplateMappings()
{
const char* passTemplatesFile = "Passes/LyShinePassTemplates.azasset";
AZ::RPI::PassSystemInterface::Get()->LoadPassTemplateMappings(passTemplatesFile);
}
#endif
}
@@ -20,6 +20,10 @@
#include <LyShine/UiComponentTypes.h>
#include "LyShine.h"
#if !defined(LYSHINE_BUILDER) && !defined(LYSHINE_TESTS)
#include <Atom/RPI.Public/Pass/PassSystemInterface.h>
#endif
namespace LyShine
{
// LyShine depends on the LegacyAllocator and CryStringAllocator. This will be managed
@@ -90,6 +94,11 @@ namespace LyShine
void BroadcastCursorImagePathname();
#if !defined(LYSHINE_BUILDER) && !defined(LYSHINE_TESTS)
// Load pass template mappings for this gem
void LoadPassTemplateMappings();
#endif
protected: // data
CLyShine* m_pLyShine = nullptr;
@@ -102,5 +111,9 @@ namespace LyShine
// We only store this in order to generate metrics on LyShine specific components
static const AZStd::list<AZ::ComponentDescriptor*>* m_componentDescriptors;
#if !defined(LYSHINE_BUILDER) && !defined(LYSHINE_TESTS)
AZ::RPI::PassSystemInterface::OnReadyLoadTemplatesEvent::Handler m_loadTemplatesHandler;
#endif
};
}
+172 -158
View File
@@ -10,6 +10,9 @@
#include "UiRenderer.h"
#include <Atom/RPI.Public/Image/ImageSystemInterface.h>
#include <Atom/RHI/RHISystemInterface.h>
#include <AzCore/Math/MatrixUtils.h>
#ifndef _RELEASE
#include <AzCore/Asset/AssetManagerBus.h>
@@ -78,57 +81,28 @@ namespace LyShine
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void PrimitiveListRenderNode::Render(UiRenderer* uiRenderer)
void PrimitiveListRenderNode::Render(UiRenderer* uiRenderer
, const AZ::Matrix4x4& modelViewProjMat
, AZ::RHI::Ptr<AZ::RPI::DynamicDrawContext> dynamicDraw)
{
#ifdef LYSHINE_ATOM_TODO // keeping this code for reference for future phase (masks/render targets)
for (int i = 0; i < m_numTextures; ++i)
{
uiRenderer->SetTexture(m_textures[i].m_texture, i, m_textures[i].m_isClampTextureMode);
}
int blendModeState = m_blendModeState;
IRenderer* renderer = gEnv->pRenderer;
renderer->SetState(blendModeState | uiRenderer->GetBaseState());
if (m_isTextureSRGB)
{
renderer->SetSrgbWrite(false);
}
// We are using SetColorOp as a way to set flags for the ui.cfx shader by reusing flags
// that the FixedPipelineEmu.cfx shader uses. So the names colorOp and alphaOp are used
// just because this are the inputs to SetColorOp.
uint8 colorOp = m_preMultiplyAlpha ? ColorOp_PreMultiplyAlpha : ColorOp_Normal;
uint8 alphaOp = AlphaOp_Normal;
switch (m_alphaMaskType)
{
case AlphaMaskType::None:
alphaOp = AlphaOp_Normal;
break;
case AlphaMaskType::ModulateAlpha:
alphaOp = AlphaOp_ModulateAlpha;
break;
case AlphaMaskType::ModulateAlphaAndColor:
alphaOp = AlphaOp_ModulateAlphaAndColor;
break;
}
renderer->SetColorOp(colorOp, alphaOp, DEF_TEXARG0, DEF_TEXARG0);
renderer->DrawDynUiPrimitiveList(m_primitives, m_totalNumVertices, m_totalNumIndices);
if (m_isTextureSRGB)
{
renderer->SetSrgbWrite(true);
}
#endif
if (!uiRenderer->IsReady())
{
return;
}
AZ::RHI::Ptr<AZ::RPI::DynamicDrawContext> dynamicDraw = uiRenderer->GetDynamicDrawContext();
UiRenderer::BaseState curBaseState = uiRenderer->GetBaseState();
UiRenderer::BaseState prevBaseState = curBaseState;
if (m_isTextureSRGB)
{
curBaseState.m_srgbWrite = false;
}
if (m_alphaMaskType == AlphaMaskType::ModulateAlpha)
{
curBaseState.m_modulateAlpha = true;
}
uiRenderer->SetBaseState(curBaseState);
const UiRenderer::UiShaderData& uiShaderData = uiRenderer->GetUiShaderData();
// Set render state
@@ -167,7 +141,7 @@ namespace LyShine
drawSrg->SetConstant(uiShaderData.m_isClampInputIndex, isClampTextureMode);
// Set projection matrix
drawSrg->SetConstant(uiShaderData.m_viewProjInputIndex, uiRenderer->GetModelViewProjectionMatrix());
drawSrg->SetConstant(uiShaderData.m_viewProjInputIndex, modelViewProjMat);
drawSrg->Compile();
@@ -180,6 +154,8 @@ namespace LyShine
{
dynamicDraw->DrawIndexed(primitive.m_vertices, primitive.m_numVertices, primitive.m_indices, primitive.m_numIndices, AZ::RHI::IndexFormat::Uint16, drawSrg);
}
uiRenderer->SetBaseState(prevBaseState);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -303,33 +279,35 @@ namespace LyShine
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void MaskRenderNode::Render(UiRenderer* uiRenderer)
void MaskRenderNode::Render(UiRenderer* uiRenderer
, const AZ::Matrix4x4& modelViewProjMat
, AZ::RHI::Ptr<AZ::RPI::DynamicDrawContext> dynamicDraw)
{
UiRenderer::BaseState priorBaseState = uiRenderer->GetBaseState();
if (m_isMaskingEnabled || m_drawBehind)
{
SetupBeforeRenderingMask(uiRenderer, true, priorBaseState);
SetupBeforeRenderingMask(uiRenderer, dynamicDraw, true, priorBaseState);
for (RenderNode* renderNode : m_maskRenderNodes)
{
renderNode->Render(uiRenderer);
renderNode->Render(uiRenderer, modelViewProjMat, dynamicDraw);
}
SetupAfterRenderingMask(uiRenderer, true, priorBaseState);
SetupAfterRenderingMask(uiRenderer, dynamicDraw, true, priorBaseState);
}
for (RenderNode* renderNode : m_contentRenderNodes)
{
renderNode->Render(uiRenderer);
renderNode->Render(uiRenderer, modelViewProjMat, dynamicDraw);
}
if (m_isMaskingEnabled || m_drawInFront)
{
SetupBeforeRenderingMask(uiRenderer, false, priorBaseState);
SetupBeforeRenderingMask(uiRenderer, dynamicDraw, false, priorBaseState);
for (RenderNode* renderNode : m_maskRenderNodes)
{
renderNode->Render(uiRenderer);
renderNode->Render(uiRenderer, modelViewProjMat, dynamicDraw);
}
SetupAfterRenderingMask(uiRenderer, false, priorBaseState);
SetupAfterRenderingMask(uiRenderer, dynamicDraw, false, priorBaseState);
}
}
@@ -367,7 +345,9 @@ namespace LyShine
#endif
////////////////////////////////////////////////////////////////////////////////////////////////////
void MaskRenderNode::SetupBeforeRenderingMask(UiRenderer* uiRenderer, bool firstPass, UiRenderer::BaseState priorBaseState)
void MaskRenderNode::SetupBeforeRenderingMask(UiRenderer* uiRenderer,
AZ::RHI::Ptr<AZ::RPI::DynamicDrawContext> dynamicDraw,
bool firstPass, UiRenderer::BaseState priorBaseState)
{
UiRenderer::BaseState curBaseState = priorBaseState;
@@ -406,7 +386,6 @@ namespace LyShine
curBaseState.m_stencilState.m_backFace = stencilOpState;
// set up for stencil write
AZ::RHI::Ptr<AZ::RPI::DynamicDrawContext> dynamicDraw = uiRenderer->GetDynamicDrawContext();
dynamicDraw->SetStencilReference(uiRenderer->GetStencilRef());
curBaseState.m_stencilState.m_enable = true;
curBaseState.m_stencilState.m_writeMask = 0xFF;
@@ -421,7 +400,9 @@ namespace LyShine
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void MaskRenderNode::SetupAfterRenderingMask(UiRenderer* uiRenderer, bool firstPass, UiRenderer::BaseState priorBaseState)
void MaskRenderNode::SetupAfterRenderingMask(UiRenderer* uiRenderer,
AZ::RHI::Ptr<AZ::RPI::DynamicDrawContext> dynamicDraw,
bool firstPass, UiRenderer::BaseState priorBaseState)
{
if (m_isMaskingEnabled)
{
@@ -439,7 +420,6 @@ namespace LyShine
uiRenderer->DecrementStencilRef();
}
AZ::RHI::Ptr<AZ::RPI::DynamicDrawContext> dynamicDraw = uiRenderer->GetDynamicDrawContext();
dynamicDraw->SetStencilReference(uiRenderer->GetStencilRef());
if (firstPass)
@@ -474,16 +454,14 @@ namespace LyShine
////////////////////////////////////////////////////////////////////////////////////////////////////
RenderTargetRenderNode::RenderTargetRenderNode(
RenderTargetRenderNode* parentRenderTarget,
int renderTargetHandle,
SDepthTexture* renderTargetDepthSurface,
AZ::Data::Instance<AZ::RPI::AttachmentImage> attachmentImage,
const AZ::Vector2& viewportTopLeft,
const AZ::Vector2& viewportSize,
const AZ::Color& clearColor,
int nestLevel)
: RenderNode(RenderNodeType::RenderTarget)
, m_parentRenderTarget(parentRenderTarget)
, m_renderTargetHandle(renderTargetHandle)
, m_renderTargetDepthSurface(renderTargetDepthSurface)
, m_attachmentImage(attachmentImage)
, m_viewportX(viewportTopLeft.GetX())
, m_viewportY(viewportTopLeft.GetY())
, m_viewportWidth(viewportSize.GetX())
@@ -491,6 +469,13 @@ namespace LyShine
, m_clearColor(clearColor)
, m_nestLevel(nestLevel)
{
AZ::MakeOrthographicMatrixRH(m_modelViewProjMat,
m_viewportX,
m_viewportX + m_viewportWidth,
m_viewportY + m_viewportHeight,
m_viewportY,
0.0f,
1.0f);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -505,9 +490,11 @@ namespace LyShine
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void RenderTargetRenderNode::Render(UiRenderer* uiRenderer)
void RenderTargetRenderNode::Render(UiRenderer* uiRenderer
, [[maybe_unused]] const AZ::Matrix4x4& modelViewProjMat
, [[maybe_unused]] AZ::RHI::Ptr<AZ::RPI::DynamicDrawContext> dynamicDraw)
{
if (m_renderTargetHandle <= 0)
if (!m_attachmentImage)
{
return;
}
@@ -515,39 +502,52 @@ namespace LyShine
ISystem* system = gEnv->pSystem;
if (system && !gEnv->IsDedicated())
{
TransformationMatrices backupMatrices;
gEnv->pRenderer->Set2DModeNonZeroTopLeft(m_viewportX, m_viewportY, m_viewportWidth, m_viewportHeight, backupMatrices);
// this will change the viewport
gEnv->pRenderer->SetRenderTarget(m_renderTargetHandle, m_renderTargetDepthSurface);
// clear the render target before rendering to it
// NOTE: the FRT_CLEAR_IMMEDIATE is required since we will have already set the render target
// In theory we could call this before setting the render target without the immediate flag
// but that doesn't work. Perhaps because FX_Commit is not called.
ColorF viewportBackgroundColor(m_clearColor.GetR(), m_clearColor.GetG(), m_clearColor.GetB(), m_clearColor.GetA());
gEnv->pRenderer->ClearTargetsImmediately(FRT_CLEAR, viewportBackgroundColor);
// we could use SetSrgbWrite to write to a linear texture here. But that gets complicated with
// having to affect all decsendant element renders. So we just let it write srgb to the render target and
// allow for that when we render using the render target as a source texture.
for (RenderNode* renderNode : m_childRenderNodes)
// Use a dedicated dynamic draw context for rendering to the texture since it can only have one draw list tag
if (!m_dynamicDraw)
{
renderNode->Render(uiRenderer);
m_dynamicDraw = uiRenderer->CreateDynamicDrawContextForRTT(GetRenderTargetName());
}
gEnv->pRenderer->SetRenderTarget(0); // restore render target
if (m_dynamicDraw)
{
UiRenderer::BaseState priorBaseState = uiRenderer->GetBaseState();
gEnv->pRenderer->Unset2DMode(backupMatrices);
UiRenderer::BaseState curBaseState = priorBaseState;
curBaseState.m_blendState.m_blendAlphaSource = AZ::RHI::BlendFactor::One;
curBaseState.m_blendState.m_blendAlphaDest = AZ::RHI::BlendFactor::AlphaSource1Inverse;
uiRenderer->SetBaseState(curBaseState);
for (RenderNode* renderNode : m_childRenderNodes)
{
renderNode->Render(uiRenderer, m_modelViewProjMat, m_dynamicDraw);
}
uiRenderer->SetBaseState(priorBaseState);
}
else
{
AZ_WarningOnce("UI", false, "Failed to create a Dynamic Draw Context for UI Element's render target. "\
"Please ensure that the custom LyShinePass has been added to the project's main render pipeline.");
}
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
const char* RenderTargetRenderNode::GetRenderTargetName() const
{
ITexture* texture = gEnv->pRenderer->EF_GetTextureByID(m_renderTargetHandle);
return texture->GetName();
return m_attachmentImage->GetRHIImage()->GetName().GetCStr();
}
////////////////////////////////////////////////////////////////////////////////////////////////////
int RenderTargetRenderNode::GetNestLevel() const
{
return m_nestLevel;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
const AZ::Data::Instance<AZ::RPI::AttachmentImage> RenderTargetRenderNode::GetRenderTarget() const
{
return m_attachmentImage;
}
#ifndef _RELEASE
@@ -671,31 +671,29 @@ namespace LyShine
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void RenderGraph::BeginRenderToTexture(int renderTargetHandle, SDepthTexture* renderTargetDepthSurface,
void RenderGraph::BeginRenderToTexture([[maybe_unused]] int renderTargetHandle, [[maybe_unused]] SDepthTexture* renderTargetDepthSurface,
[[maybe_unused]] const AZ::Vector2& viewportTopLeft, [[maybe_unused]] const AZ::Vector2& viewportSize, [[maybe_unused]] const AZ::Color& clearColor)
{
// LYSHINE_ATOM_TODO - this function will be removed when all IRenderer references are gone from UI components
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void RenderGraph::BeginRenderToTexture(AZ::Data::Instance<AZ::RPI::AttachmentImage> attachmentImage,
const AZ::Vector2& viewportTopLeft, const AZ::Vector2& viewportSize, const AZ::Color& clearColor)
{
#ifdef LYSHINE_ATOM_TODO // keeping this code for future phase (masks and render targets)
// this uses pool allocator
RenderTargetRenderNode* renderTargetRenderNode = new RenderTargetRenderNode(
m_currentRenderTarget, renderTargetHandle, renderTargetDepthSurface,
m_currentRenderTarget, attachmentImage,
viewportTopLeft, viewportSize, clearColor, m_renderTargetNestLevel);
m_currentRenderTarget = renderTargetRenderNode;
m_renderNodeListStack.push(&m_currentRenderTarget->GetChildRenderNodeList());
m_renderTargetNestLevel++;
#else
AZ_UNUSED(clearColor);
AZ_UNUSED(viewportSize);
AZ_UNUSED(viewportTopLeft);
AZ_UNUSED(renderTargetDepthSurface);
AZ_UNUSED(renderTargetHandle);
#endif
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void RenderGraph::EndRenderToTexture()
{
#ifdef LYSHINE_ATOM_TODO // keeping this code for future phase (masks and render targets)
AZ_Assert(m_currentRenderTarget, "Calling EndRenderToTexture while not defining a render target node");
if (m_currentRenderTarget)
{
@@ -709,7 +707,6 @@ namespace LyShine
m_renderNodeListStack.pop();
m_renderTargetNestLevel--;
}
#endif
}
void RenderGraph::AddPrimitive(
@@ -803,11 +800,22 @@ namespace LyShine
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void RenderGraph::AddAlphaMaskPrimitive(IRenderer::DynUiPrimitive* primitive,
ITexture* texture, ITexture* maskTexture,
bool isClampTextureMode, bool isTextureSRGB, bool isTexturePremultipliedAlpha, BlendMode blendMode)
void RenderGraph::AddAlphaMaskPrimitive([[maybe_unused]] IRenderer::DynUiPrimitive* primitive,
[[maybe_unused]] ITexture* texture, [[maybe_unused]] ITexture* maskTexture,
[[maybe_unused]] bool isClampTextureMode, [[maybe_unused]] bool isTextureSRGB, [[maybe_unused]] bool isTexturePremultipliedAlpha, [[maybe_unused]] BlendMode blendMode)
{
// LYSHINE_ATOM_TODO - this function will be removed when all IRenderer references are gone from UI components
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void RenderGraph::AddAlphaMaskPrimitiveAtom(IRenderer::DynUiPrimitive* primitive,
AZ::Data::Instance<AZ::RPI::AttachmentImage> contentAttachmentImage,
AZ::Data::Instance<AZ::RPI::AttachmentImage> maskAttachmentImage,
bool isClampTextureMode,
bool isTextureSRGB,
bool isTexturePremultipliedAlpha,
BlendMode blendMode)
{
#ifdef LYSHINE_ATOM_TODO // keeping this code for future phase (masks and render targets)
AZStd::vector<RenderNode*>* renderNodeList = m_renderNodeListStack.top();
int texUnit0 = -1;
@@ -842,8 +850,8 @@ namespace LyShine
{
// render state is the same - we can add the primitive to this list if the texture is in
// the list or there is space for another texture
texUnit0 = primListRenderNode->GetOrAddTexture(texture, true);
texUnit1 = primListRenderNode->GetOrAddTexture(maskTexture, true);
texUnit0 = primListRenderNode->GetOrAddTexture(contentAttachmentImage, true);
texUnit1 = primListRenderNode->GetOrAddTexture(maskAttachmentImage, true);
if (texUnit0 != -1 && texUnit1 != -1)
{
@@ -857,7 +865,7 @@ namespace LyShine
{
// We can't add this primitive to the existing render node, we need to create a new render node
// this uses a pool allocator for fast allocation
renderNodeToAddTo = new PrimitiveListRenderNode(texture, maskTexture,
renderNodeToAddTo = new PrimitiveListRenderNode(contentAttachmentImage, maskAttachmentImage,
isClampTextureMode, isTextureSRGB, isPreMultiplyAlpha, alphaMaskType, blendModeState);
renderNodeList->push_back(renderNodeToAddTo);
@@ -881,15 +889,6 @@ namespace LyShine
// add this primitive to the render node
renderNodeToAddTo->AddPrimitive(primitive);
}
#else
AZ_UNUSED(primitive);
AZ_UNUSED(texture);
AZ_UNUSED(maskTexture);
AZ_UNUSED(isClampTextureMode);
AZ_UNUSED(isTextureSRGB);
AZ_UNUSED(isTexturePremultipliedAlpha);
AZ_UNUSED(blendMode);
#endif
}
////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -972,11 +971,8 @@ namespace LyShine
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void RenderGraph::Render(UiRenderer* uiRenderer, const AZ::Vector2& viewportSize)
void RenderGraph::Render(UiRenderer* uiRenderer, [[maybe_unused]] const AZ::Vector2& viewportSize)
{
// LYSHINE_ATOM_TODO - will probably need to support this when converting UI Editor to use Atom
AZ_UNUSED(viewportSize);
AZ::RHI::Ptr<AZ::RPI::DynamicDrawContext> dynamicDraw = uiRenderer->GetDynamicDrawContext();
// Disable stencil and enable blend/color write
@@ -984,57 +980,35 @@ namespace LyShine
dynamicDraw->SetTarget0BlendState(uiRenderer->GetBaseState().m_blendState);
// First render the render targets, they are sorted so that more deeply nested ones are rendered first.
#ifdef LYSHINE_ATOM_TODO // keeping this code for reference for future phase (render targets)
// They only need to be rendered the first time that a render graph is rendered after it has been built.
// Though there is a special case, if this is the first time a shader variant has been used it can miss
// the first render. So to be safe we only stop rendering to render targets after we have rendered to
// them twice with no shader compiles initiated.
if (m_renderToRenderTargetCount < 2)
if (m_renderToRenderTargetCount == 0)
{
// Enable the Rtt passes to draw onto the render targets
SetRttPassesEnabled(uiRenderer, true);
}
// LYSHINE_ATOM_TODO - It is currently necessary to render to the targets twice. Needs investigation
constexpr int timesToRenderToRenderTargets = 2;
if (m_renderToRenderTargetCount < timesToRenderToRenderTargets)
{
for (RenderNode* renderNode : m_renderTargetRenderNodes)
{
renderNode->Render(uiRenderer);
}
// if the render targets render OK we don't need to render them every frame. But if a new shader
// variant needed to be compiled then they will not have rendered OK. So we check is there are
// any shaders still in the process of compiling. Because they are compiled on the render
// thread, we may not know until the next frame that a shader needed to be compiled. So we need
// the counter.
SShaderCacheStatistics stats;
gEnv->pRenderer->EF_Query(EFQ_GetShaderCacheInfo, stats);
bool waitingOnShadersToCompile = stats.m_nNumShaderAsyncCompiles > 0 ? true : false;
if (!waitingOnShadersToCompile)
{
m_renderToRenderTargetCount++;
}
else
{
m_renderToRenderTargetCount = 0;
renderNode->Render(uiRenderer, uiRenderer->GetModelViewProjectionMatrix(), dynamicDraw);
}
m_renderToRenderTargetCount++;
}
#else
for (RenderNode* renderNode : m_renderTargetRenderNodes)
else if (m_renderToRenderTargetCount < timesToRenderToRenderTargets + 1)
{
renderNode->Render(uiRenderer);
// Disable the rtt render passes since they don't need to be rendered to until the graph becomes invalidated again.
// This is also necessary to prevent the render targets' contents getting cleared on load by the pass.
SetRttPassesEnabled(uiRenderer, false);
m_renderToRenderTargetCount++;
}
#endif
#ifdef LYSHINE_ATOM_TODO // keeping this code for reference for future phase (UI Editor)
// Set2DMode defines the viewport so we set it to canvas viewport here (the render target render nodes
// above will have set the viewport as they needed).
TransformationMatrices backupMatrices;
gEnv->pRenderer->Set2DMode(static_cast<uint32>(viewportSize.GetX()), static_cast<uint32>(viewportSize.GetY()), backupMatrices);
#endif
for (RenderNode* renderNode : m_renderNodes)
{
renderNode->Render(uiRenderer);
renderNode->Render(uiRenderer, uiRenderer->GetModelViewProjectionMatrix(), dynamicDraw);
}
#ifdef LYSHINE_ATOM_TODO // keeping this code for reference for future phase (UI Editor)
// end the 2D mode
gEnv->pRenderer->Unset2DMode(backupMatrices);
#endif
}
////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -1072,6 +1046,31 @@ namespace LyShine
return m_renderNodes.empty();
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void RenderGraph::GetRenderTargetsAndDependencies(LyShine::AttachmentImagesAndDependencies& attachmentImagesAndDependencies)
{
for (RenderNode* renderNode : m_renderTargetRenderNodes)
{
const RenderTargetRenderNode* renderTargetRenderNode = static_cast<const RenderTargetRenderNode*>(renderNode);
if (renderTargetRenderNode->GetNestLevel() == 0)
{
LyShine::AttachmentImages attachmentImages;
const AZStd::vector<RenderNode*>& childNodeList = renderTargetRenderNode->GetChildRenderNodeList();
for (auto& childNode : childNodeList)
{
if (childNode->GetType() == RenderNodeType::RenderTarget)
{
const RenderTargetRenderNode* childRenderTargetRenderNode = static_cast<const RenderTargetRenderNode*>(childNode);
attachmentImages.emplace_back(childRenderTargetRenderNode->GetRenderTarget());
}
}
attachmentImagesAndDependencies.emplace_back(AttachmentImageAndDependentsPair(renderTargetRenderNode->GetRenderTarget(), attachmentImages));
}
}
}
#ifndef _RELEASE
////////////////////////////////////////////////////////////////////////////////////////////////////
void RenderGraph::ValidateGraph()
@@ -1540,4 +1539,19 @@ namespace LyShine
return flags;
}
void RenderGraph::SetRttPassesEnabled(UiRenderer* uiRenderer, bool enabled)
{
// Enable or disable the rtt render passes
AZ::RPI::SceneId sceneId = uiRenderer->GetViewportContext()->GetRenderScene()->GetId();
for (RenderTargetRenderNode* renderTargetRenderNode : m_renderTargetRenderNodes)
{
// Find the rtt pass to disable
AZ::RPI::RasterPass* rttPass = nullptr;
LyShinePassRequestBus::EventResult(rttPass, sceneId, &LyShinePassRequestBus::Events::GetRttPass, renderTargetRenderNode->GetRenderTargetName());
if (rttPass)
{
rttPass->SetEnabled(enabled);
}
}
}
}
+52 -12
View File
@@ -15,10 +15,13 @@
#include <AzCore/std/containers/set.h>
#include <AzCore/Math/Color.h>
#include <Atom/RPI.Public/Image/AttachmentImage.h>
#include <Atom/RPI.Reflect/Image/Image.h>
#include <Atom/RPI.Public/DynamicDraw/DynamicDrawContext.h>
#include <AtomCore/Instance/Instance.h>
#include "UiRenderer.h"
#include "LyShinePass.h"
#ifndef _RELEASE
#include "LyShineDebug.h"
#endif
@@ -46,7 +49,9 @@ namespace LyShine
RenderNode(RenderNodeType type) : m_type(type) {}
virtual ~RenderNode() {};
virtual void Render(UiRenderer* uiRenderer) = 0;
virtual void Render(UiRenderer* uiRenderer
, const AZ::Matrix4x4& modelViewProjMat
, AZ::RHI::Ptr<AZ::RPI::DynamicDrawContext> dynamicDraw) = 0;
RenderNodeType GetType() const { return m_type; }
@@ -70,7 +75,9 @@ namespace LyShine
PrimitiveListRenderNode(const AZ::Data::Instance<AZ::RPI::Image>& texture, const AZ::Data::Instance<AZ::RPI::Image>& maskTexture,
bool isClampTextureMode, bool isTextureSRGB, bool preMultiplyAlpha, AlphaMaskType alphaMaskType, int blendModeState);
~PrimitiveListRenderNode() override;
void Render(UiRenderer* uiRenderer) override;
void Render(UiRenderer* uiRenderer
, const AZ::Matrix4x4& modelViewProjMat
, AZ::RHI::Ptr<AZ::RPI::DynamicDrawContext> dynamicDraw) override;
void AddPrimitive(IRenderer::DynUiPrimitive* primitive);
IRenderer::DynUiPrimitiveList& GetPrimitives() const;
@@ -128,7 +135,9 @@ namespace LyShine
MaskRenderNode(MaskRenderNode* parentMask, bool isMaskingEnabled, bool useAlphaTest, bool drawBehind, bool drawInFront);
~MaskRenderNode() override;
void Render(UiRenderer* uiRenderer) override;
void Render(UiRenderer* uiRenderer
, const AZ::Matrix4x4& modelViewProjMat
, AZ::RHI::Ptr<AZ::RPI::DynamicDrawContext> dynamicDraw) override;
AZStd::vector<RenderNode*>& GetMaskRenderNodeList() { return m_maskRenderNodes; }
const AZStd::vector<RenderNode*>& GetMaskRenderNodeList() const { return m_maskRenderNodes; }
@@ -152,8 +161,12 @@ namespace LyShine
#endif
private: // functions
void SetupBeforeRenderingMask(UiRenderer* uiRenderer, bool firstPass, UiRenderer::BaseState priorBaseState);
void SetupAfterRenderingMask(UiRenderer* uiRenderer, bool firstPass, UiRenderer::BaseState priorBaseState);
void SetupBeforeRenderingMask(UiRenderer* uiRenderer,
AZ::RHI::Ptr<AZ::RPI::DynamicDrawContext> dynamicDraw,
bool firstPass, UiRenderer::BaseState priorBaseState);
void SetupAfterRenderingMask(UiRenderer* uiRenderer,
AZ::RHI::Ptr<AZ::RPI::DynamicDrawContext> dynamicDraw,
bool firstPass, UiRenderer::BaseState priorBaseState);
private: // data
AZStd::vector<RenderNode*> m_maskRenderNodes; //!< The render nodes used to render the mask shape
@@ -175,15 +188,17 @@ namespace LyShine
// We use a pool allocator to keep these allocations fast.
AZ_CLASS_ALLOCATOR(RenderTargetRenderNode, AZ::PoolAllocator, 0);
RenderTargetRenderNode(RenderTargetRenderNode* parentRenderTarget, int renderTargetHandle,
SDepthTexture* renderTargetDepthSurface,
RenderTargetRenderNode(RenderTargetRenderNode* parentRenderTarget,
AZ::Data::Instance<AZ::RPI::AttachmentImage> attachmentImage,
const AZ::Vector2& viewportTopLeft,
const AZ::Vector2& viewportSize,
const AZ::Color& clearColor,
int nestLevel);
~RenderTargetRenderNode() override;
void Render(UiRenderer* uiRenderer) override;
void Render(UiRenderer* uiRenderer
, const AZ::Matrix4x4& modelViewProjMat
, AZ::RHI::Ptr<AZ::RPI::DynamicDrawContext> dynamicDraw) override;
AZStd::vector<RenderNode*>& GetChildRenderNodeList() { return m_childRenderNodes; }
const AZStd::vector<RenderNode*>& GetChildRenderNodeList() const { return m_childRenderNodes; }
@@ -197,6 +212,9 @@ namespace LyShine
AZ::Color GetClearColor() const { return m_clearColor; }
const char* GetRenderTargetName() const;
int GetNestLevel() const;
const AZ::Data::Instance<AZ::RPI::AttachmentImage> GetRenderTarget() const;
#ifndef _RELEASE
// A debug-only function useful for debugging
@@ -213,13 +231,16 @@ namespace LyShine
RenderTargetRenderNode* m_parentRenderTarget = nullptr; //! Used while building the render graph.
int m_renderTargetHandle = -1;
SDepthTexture* m_renderTargetDepthSurface = nullptr;
AZ::Data::Instance<AZ::RPI::AttachmentImage> m_attachmentImage;
// Each render target requires a unique dynamic draw context to draw to the raster pass associated with the target
AZ::RHI::Ptr<AZ::RPI::DynamicDrawContext> m_dynamicDraw;
float m_viewportX = 0;
float m_viewportY = 0;
float m_viewportWidth = 0;
float m_viewportHeight = 0;
AZ::Matrix4x4 m_modelViewProjMat;
AZ::Color m_clearColor;
int m_nestLevel = 0;
};
@@ -241,9 +262,10 @@ namespace LyShine
void StartChildrenForMask() override;
void EndMask() override;
//! Begin rendering to a texture
void BeginRenderToTexture(int renderTargetHandle, SDepthTexture* renderTargetDepthSurface,
const AZ::Vector2& viewportTopLeft, const AZ::Vector2& viewportSize,
const AZ::Color& clearColor) override;
const AZ::Vector2& viewportTopLeft, const AZ::Vector2& viewportSize, const AZ::Color& clearColor) override;
void EndRenderToTexture() override;
void AddPrimitive(IRenderer::DynUiPrimitive* primitive, ITexture* texture,
@@ -268,6 +290,20 @@ namespace LyShine
void AddPrimitiveAtom(IRenderer::DynUiPrimitive* primitive, const AZ::Data::Instance<AZ::RPI::Image>& texture,
bool isClampTextureMode, bool isTextureSRGB, bool isTexturePremultipliedAlpha, BlendMode blendMode);
//! Add an indexed triangle list primitive to the render graph which will use maskTexture as an alpha (gradient) mask
void AddAlphaMaskPrimitiveAtom(IRenderer::DynUiPrimitive* primitive,
AZ::Data::Instance<AZ::RPI::AttachmentImage> contentAttachmentImage,
AZ::Data::Instance<AZ::RPI::AttachmentImage> maskAttachmentImage,
bool isClampTextureMode,
bool isTextureSRGB,
bool isTexturePremultipliedAlpha,
BlendMode blendMode);
void BeginRenderToTexture(AZ::Data::Instance<AZ::RPI::AttachmentImage> attachmentImage,
const AZ::Vector2& viewportTopLeft,
const AZ::Vector2& viewportSize,
const AZ::Color& clearColor);
//! Render the display graph
void Render(UiRenderer* uiRenderer, const AZ::Vector2& viewportSize);
@@ -283,6 +319,8 @@ namespace LyShine
//! Test whether the render graph contains any render nodes
bool IsEmpty();
void GetRenderTargetsAndDependencies(LyShine::AttachmentImagesAndDependencies& attachmentImagesAndDependencies);
#ifndef _RELEASE
// A debug-only function useful for debugging, not called but calls can be added during debugging
void ValidateGraph();
@@ -311,6 +349,8 @@ namespace LyShine
//! Given a blend mode and whether the shader will be outputing premultiplied alpha, return state flags
int GetBlendModeState(LyShine::BlendMode blendMode, bool isShaderOutputPremultAlpha) const;
void SetRttPassesEnabled(UiRenderer* uiRenderer, bool enabled);
protected: // data
AZStd::vector<RenderNode*> m_renderNodes;
@@ -0,0 +1,22 @@
/*
* 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
namespace LyShine
{
//! Ebus to handle render target requests
class RenderToTextureRequests
: public AZ::ComponentBus
{
public:
virtual AZ::RHI::AttachmentId UseRenderTarget(const AZ::Name& renderTargetName, AZ::RHI::Size size) = 0;
virtual void ReleaseRenderTarget(const AZ::RHI::AttachmentId& attachmentId) = 0;
virtual AZ::Data::Instance<AZ::RPI::AttachmentImage> GetRenderTarget(const AZ::RHI::AttachmentId& attachmentId) = 0;
};
using RenderToTextureRequestBus = AZ::EBus<RenderToTextureRequests>;
}
+83 -4
View File
@@ -49,6 +49,8 @@
#include <AzFramework/Input/Devices/Mouse/InputDeviceMouse.h>
#include <AzFramework/Input/Devices/Touch/InputDeviceTouch.h>
#include <Atom/RPI.Public/Image/ImageSystemInterface.h>
#include <Atom/RPI.Public/Image/AttachmentImagePool.h>
#include "Animation/UiAnimationSystem.h"
@@ -64,6 +66,8 @@
#include <LyShine/Bus/UiFaderBus.h>
#endif
#include "LyShinePassDataBus.h"
////////////////////////////////////////////////////////////////////////////////////////////////////
//! UiCanvasNotificationBus Behavior context handler class
class UiCanvasNotificationBusBehaviorHandler
@@ -251,14 +255,22 @@ namespace
UiRenderer* GetUiRendererForGame()
{
CLyShine* lyShine = static_cast<CLyShine*>(gEnv->pLyShine);
return lyShine ? lyShine->GetUiRenderer() : nullptr;
if (gEnv && gEnv->pLyShine)
{
CLyShine* lyShine = static_cast<CLyShine*>(gEnv->pLyShine);
return lyShine->GetUiRenderer();
}
return nullptr;
}
UiRenderer* GetUiRendererForEditor()
{
CLyShine* lyShine = static_cast<CLyShine*>(gEnv->pLyShine);
return lyShine ? lyShine->GetUiRendererForEditor() : nullptr;
if (gEnv && gEnv->pLyShine)
{
CLyShine* lyShine = static_cast<CLyShine*>(gEnv->pLyShine);
return lyShine->GetUiRendererForEditor();
}
return nullptr;
}
bool IsValidInteractable(const AZ::EntityId& entityId)
@@ -1829,6 +1841,46 @@ void UiCanvasComponent::MarkRenderGraphDirty()
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
AZ::RHI::AttachmentId UiCanvasComponent::UseRenderTarget(const AZ::Name& renderTargetName, AZ::RHI::Size size)
{
// Create a render target that UI elements will render to
AZ::RHI::ImageDescriptor imageDesc;
imageDesc.m_bindFlags = AZ::RHI::ImageBindFlags::Color | AZ::RHI::ImageBindFlags::ShaderReadWrite;
imageDesc.m_size = size;
imageDesc.m_format = AZ::RHI::Format::R8G8B8A8_UNORM;
AZ::Data::Instance<AZ::RPI::AttachmentImagePool> pool = AZ::RPI::ImageSystemInterface::Get()->GetSystemAttachmentPool();
auto attachmentImage = AZ::RPI::AttachmentImage::Create(*pool.get(), imageDesc, renderTargetName);
if (!attachmentImage)
{
AZ_Warning("UI", false, "Failed to create render target");
return AZ::RHI::AttachmentId();
}
m_attachmentImageMap[attachmentImage->GetAttachmentId()] = attachmentImage;
// Notify LyShine render pass that it needs to rebuild
QueueRttPassRebuild();
return attachmentImage->GetAttachmentId();
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiCanvasComponent::ReleaseRenderTarget(const AZ::RHI::AttachmentId& attachmentId)
{
m_attachmentImageMap.erase(attachmentId);
// Notify LyShine render pass that it needs to rebuild
QueueRttPassRebuild();
}
////////////////////////////////////////////////////////////////////////////////////////////////////
AZ::Data::Instance<AZ::RPI::AttachmentImage> UiCanvasComponent::GetRenderTarget(const AZ::RHI::AttachmentId& attachmentId)
{
return m_attachmentImageMap[attachmentId];
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiCanvasComponent::UpdateCanvas(float deltaTime, bool isInGame)
{
@@ -1864,6 +1916,8 @@ void UiCanvasComponent::RenderCanvas(bool isInGame, AZ::Vector2 viewportSize, Ui
return;
}
m_renderInEditor = uiRenderer ? true : false;
if (!uiRenderer)
{
uiRenderer = GetUiRendererForGame();
@@ -1948,6 +2002,12 @@ void UiCanvasComponent::ScheduleElementDestroy(AZ::EntityId entityId)
m_elementsScheduledForDestroy.push_back(entityId);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiCanvasComponent::GetRenderTargets(LyShine::AttachmentImagesAndDependencies& attachmentImagesAndDependencies)
{
m_renderGraph.GetRenderTargetsAndDependencies(attachmentImagesAndDependencies);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiCanvasComponent::DestroyScheduledElements()
{
@@ -1959,6 +2019,17 @@ void UiCanvasComponent::DestroyScheduledElements()
m_elementsScheduledForDestroy.clear();
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiCanvasComponent::QueueRttPassRebuild()
{
UiRenderer* uiRenderer = m_renderInEditor ? GetUiRendererForEditor() : GetUiRendererForGame();
if (uiRenderer && uiRenderer->GetViewportContext()) // can be null in automated testing
{
AZ::RPI::SceneId sceneId = uiRenderer->GetViewportContext()->GetRenderScene()->GetId();
EBUS_EVENT_ID(sceneId, LyShinePassRequestBus, RebuildRttChildren);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
#ifndef _RELEASE
void UiCanvasComponent::GetDebugInfoInteractables(AZ::EntityId& activeInteractable, AZ::EntityId& hoverInteractable) const
@@ -2350,6 +2421,7 @@ void UiCanvasComponent::Activate()
UiCanvasComponentImplementationBus::Handler::BusConnect(m_entity->GetId());
UiEditorCanvasBus::Handler::BusConnect(m_entity->GetId());
UiAnimationBus::Handler::BusConnect(m_entity->GetId());
LyShine::RenderToTextureRequestBus::Handler::BusConnect(m_entity->GetId());
// Reconnect to buses that we connect to intermittently
// This will only happen if we have been deactivated and reactivated at runtime
@@ -2382,6 +2454,7 @@ void UiCanvasComponent::Deactivate()
UiCanvasComponentImplementationBus::Handler::BusDisconnect();
UiEditorCanvasBus::Handler::BusDisconnect();
UiAnimationBus::Handler::BusDisconnect();
LyShine::RenderToTextureRequestBus::Handler::BusDisconnect();
// disconnect from any other buses we could be connected to
if (m_hoverInteractable.IsValid() && AZ::EntityBus::Handler::BusIsConnectedId(m_hoverInteractable))
@@ -2400,6 +2473,12 @@ void UiCanvasComponent::Deactivate()
DestroyRenderTarget();
}
// Destroy owned render targets
m_attachmentImageMap.clear();
//! Notify LyShine pass that it needs to rebuild
QueueRttPassRebuild();
delete m_layoutManager;
m_layoutManager = nullptr;
@@ -32,6 +32,8 @@
#include "TextureAtlas/TextureAtlasBus.h"
#include "TextureAtlas/TextureAtlasNotificationBus.h"
#include "RenderToTextureBus.h"
namespace AZ
{
class SerializeContext;
@@ -51,6 +53,7 @@ class UiCanvasComponent
, public IUiAnimationListener
, public UiEditorCanvasBus::Handler
, public UiCanvasComponentImplementationBus::Handler
, public LyShine::RenderToTextureRequestBus::Handler
{
public: // constants
static const AZ::Vector2 s_defaultCanvasSize;
@@ -232,6 +235,12 @@ public: // member functions
void MarkRenderGraphDirty() override;
// ~UiCanvasComponentImplementationInterface
// RenderToTextureRequests
AZ::RHI::AttachmentId UseRenderTarget(const AZ::Name& renderTargetName, AZ::RHI::Size size) override;
void ReleaseRenderTarget(const AZ::RHI::AttachmentId& attachmentId) override;
AZ::Data::Instance<AZ::RPI::AttachmentImage> GetRenderTarget(const AZ::RHI::AttachmentId& attachmentId) override;
// ~RenderToTextureRequests
void UpdateCanvas(float deltaTime, bool isInGame);
void RenderCanvas(bool isInGame, AZ::Vector2 viewportSize, UiRenderer* uiRenderer = nullptr);
@@ -257,6 +266,10 @@ public: // member functions
//! Queue an element to be destroyed at end of frame
void ScheduleElementDestroy(AZ::EntityId entityId);
bool IsRenderGraphDirty() { return m_renderGraph.GetDirtyFlag(); }
void GetRenderTargets(LyShine::AttachmentImagesAndDependencies& attachmentImagesAndDependencies);
#ifndef _RELEASE
struct DebugInfoNumElements
{
@@ -427,6 +440,9 @@ private: // member functions
void DestroyScheduledElements();
//! Notify LyShine pass that it needs to rebuild its Rtt child passes
void QueueRttPassRebuild();
private: // static member functions
static AZ::u64 CreateUniqueId();
@@ -597,4 +613,8 @@ private: // static data
LyShine::RenderGraph m_renderGraph; //!< the render graph for rendering the canvas, can be cached between frames
bool m_isRendering = false;
bool m_renderInEditor = false; //!< indicates whether this canvas will render in the Editor viewport or the Game viewport
//! Map of attachments used by this canvas's elements
AZStd::unordered_map<AZ::RHI::AttachmentId, AZ::Data::Instance<AZ::RPI::AttachmentImage>> m_attachmentImageMap;
};
+11 -7
View File
@@ -301,6 +301,17 @@ void UiCanvasManager::OnFontTextureUpdated([[maybe_unused]] IFFont* font)
m_fontTextureHasChanged = true;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiCanvasManager::GetRenderTargets(LyShine::AttachmentImagesAndDependencies& attachmentImagesAndDependencies)
{
for (auto canvas : m_loadedCanvases)
{
LyShine::AttachmentImagesAndDependencies canvasTargets;
canvas->GetRenderTargets(canvasTargets);
attachmentImagesAndDependencies.insert(attachmentImagesAndDependencies.end(), canvasTargets.begin(), canvasTargets.end());
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiCanvasManager::OnCatalogAssetChanged(const AZ::Data::AssetId& assetId)
{
@@ -606,13 +617,6 @@ void UiCanvasManager::RenderLoadedCanvases()
m_fontTextureHasChanged = false;
}
#ifdef LYSHINE_ATOM_TODO // render target conversion to Atom
// clear the stencil buffer before rendering the loaded canvases - required for masking
// NOTE: We want to use ClearTargetsImmediately instead of ClearTargetsLater since we will not be setting the render target
ColorF viewportBackgroundColor(0, 0, 0, 0); // if clearing color we want to set alpha to zero also
gEnv->pRenderer->ClearTargetsImmediately(FRT_CLEAR_STENCIL, viewportBackgroundColor);
#endif
for (auto canvas : m_loadedCanvases)
{
if (!canvas->GetIsRenderToTexture())
@@ -11,6 +11,7 @@
#include <LyShine/Bus/UiCanvasManagerBus.h>
#include <LyShine/Bus/UiCanvasBus.h>
#include <LyShine/UiEntityContext.h>
#include "LyShinePassDataBus.h"
#include <IFont.h>
class UiCanvasComponent;
@@ -92,6 +93,9 @@ public: // member functions
bool HandleInputEventForLoadedCanvases(const AzFramework::InputChannel& inputChannel);
bool HandleTextEventForLoadedCanvases(const AZStd::string& textUTF8);
// Get the render targets used by all currently loaded UI Canvases
void GetRenderTargets(LyShine::AttachmentImagesAndDependencies& attachmentImagesAndDependencies);
#ifndef _RELEASE
void DebugDisplayCanvasData(int setting) const;
void DebugDisplayDrawCallData() const;
+48 -75
View File
@@ -6,6 +6,7 @@
*
*/
#include "UiFaderComponent.h"
#include "RenderGraph.h"
#include <LyShine/Draw2d.h>
#include <AzCore/Math/Crc.h>
@@ -14,6 +15,9 @@
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/RTTI/BehaviorContext.h>
#include <Atom/RPI.Public/Image/AttachmentImage.h>
#include <AtomCore/Instance/Instance.h>
#include <LyShine/Bus/UiElementBus.h>
#include <LyShine/Bus/UiRenderBus.h>
#include <LyShine/Bus/UiCanvasBus.h>
@@ -22,6 +26,7 @@
#include <ITimer.h>
#include "UiSerialize.h"
#include "RenderToTextureBus.h"
// BehaviorContext UiFaderNotificationBus forwarder
class BehaviorUiFaderNotificationBusHandler
@@ -120,7 +125,7 @@ void UiFaderComponent::Render(LyShine::IRenderGraph* renderGraph, UiElementInter
AZ::Vector2 renderTargetSize = pixelAlignedBottomRight - pixelAlignedTopLeft;
bool needsResize = static_cast<int>(renderTargetSize.GetX()) != m_renderTargetWidth || static_cast<int>(renderTargetSize.GetY()) != m_renderTargetHeight;
if (m_renderTargetHandle == -1 || needsResize)
if (m_attachmentImageId.IsEmpty() || needsResize)
{
// We delay first creation of the render target until render time since size is not known in Activate
// We also call this if the size has changed
@@ -128,7 +133,7 @@ void UiFaderComponent::Render(LyShine::IRenderGraph* renderGraph, UiElementInter
}
// if the render target failed to be created (zero size for example) we don't render the element at all
if (m_renderTargetHandle == -1)
if (m_attachmentImageId.IsEmpty())
{
return;
}
@@ -139,7 +144,7 @@ void UiFaderComponent::Render(LyShine::IRenderGraph* renderGraph, UiElementInter
else
{
// destroy previous render target, if exists
if (m_renderTargetHandle != -1)
if (!m_attachmentImageId.IsEmpty())
{
DestroyRenderTarget();
}
@@ -452,54 +457,22 @@ void UiFaderComponent::CreateOrResizeRenderTarget(const AZ::Vector2& pixelAligne
m_viewportTopLeft = pixelAlignedTopLeft;
m_viewportSize = renderTargetSize;
#ifdef LYSHINE_ATOM_TODO // [LYN-3359] Support RTT using Atom
// Check if the render target already exists
if (m_renderTargetHandle != -1)
{
// Render target exists, resize it to the given size
if (!gEnv->pRenderer->ResizeRenderTarget(m_renderTargetHandle, static_cast<int>(renderTargetSize.GetX()), static_cast<int>(renderTargetSize.GetY())))
{
AZ_Warning("UI", false, "Failed to resize render target for UiFaderComponent");
DestroyRenderTarget();
}
}
else
{
// Create a render target that this element and its children will be rendered to.
m_renderTargetHandle = gEnv->pRenderer->CreateRenderTarget(m_renderTargetName.c_str(),
static_cast<int>(renderTargetSize.GetX()), static_cast<int>(renderTargetSize.GetY()), Clr_Transparent, eTF_R8G8B8A8);
// LYSHINE_ATOM_TODO: optimize by reusing/resizing targets
DestroyRenderTarget();
if (m_renderTargetHandle == -1)
{
AZ_Warning("UI", false, "Failed to create render target for UiFaderComponent");
}
}
// if depth surface already exists then destroy it
if (m_renderTargetDepthSurface)
// Create a render target that this element and its children will be rendered to
AZ::EntityId canvasEntityId;
EBUS_EVENT_ID_RESULT(canvasEntityId, GetEntityId(), UiElementBus, GetCanvasEntityId);
AZ::RHI::Size imageSize(renderTargetSize.GetX(), renderTargetSize.GetY(), 1);
EBUS_EVENT_ID_RESULT(m_attachmentImageId, canvasEntityId, LyShine::RenderToTextureRequestBus, UseRenderTarget, AZ::Name(m_renderTargetName.c_str()), imageSize);
if (m_attachmentImageId.IsEmpty())
{
gEnv->pRenderer->DestroyDepthSurface(m_renderTargetDepthSurface);
m_renderTargetDepthSurface = nullptr;
AZ_Warning("UI", false, "Failed to create render target for UiFaderComponent");
}
if (m_renderTargetHandle != -1)
{
// Also create a depth surface to render the canvas to, we need depth for masking
// since that uses the stencil buffer. We support any combination of nesting faders and masks
m_renderTargetDepthSurface = gEnv->pRenderer->CreateDepthSurface(
static_cast<int>(renderTargetSize.GetX()), static_cast<int>(renderTargetSize.GetY()));
if (!m_renderTargetDepthSurface)
{
AZ_Warning("UI", false, "Failed to create depth surface for UiFaderComponent");
DestroyRenderTarget();
}
}
#endif
// at this point either all render targets and depth surfaces are created or none are.
// If all succeeded then update the render target size
if (m_renderTargetHandle != -1)
if (!m_attachmentImageId.IsEmpty())
{
m_renderTargetWidth = static_cast<int>(renderTargetSize.GetX());
m_renderTargetHeight = static_cast<int>(renderTargetSize.GetY());
@@ -511,16 +484,12 @@ void UiFaderComponent::CreateOrResizeRenderTarget(const AZ::Vector2& pixelAligne
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiFaderComponent::DestroyRenderTarget()
{
if (m_renderTargetHandle != -1)
if (!m_attachmentImageId.IsEmpty())
{
gEnv->pRenderer->DestroyRenderTarget(m_renderTargetHandle);
m_renderTargetHandle = -1;
}
if (m_renderTargetDepthSurface)
{
gEnv->pRenderer->DestroyDepthSurface(m_renderTargetDepthSurface);
m_renderTargetDepthSurface = nullptr;
AZ::EntityId canvasEntityId;
EBUS_EVENT_ID_RESULT(canvasEntityId, GetEntityId(), UiElementBus, GetCanvasEntityId);
EBUS_EVENT_ID(canvasEntityId, LyShine::RenderToTextureRequestBus, ReleaseRenderTarget, m_attachmentImageId);
m_attachmentImageId = AZ::RHI::AttachmentId{};
}
}
@@ -594,14 +563,20 @@ void UiFaderComponent::RenderStandardFader(LyShine::IRenderGraph* renderGraph, U
void UiFaderComponent::RenderRttFader(LyShine::IRenderGraph* renderGraph, UiElementInterface* elementInterface,
UiRenderInterface* renderInterface, int numChildren, bool isInGame)
{
// Get the render target
AZ::Data::Instance<AZ::RPI::AttachmentImage> attachmentImage;
AZ::EntityId canvasEntityId;
EBUS_EVENT_ID_RESULT(canvasEntityId, GetEntityId(), UiElementBus, GetCanvasEntityId);
EBUS_EVENT_ID_RESULT(attachmentImage, canvasEntityId, LyShine::RenderToTextureRequestBus, GetRenderTarget, m_attachmentImageId);
// Render the element and its children to a render target
{
// we always clear to transparent black - the accumulation of alpha in the render target requires it
AZ::Color clearColor(0.0f, 0.0f, 0.0f, 0.0f);
// Start building the render to texture node in the render graph
renderGraph->BeginRenderToTexture(m_renderTargetHandle, m_renderTargetDepthSurface,
m_viewportTopLeft, m_viewportSize, clearColor);
LyShine::RenderGraph* lyRenderGraph = dynamic_cast<LyShine::RenderGraph*>(renderGraph);
lyRenderGraph->BeginRenderToTexture(attachmentImage, m_viewportTopLeft, m_viewportSize, clearColor);
// We don't want this fader or parent faders to affect what is rendered to the render target since we will
// apply those fades when we render from the render target.
@@ -624,14 +599,13 @@ void UiFaderComponent::RenderRttFader(LyShine::IRenderGraph* renderGraph, UiElem
float desiredAlpha = renderGraph->GetAlphaFade() * m_fade;
uint8 desiredPackedAlpha = static_cast<uint8>(desiredAlpha * 255.0f);
UCol desiredPackedColor;
// This is a special case. We have an input texture that already has premultiplied alpha.
// So we tell the shader not to premultiply the output colors and we premultiply the alpha
// into the vertex colors so that they are premultiplied too.
desiredPackedColor.r = desiredPackedColor.g = desiredPackedColor.b = desiredPackedColor.a = desiredPackedAlpha;
if (m_cachedPrimitive.m_vertices[0].color.dcolor != desiredPackedColor.dcolor)
// If the fade value has changed we need to update the alpha values in the vertex colors but we do
// not want to touch or recompute the RGB values
if (m_cachedPrimitive.m_vertices[0].color.a != desiredPackedAlpha)
{
// go through the cached vertices and update the color values
// go through all the cached vertices and update the alpha values
UCol desiredPackedColor = m_cachedPrimitive.m_vertices[0].color;
desiredPackedColor.a = desiredPackedAlpha;
for (int i = 0; i < m_cachedPrimitive.m_numVertices; ++i)
{
m_cachedPrimitive.m_vertices[i].color = desiredPackedColor;
@@ -639,21 +613,20 @@ void UiFaderComponent::RenderRttFader(LyShine::IRenderGraph* renderGraph, UiElem
}
}
#ifdef LYSHINE_ATOM_TODO // [LYN-3359] Support RTT using Atom
// Add a primitive to render a quad using the render target we have created
{
// Set the texture and other render state required
ITexture* texture = gEnv->pRenderer->EF_GetTextureByID(m_renderTargetHandle);
bool isClampTextureMode = true;
bool isTextureSRGB = true;
bool isTexturePremultipliedAlpha = true;
LyShine::BlendMode blendMode = LyShine::BlendMode::Normal;
// add a render node to render from the render target texture to the current target
renderGraph->AddPrimitive(&m_cachedPrimitive, texture,
isClampTextureMode, isTextureSRGB, isTexturePremultipliedAlpha, blendMode);
LyShine::RenderGraph* lyRenderGraph = dynamic_cast<LyShine::RenderGraph*>(renderGraph);
if (lyRenderGraph)
{
// Set the texture and other render state required
AZ::Data::Instance<AZ::RPI::Image> image = attachmentImage;
bool isClampTextureMode = true;
bool isTextureSRGB = true;
bool isTexturePremultipliedAlpha = true;
LyShine::BlendMode blendMode = LyShine::BlendMode::Normal;
lyRenderGraph->AddPrimitiveAtom(&m_cachedPrimitive, image, isClampTextureMode, isTextureSRGB, isTexturePremultipliedAlpha, blendMode);
}
}
#endif
}
}
+3 -5
View File
@@ -18,6 +18,7 @@
#include <AzCore/Component/Component.h>
#include <AzCore/Math/Color.h>
#include <Atom/RHI.Reflect/AttachmentId.h>
////////////////////////////////////////////////////////////////////////////////////////////////////
class UiFaderComponent
@@ -156,11 +157,8 @@ private: // data
//! This is generated from the entity ID and cached
AZStd::string m_renderTargetName;
//! When rendering to a texture this is the texture ID of the render target
int m_renderTargetHandle = -1;
//! When rendering to a texture this is our depth surface
SDepthTexture* m_renderTargetDepthSurface = nullptr;
//! When rendering to a texture this is the attachment image for the render target
AZ::RHI::AttachmentId m_attachmentImageId;
//! The positions used for the render to texture viewport and to render the render target to the screen
AZ::Vector2 m_viewportTopLeft;
+73 -101
View File
@@ -14,12 +14,17 @@
#include <AzCore/RTTI/BehaviorContext.h>
#include "IRenderer.h"
#include "RenderToTextureBus.h"
#include "RenderGraph.h"
#include <LyShine/Bus/UiTransformBus.h>
#include <LyShine/Bus/UiElementBus.h>
#include <LyShine/Bus/UiRenderBus.h>
#include <LyShine/Bus/UiVisualBus.h>
#include <LyShine/Bus/UiCanvasBus.h>
#include <Atom/RPI.Public/Image/AttachmentImage.h>
#include <AtomCore/Instance/Instance.h>
////////////////////////////////////////////////////////////////////////////////////////////////////
// PUBLIC MEMBER FUNCTIONS
////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -79,7 +84,7 @@ void UiMaskComponent::Render(LyShine::IRenderGraph* renderGraph, UiElementInterf
AZ::Vector2 renderTargetSize = pixelAlignedBottomRight - pixelAlignedTopLeft;
bool needsResize = static_cast<int>(renderTargetSize.GetX()) != m_renderTargetWidth || static_cast<int>(renderTargetSize.GetY()) != m_renderTargetHeight;
if (m_contentRenderTargetHandle == -1 || needsResize)
if (m_contentAttachmentImageId.IsEmpty() || needsResize)
{
// Need to create or resize the render target
CreateOrResizeRenderTarget(pixelAlignedTopLeft, pixelAlignedBottomRight);
@@ -89,7 +94,7 @@ void UiMaskComponent::Render(LyShine::IRenderGraph* renderGraph, UiElementInterf
// in theory the child mask element could still be non-zero size and could reveal things. But the way gradient masks
// currently work is that the size of the render target is defined by the size of this element, therefore nothing would
// be revealed by the mask if it is zero sized.
if (m_contentRenderTargetHandle == -1)
if (m_contentAttachmentImageId.IsEmpty())
{
return;
}
@@ -101,7 +106,7 @@ void UiMaskComponent::Render(LyShine::IRenderGraph* renderGraph, UiElementInterf
else
{
// using stencil mask, not going to use render targets, destroy previous render target, if exists
if (m_contentRenderTargetHandle != -1)
if (!m_contentAttachmentImageId.IsEmpty())
{
DestroyRenderTarget();
}
@@ -113,7 +118,7 @@ void UiMaskComponent::Render(LyShine::IRenderGraph* renderGraph, UiElementInterf
else
{
// masking disabled, not going to use render targets, destroy previous render target, if exists
if (m_contentRenderTargetHandle != -1)
if (!m_contentAttachmentImageId.IsEmpty())
{
DestroyRenderTarget();
}
@@ -553,77 +558,30 @@ void UiMaskComponent::CreateOrResizeRenderTarget(const AZ::Vector2& pixelAligned
m_viewportTopLeft = pixelAlignedTopLeft;
m_viewportSize = renderTargetSize;
#ifdef LYSHINE_ATOM_TODO // [LYN-3359] Support RTT using Atom
// Check if the render target already exists
if (m_contentRenderTargetHandle != -1)
{
// Render target exists, resize it to the given size
if (!gEnv->pRenderer->ResizeRenderTarget(m_contentRenderTargetHandle, static_cast<int>(renderTargetSize.GetX()), static_cast<int>(renderTargetSize.GetY())))
{
AZ_Warning("UI", false, "Failed to resize content render target for UiMaskComponent");
DestroyRenderTarget();
}
}
else
{
// Create a render target that this element and its children will be rendered to.
m_contentRenderTargetHandle = gEnv->pRenderer->CreateRenderTarget(m_renderTargetName.c_str(),
static_cast<int>(renderTargetSize.GetX()), static_cast<int>(renderTargetSize.GetY()), Clr_Transparent, eTF_R8G8B8A8);
// LYSHINE_ATOM_TODO: optimize by reusing/resizing targets
DestroyRenderTarget();
if (m_contentRenderTargetHandle == -1)
{
AZ_Warning("UI", false, "Failed to create content render target for UiMaskComponent");
}
// Create a render target that this element and its children will be rendered to
AZ::EntityId canvasEntityId;
EBUS_EVENT_ID_RESULT(canvasEntityId, GetEntityId(), UiElementBus, GetCanvasEntityId);
AZ::RHI::Size imageSize(renderTargetSize.GetX(), renderTargetSize.GetY(), 1);
EBUS_EVENT_ID_RESULT(m_contentAttachmentImageId, canvasEntityId, LyShine::RenderToTextureRequestBus, UseRenderTarget, AZ::Name(m_renderTargetName.c_str()), imageSize);
if (m_contentAttachmentImageId.IsEmpty())
{
AZ_Warning("UI", false, "Failed to create content render target for UiMaskComponent");
}
// if depth surface already exists then destroy it
if (m_renderTargetDepthSurface)
// Create separate render target for the mask texture
EBUS_EVENT_ID_RESULT(m_maskAttachmentImageId, canvasEntityId, LyShine::RenderToTextureRequestBus, UseRenderTarget, AZ::Name(m_maskRenderTargetName.c_str()), imageSize);
if (m_maskAttachmentImageId.IsEmpty())
{
gEnv->pRenderer->DestroyDepthSurface(m_renderTargetDepthSurface);
m_renderTargetDepthSurface = nullptr;
AZ_Warning("UI", false, "Failed to create mask render target for UiMaskComponent");
DestroyRenderTarget();
}
if (m_contentRenderTargetHandle != -1)
{
// Also create a depth surface to render the canvas to, we need depth for masking
// since that uses the stencil buffer. We support any combination of nesting faders and masks
m_renderTargetDepthSurface = gEnv->pRenderer->CreateDepthSurface(
static_cast<int>(renderTargetSize.GetX()), static_cast<int>(renderTargetSize.GetY()));
if (!m_renderTargetDepthSurface)
{
AZ_Warning("UI", false, "Failed to create depth surface for UiMaskComponent");
DestroyRenderTarget();
}
}
// Check if the mask render target already exists
if (m_maskRenderTargetHandle != -1)
{
// Render target exists, resize it to the given size
if (!gEnv->pRenderer->ResizeRenderTarget(m_maskRenderTargetHandle, static_cast<int>(renderTargetSize.GetX()), static_cast<int>(renderTargetSize.GetY())))
{
AZ_Warning("UI", false, "Failed to resize mask render target for UiMaskComponent");
DestroyRenderTarget();
}
}
else
{
// create separate render target for the mask texture
m_maskRenderTargetHandle = gEnv->pRenderer->CreateRenderTarget(m_maskRenderTargetName.c_str(),
static_cast<int>(renderTargetSize.GetX()), static_cast<int>(renderTargetSize.GetY()), Clr_Transparent, eTF_R8G8B8A8);
if (m_maskRenderTargetHandle == -1)
{
AZ_Warning("UI", false, "Failed to create mask render target for UiMaskComponent");
DestroyRenderTarget();
}
}
#endif
// at this point either all render targets and depth surfaces are created or none are.
// If all succeeded then update the render target size
if (m_contentRenderTargetHandle != -1)
if (!m_contentAttachmentImageId.IsEmpty())
{
m_renderTargetWidth = static_cast<int>(renderTargetSize.GetX());
m_renderTargetHeight = static_cast<int>(renderTargetSize.GetY());
@@ -635,22 +593,22 @@ void UiMaskComponent::CreateOrResizeRenderTarget(const AZ::Vector2& pixelAligned
////////////////////////////////////////////////////////////////////////////////////////////////////
void UiMaskComponent::DestroyRenderTarget()
{
if (m_contentRenderTargetHandle != -1)
if (!m_contentAttachmentImageId.IsEmpty())
{
gEnv->pRenderer->DestroyRenderTarget(m_contentRenderTargetHandle);
m_contentRenderTargetHandle = -1;
AZ::EntityId canvasEntityId;
EBUS_EVENT_ID_RESULT(canvasEntityId, GetEntityId(), UiElementBus, GetCanvasEntityId);
EBUS_EVENT_ID(canvasEntityId, LyShine::RenderToTextureRequestBus, ReleaseRenderTarget, m_contentAttachmentImageId);
m_contentAttachmentImageId = AZ::RHI::AttachmentId{};
}
if (m_renderTargetDepthSurface)
if (!m_maskAttachmentImageId.IsEmpty())
{
gEnv->pRenderer->DestroyDepthSurface(m_renderTargetDepthSurface);
m_renderTargetDepthSurface = nullptr;
}
AZ::EntityId canvasEntityId;
EBUS_EVENT_ID_RESULT(canvasEntityId, GetEntityId(), UiElementBus, GetCanvasEntityId);
EBUS_EVENT_ID(canvasEntityId, LyShine::RenderToTextureRequestBus, ReleaseRenderTarget, m_maskAttachmentImageId);
if (m_maskRenderTargetHandle != -1)
{
gEnv->pRenderer->DestroyRenderTarget(m_maskRenderTargetHandle);
m_maskRenderTargetHandle = -1;
m_maskAttachmentImageId = AZ::RHI::AttachmentId{};
}
}
@@ -747,6 +705,14 @@ void UiMaskComponent::RenderUsingGradientMask(LyShine::IRenderGraph* renderGraph
// we always clear to transparent black - the accumulation of alpha in the render target requires it
AZ::Color clearColor(0.0f, 0.0f, 0.0f, 0.0f);
// Get the render targets
AZ::Data::Instance<AZ::RPI::AttachmentImage> contentAttachmentImage;
AZ::Data::Instance<AZ::RPI::AttachmentImage> maskAttachmentImage;
AZ::EntityId canvasEntityId;
EBUS_EVENT_ID_RESULT(canvasEntityId, GetEntityId(), UiElementBus, GetCanvasEntityId);
EBUS_EVENT_ID_RESULT(contentAttachmentImage, canvasEntityId, LyShine::RenderToTextureRequestBus, GetRenderTarget, m_contentAttachmentImageId);
EBUS_EVENT_ID_RESULT(maskAttachmentImage, canvasEntityId, LyShine::RenderToTextureRequestBus, GetRenderTarget, m_maskAttachmentImageId);
// We don't want parent faders to affect what is rendered to the render target since we will
// apply those fades when we render from the render target.
// Note that this means that, if there are parent (no render to texture) faders, we get a "free"
@@ -756,8 +722,8 @@ void UiMaskComponent::RenderUsingGradientMask(LyShine::IRenderGraph* renderGraph
// mask render target
{
// Start building the render to texture node in the render graph
renderGraph->BeginRenderToTexture(m_maskRenderTargetHandle, m_renderTargetDepthSurface,
m_viewportTopLeft, m_viewportSize, clearColor);
LyShine::RenderGraph* lyRenderGraph = dynamic_cast<LyShine::RenderGraph*>(renderGraph);
lyRenderGraph->BeginRenderToTexture(maskAttachmentImage, m_viewportTopLeft, m_viewportSize, clearColor);
// Render the visual component for this element (if there is one) plus the child mask element (if there is one)
RenderMaskPrimitives(renderGraph, renderInterface, childMaskElementInterface, isInGame);
@@ -769,8 +735,8 @@ void UiMaskComponent::RenderUsingGradientMask(LyShine::IRenderGraph* renderGraph
// content render target
{
// Start building the render to texture node for the content render target in the render graph
renderGraph->BeginRenderToTexture(m_contentRenderTargetHandle, m_renderTargetDepthSurface,
m_viewportTopLeft, m_viewportSize, clearColor);
LyShine::RenderGraph* lyRenderGraph = dynamic_cast<LyShine::RenderGraph*>(renderGraph);
lyRenderGraph->BeginRenderToTexture(contentAttachmentImage, m_viewportTopLeft, m_viewportSize, clearColor);
// Render the "content" - the child elements excluding the child mask element (if any)
RenderContentPrimitives(renderGraph, elementInterface, childMaskElementInterface, numChildren, isInGame);
@@ -790,14 +756,13 @@ void UiMaskComponent::RenderUsingGradientMask(LyShine::IRenderGraph* renderGraph
float desiredAlpha = renderGraph->GetAlphaFade();
uint32 desiredPackedAlpha = static_cast<uint8>(desiredAlpha * 255.0f);
UCol desiredPackedColor;
// This is a special case. We have an input texture that already has premultiplied alpha.
// So we tell the shader not to premultiply the output colors and we premultiply the alpha
// into the vertex colors so that they are premultiplied too.
desiredPackedColor.r = desiredPackedColor.g = desiredPackedColor.b = desiredPackedColor.a = desiredPackedAlpha;
if (m_cachedPrimitive.m_vertices[0].color.dcolor != desiredPackedColor.dcolor)
// If the fade value has changed we need to update the alpha values in the vertex colors but we do
// not want to touch or recompute the RGB values
if (m_cachedPrimitive.m_vertices[0].color.a != desiredPackedAlpha)
{
// go through the cached vertices and update the color values
// go through all the cached vertices and update the alpha values
UCol desiredPackedColor = m_cachedPrimitive.m_vertices[0].color;
desiredPackedColor.a = desiredPackedAlpha;
for (int i = 0; i < m_cachedPrimitive.m_numVertices; ++i)
{
m_cachedPrimitive.m_vertices[i].color = desiredPackedColor;
@@ -805,22 +770,29 @@ void UiMaskComponent::RenderUsingGradientMask(LyShine::IRenderGraph* renderGraph
}
}
#ifdef LYSHINE_ATOM_TODO // [LYN-3359] Support RTT using Atom
// Add a primitive to do the alpha mask
{
// Set the texture and other render state required
ITexture* texture = gEnv->pRenderer->EF_GetTextureByID(m_contentRenderTargetHandle);
ITexture* maskTexture = gEnv->pRenderer->EF_GetTextureByID(m_maskRenderTargetHandle);
bool isClampTextureMode = true;
bool isTextureSRGB = true;
bool isTexturePremultipliedAlpha = true;
LyShine::BlendMode blendMode = LyShine::BlendMode::Normal;
LyShine::RenderGraph* lyRenderGraph = dynamic_cast<LyShine::RenderGraph*>(renderGraph);
if (lyRenderGraph)
{
// Set the texture and other render state required
AZ::Data::Instance<AZ::RPI::Image> contentImage = contentAttachmentImage;
AZ::Data::Instance<AZ::RPI::Image> maskImage = maskAttachmentImage;
bool isClampTextureMode = true;
bool isTextureSRGB = true;
bool isTexturePremultipliedAlpha = false;
LyShine::BlendMode blendMode = LyShine::BlendMode::Normal;
// add a render node to render using the two render targets, one as an alpha mask of the other
renderGraph->AddAlphaMaskPrimitive(&m_cachedPrimitive, texture, maskTexture,
isClampTextureMode, isTextureSRGB, isTexturePremultipliedAlpha, blendMode);
// add a render node to render using the two render targets, one as an alpha mask of the other
lyRenderGraph->AddAlphaMaskPrimitiveAtom(&m_cachedPrimitive,
contentAttachmentImage,
maskAttachmentImage,
isClampTextureMode,
isTextureSRGB,
isTexturePremultipliedAlpha,
blendMode);
}
}
#endif
}
}
+5 -3
View File
@@ -15,6 +15,7 @@
#include <LyShine/IRenderGraph.h>
#include <AzCore/Component/Component.h>
#include <Atom/RHI.Reflect/AttachmentId.h>
////////////////////////////////////////////////////////////////////////////////////////////////////
class UiMaskComponent
@@ -184,15 +185,16 @@ private: // data
//! This is generated from the entity ID and cached
AZStd::string m_maskRenderTargetName;
//! When rendering to a texture this is the texture ID of the render target
int m_contentRenderTargetHandle = -1;
//! When rendering to a texture this is the attachment image for the render target
AZ::RHI::AttachmentId m_contentAttachmentImageId;
//! When rendering to a texture this is our depth surface, we use the same one for rendering the mask elements
//! and the content elements - it is cleared in between.
SDepthTexture* m_renderTargetDepthSurface = nullptr;
//! When rendering to a texture this is the texture ID of the render target
int m_maskRenderTargetHandle = -1;
//! When rendering to a texture this is the attachment image for the render target
AZ::RHI::AttachmentId m_maskAttachmentImageId;
//! The positions used for the render to texture viewport and to render the render target to the screen
AZ::Vector2 m_viewportTopLeft = AZ::Vector2::CreateZero();
+110 -29
View File
@@ -6,6 +6,7 @@
*
*/
#include "UiRenderer.h"
#include "LyShinePassDataBus.h"
#include <Atom/RPI.Public/Image/ImageSystemInterface.h>
#include <Atom/RPI.Public/DynamicDraw/DynamicDrawInterface.h>
@@ -60,25 +61,32 @@ void UiRenderer::OnBootstrapSceneReady([[maybe_unused]] AZ::RPI::Scene* bootstra
AZ::Data::Instance<AZ::RPI::Shader> uiShader = AZ::RPI::LoadShader(uiShaderFilepath);
// Create scene to be used by the dynamic draw context
AZ::RPI::ScenePtr scene;
if (m_viewportContext)
{
// Create a new scene based on the user specified viewport context
scene = CreateScene(m_viewportContext);
m_scene = CreateScene(m_viewportContext);
}
else
{
// No viewport context specified, use default scene
scene = AZ::RPI::RPISystemInterface::Get()->GetDefaultScene();
m_scene = AZ::RPI::RPISystemInterface::Get()->GetDefaultScene();
}
// Create a dynamic draw context for UI Canvas drawing for the scene
CreateDynamicDrawContext(scene, uiShader);
m_dynamicDraw = CreateDynamicDrawContext(m_scene, uiShader);
// Cache shader data such as input indices for later use
CacheShaderData(m_dynamicDraw);
if (m_dynamicDraw)
{
// Cache shader data such as input indices for later use
CacheShaderData(m_dynamicDraw);
m_isRPIReady = true;
m_isRPIReady = true;
}
else
{
AZ_Error(LogName, false, "Failed to create a dynamic draw context for LyShine. \
This can happen if the LyShine pass hasn't been added to the main render pipeline.");
}
}
AZ::RPI::ScenePtr UiRenderer::CreateScene(AZStd::shared_ptr<AZ::RPI::ViewportContext> viewportContext)
@@ -107,22 +115,40 @@ AZ::RPI::ScenePtr UiRenderer::CreateScene(AZStd::shared_ptr<AZ::RPI::ViewportCon
return atomScene;
}
void UiRenderer::CreateDynamicDrawContext(AZ::RPI::ScenePtr scene, AZ::Data::Instance<AZ::RPI::Shader> uiShader)
AZ::RHI::Ptr<AZ::RPI::DynamicDrawContext> UiRenderer::CreateDynamicDrawContext(
AZ::RPI::ScenePtr scene,
AZ::Data::Instance<AZ::RPI::Shader> uiShader)
{
m_dynamicDraw = AZ::RPI::DynamicDrawInterface::Get()->CreateDynamicDrawContext();
// Find the pass that renders the UI canvases after the rtt passes
AZ::RPI::RasterPass* uiCanvasPass = nullptr;
AZ::RPI::SceneId sceneId = m_scene->GetId();
LyShinePassRequestBus::EventResult(uiCanvasPass, sceneId, &LyShinePassRequestBus::Events::GetUiCanvasPass);
AZ::RHI::Ptr<AZ::RPI::DynamicDrawContext> dynamicDraw = AZ::RPI::DynamicDrawInterface::Get()->CreateDynamicDrawContext();
// Initialize the dynamic draw context
m_dynamicDraw->InitShader(uiShader);
m_dynamicDraw->InitVertexFormat(
dynamicDraw->InitShader(uiShader);
dynamicDraw->InitVertexFormat(
{ { "POSITION", AZ::RHI::Format::R32G32_FLOAT },
{ "COLOR", AZ::RHI::Format::B8G8R8A8_UNORM },
{ "TEXCOORD", AZ::RHI::Format::R32G32_FLOAT },
{ "BLENDINDICES", AZ::RHI::Format::R16G16_UINT } }
);
m_dynamicDraw->AddDrawStateOptions(AZ::RPI::DynamicDrawContext::DrawStateOptions::StencilState
dynamicDraw->AddDrawStateOptions(AZ::RPI::DynamicDrawContext::DrawStateOptions::StencilState
| AZ::RPI::DynamicDrawContext::DrawStateOptions::BlendMode);
m_dynamicDraw->SetOutputScope(scene.get());
m_dynamicDraw->EndInit();
if (uiCanvasPass)
{
dynamicDraw->SetOutputScope(uiCanvasPass);
}
else
{
// Render target support is disabled
dynamicDraw->SetOutputScope(m_scene.get());
}
dynamicDraw->EndInit();
return dynamicDraw;
}
AZStd::shared_ptr<AZ::RPI::ViewportContext> UiRenderer::GetViewportContext()
@@ -158,19 +184,26 @@ void UiRenderer::CacheShaderData(const AZ::RHI::Ptr<AZ::RPI::DynamicDrawContext>
isClampIndexName);
// Cache shader variants that will be used
// LYSHINE_ATOM_TODO - more variants will be used in future phase (masks/render target support)
AZ::RPI::ShaderOptionList shaderOptionsDefault;
shaderOptionsDefault.push_back(AZ::RPI::ShaderOption(AZ::Name("o_preMultiplyAlpha"), AZ::Name("false")));
shaderOptionsDefault.push_back(AZ::RPI::ShaderOption(AZ::Name("o_alphaTest"), AZ::Name("false")));
shaderOptionsDefault.push_back(AZ::RPI::ShaderOption(AZ::Name("o_srgbWrite"), AZ::Name("true")));
shaderOptionsDefault.push_back(AZ::RPI::ShaderOption(AZ::Name("o_modulate"), AZ::Name("Modulate::None")));
m_uiShaderData.m_shaderVariantDefault = dynamicDraw->UseShaderVariant(shaderOptionsDefault);
AZ::RPI::ShaderOptionList shaderOptionsAlphaTest;
shaderOptionsAlphaTest.push_back(AZ::RPI::ShaderOption(AZ::Name("o_preMultiplyAlpha"), AZ::Name("false")));
shaderOptionsAlphaTest.push_back(AZ::RPI::ShaderOption(AZ::Name("o_alphaTest"), AZ::Name("true")));
shaderOptionsAlphaTest.push_back(AZ::RPI::ShaderOption(AZ::Name("o_srgbWrite"), AZ::Name("true")));
shaderOptionsAlphaTest.push_back(AZ::RPI::ShaderOption(AZ::Name("o_modulate"), AZ::Name("Modulate::None")));
m_uiShaderData.m_shaderVariantAlphaTest = dynamicDraw->UseShaderVariant(shaderOptionsAlphaTest);
AZ::RPI::ShaderOptionList shaderOptionsTextureLinear;
shaderOptionsTextureLinear.push_back(AZ::RPI::ShaderOption(AZ::Name("o_alphaTest"), AZ::Name("false")));
shaderOptionsTextureLinear.push_back(AZ::RPI::ShaderOption(AZ::Name("o_srgbWrite"), AZ::Name("true")));
shaderOptionsTextureLinear.push_back(AZ::RPI::ShaderOption(AZ::Name("o_modulate"), AZ::Name("Modulate::None")));
m_uiShaderData.m_shaderVariantTextureLinear = dynamicDraw->UseShaderVariant(shaderOptionsTextureLinear);
AZ::RPI::ShaderOptionList shaderOptionsTextureSrgb;
shaderOptionsTextureSrgb.push_back(AZ::RPI::ShaderOption(AZ::Name("o_alphaTest"), AZ::Name("false")));
shaderOptionsTextureSrgb.push_back(AZ::RPI::ShaderOption(AZ::Name("o_srgbWrite"), AZ::Name("false")));
shaderOptionsTextureSrgb.push_back(AZ::RPI::ShaderOption(AZ::Name("o_modulate"), AZ::Name("Modulate::None")));
m_uiShaderData.m_shaderVariantTextureSrgb = dynamicDraw->UseShaderVariant(shaderOptionsTextureSrgb);
AZ::RPI::ShaderOptionList shaderVariantAlphaTestMask;
shaderVariantAlphaTestMask.push_back(AZ::RPI::ShaderOption(AZ::Name("o_alphaTest"), AZ::Name("true")));
shaderVariantAlphaTestMask.push_back(AZ::RPI::ShaderOption(AZ::Name("o_srgbWrite"), AZ::Name("false")));
shaderVariantAlphaTestMask.push_back(AZ::RPI::ShaderOption(AZ::Name("o_modulate"), AZ::Name("Modulate::None")));
m_uiShaderData.m_shaderVariantAlphaTestMask = dynamicDraw->UseShaderVariant(shaderVariantAlphaTestMask);
AZ::RPI::ShaderOptionList shaderVariantGradientMask;
shaderVariantGradientMask.push_back(AZ::RPI::ShaderOption(AZ::Name("o_alphaTest"), AZ::Name("false")));
shaderVariantGradientMask.push_back(AZ::RPI::ShaderOption(AZ::Name("o_srgbWrite"), AZ::Name("false")));
shaderVariantGradientMask.push_back(AZ::RPI::ShaderOption(AZ::Name("o_modulate"), AZ::Name("Modulate::Alpha")));
m_uiShaderData.m_shaderVariantGradientMask = dynamicDraw->UseShaderVariant(shaderVariantGradientMask);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -215,6 +248,38 @@ AZ::RHI::Ptr<AZ::RPI::DynamicDrawContext> UiRenderer::GetDynamicDrawContext()
return m_dynamicDraw;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
AZ::RHI::Ptr<AZ::RPI::DynamicDrawContext> UiRenderer::CreateDynamicDrawContextForRTT(const AZStd::string& rttName)
{
// find the rtt pass with the specified name
AZ::RPI::RasterPass* rttPass = nullptr;
AZ::RPI::SceneId sceneId = m_scene->GetId();
LyShinePassRequestBus::EventResult(rttPass, sceneId, &LyShinePassRequestBus::Events::GetRttPass, rttName);
if (!rttPass)
{
return nullptr;
}
AZ::RHI::Ptr<AZ::RPI::DynamicDrawContext> dynamicDraw = AZ::RPI::DynamicDrawInterface::Get()->CreateDynamicDrawContext();
// Initialize the dynamic draw context
dynamicDraw->InitShader(m_dynamicDraw->GetShader());
dynamicDraw->InitVertexFormat(
{ { "POSITION", AZ::RHI::Format::R32G32_FLOAT },
{ "COLOR", AZ::RHI::Format::B8G8R8A8_UNORM },
{ "TEXCOORD", AZ::RHI::Format::R32G32_FLOAT },
{ "BLENDINDICES", AZ::RHI::Format::R16G16_UINT } }
);
dynamicDraw->AddDrawStateOptions(AZ::RPI::DynamicDrawContext::DrawStateOptions::StencilState
| AZ::RPI::DynamicDrawContext::DrawStateOptions::BlendMode);
dynamicDraw->SetOutputScope(rttPass);
dynamicDraw->EndInit();
return dynamicDraw;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
const UiRenderer::UiShaderData& UiRenderer::GetUiShaderData()
{
@@ -270,11 +335,27 @@ void UiRenderer::SetBaseState(BaseState state)
////////////////////////////////////////////////////////////////////////////////////////////////////
AZ::RPI::ShaderVariantId UiRenderer::GetCurrentShaderVariant()
{
AZ::RPI::ShaderVariantId variantId = m_uiShaderData.m_shaderVariantDefault;
AZ::RPI::ShaderVariantId variantId = m_uiShaderData.m_shaderVariantTextureLinear;
if (m_baseState.m_useAlphaTest)
{
variantId = m_uiShaderData.m_shaderVariantAlphaTest;
variantId = m_uiShaderData.m_shaderVariantAlphaTestMask;
}
else if (m_baseState.m_modulateAlpha)
{
variantId = m_uiShaderData.m_shaderVariantGradientMask;
}
else if (!m_baseState.m_useAlphaTest && m_baseState.m_srgbWrite)
{
variantId = m_uiShaderData.m_shaderVariantTextureLinear;
}
else if (!m_baseState.m_useAlphaTest && !m_baseState.m_srgbWrite)
{
variantId = m_uiShaderData.m_shaderVariantTextureSrgb;
}
else
{
AZ_Error(LogName, 0, "Unsupported shader variant.");
}
return variantId;
+20 -6
View File
@@ -36,8 +36,10 @@ public: // types
AZ::RHI::ShaderInputConstantIndex m_viewProjInputIndex;
AZ::RHI::ShaderInputConstantIndex m_isClampInputIndex;
AZ::RPI::ShaderVariantId m_shaderVariantDefault;
AZ::RPI::ShaderVariantId m_shaderVariantAlphaTest;
AZ::RPI::ShaderVariantId m_shaderVariantTextureLinear;
AZ::RPI::ShaderVariantId m_shaderVariantTextureSrgb;
AZ::RPI::ShaderVariantId m_shaderVariantAlphaTestMask;
AZ::RPI::ShaderVariantId m_shaderVariantGradientMask;
};
// Base state
@@ -56,17 +58,23 @@ public: // types
m_blendState.m_blendSource = AZ::RHI::BlendFactor::AlphaSource;
m_blendState.m_blendDest = AZ::RHI::BlendFactor::AlphaSourceInverse;
m_blendState.m_blendOp = AZ::RHI::BlendOp::Add;
m_blendState.m_blendAlphaSource = AZ::RHI::BlendFactor::One;
m_blendState.m_blendAlphaDest = AZ::RHI::BlendFactor::Zero;
m_blendState.m_blendAlphaOp = AZ::RHI::BlendOp::Add;
// Disable stencil
m_stencilState = AZ::RHI::StencilState();
m_stencilState.m_enable = 0;
m_useAlphaTest = false;
m_modulateAlpha = false;
}
AZ::RHI::TargetBlendState m_blendState;
AZ::RHI::StencilState m_stencilState;
bool m_useAlphaTest = false;
bool m_modulateAlpha = false;
bool m_srgbWrite = true;
};
public: // member functions
@@ -93,6 +101,8 @@ public: // member functions
//! Return the dynamic draw context associated with this UI renderer
AZ::RHI::Ptr<AZ::RPI::DynamicDrawContext> GetDynamicDrawContext();
AZ::RHI::Ptr<AZ::RPI::DynamicDrawContext> CreateDynamicDrawContextForRTT(const AZStd::string& rttName);
//! Return the shader data for the ui shader
const UiShaderData& GetUiShaderData();
@@ -123,6 +133,9 @@ public: // member functions
//! Decrement the current stencil reference value
void DecrementStencilRef();
//! Return the viewport context set by the user, or the default if not set
AZStd::shared_ptr<AZ::RPI::ViewportContext> GetViewportContext();
#ifndef _RELEASE
//! Setup to record debug texture data before rendering
void DebugSetRecordingOptionForTextureData(int recordingOption);
@@ -143,10 +156,9 @@ private: // member functions
AZ::RPI::ScenePtr CreateScene(AZStd::shared_ptr<AZ::RPI::ViewportContext> viewportContext);
//! Create a dynamic draw context for this renderer
void CreateDynamicDrawContext(AZ::RPI::ScenePtr scene, AZ::Data::Instance<AZ::RPI::Shader>);
//! Return the viewport context set by the user, or the default if not set
AZStd::shared_ptr<AZ::RPI::ViewportContext> GetViewportContext();
AZ::RHI::Ptr<AZ::RPI::DynamicDrawContext> CreateDynamicDrawContext(
AZ::RPI::ScenePtr scene,
AZ::Data::Instance<AZ::RPI::Shader> uiShader);
//! Bind the global white texture for all the texture units we use
void BindNullTexture();
@@ -168,6 +180,8 @@ protected: // attributes
// Set by user when viewport context is not the main/default viewport
AZStd::shared_ptr<AZ::RPI::ViewportContext> m_viewportContext;
AZ::RPI::ScenePtr m_scene;
#ifndef _RELEASE
int m_debugTextureDataRecordLevel = 0;
AZStd::unordered_set<ITexture*> m_texturesUsedInFrame; // LYSHINE_ATOM_TODO - convert to RPI::Image
@@ -163,11 +163,9 @@ namespace UnitTest
SSystemGlobalEnvironment* prevEnv = gEnv;
gEnv = &env;
gEnv->pTimer = &m_timer;
gEnv->pLyShine = nullptr;
UiCanvasComponent* uiCanvasComponent;
UiTooltipDisplayComponent* uiTooltipDisplayComponent;
UiTooltipComponent* uiTooltipComponent;
std::tie(uiCanvasComponent, uiTooltipDisplayComponent, uiTooltipComponent) = CreateUiCanvasWithTooltip();
auto [uiCanvasComponent, uiTooltipDisplayComponent, uiTooltipComponent] = CreateUiCanvasWithTooltip();
uiTooltipDisplayComponent->SetTriggerMode(UiTooltipDisplayInterface::TriggerMode::OnHover);
AZ::Entity* uiTooltipEntity = uiTooltipComponent->GetEntity();
@@ -193,11 +191,9 @@ namespace UnitTest
SSystemGlobalEnvironment* prevEnv = gEnv;
gEnv = &env;
gEnv->pTimer = &m_timer;
gEnv->pLyShine = nullptr;
UiCanvasComponent* uiCanvasComponent;
UiTooltipDisplayComponent* uiTooltipDisplayComponent;
UiTooltipComponent* uiTooltipComponent;
std::tie(uiCanvasComponent, uiTooltipDisplayComponent, uiTooltipComponent) = CreateUiCanvasWithTooltip();
auto [uiCanvasComponent, uiTooltipDisplayComponent, uiTooltipComponent] = CreateUiCanvasWithTooltip();
uiTooltipDisplayComponent->SetTriggerMode(UiTooltipDisplayInterface::TriggerMode::OnHover);
AZ::Entity* uiTooltipEntity = uiTooltipComponent->GetEntity();
@@ -221,11 +217,9 @@ namespace UnitTest
SSystemGlobalEnvironment* prevEnv = gEnv;
gEnv = &env;
gEnv->pTimer = &m_timer;
gEnv->pLyShine = nullptr;
UiCanvasComponent* uiCanvasComponent;
UiTooltipDisplayComponent* uiTooltipDisplayComponent;
UiTooltipComponent* uiTooltipComponent;
std::tie(uiCanvasComponent, uiTooltipDisplayComponent, uiTooltipComponent) = CreateUiCanvasWithTooltip();
auto [uiCanvasComponent, uiTooltipDisplayComponent, uiTooltipComponent] = CreateUiCanvasWithTooltip();
uiTooltipDisplayComponent->SetTriggerMode(UiTooltipDisplayInterface::TriggerMode::OnPress);
AZ::Entity* uiTooltipEntity = uiTooltipComponent->GetEntity();
@@ -249,11 +243,9 @@ namespace UnitTest
SSystemGlobalEnvironment* prevEnv = gEnv;
gEnv = &env;
gEnv->pTimer = &m_timer;
gEnv->pLyShine = nullptr;
UiCanvasComponent* uiCanvasComponent;
UiTooltipDisplayComponent* uiTooltipDisplayComponent;
UiTooltipComponent* uiTooltipComponent;
std::tie(uiCanvasComponent, uiTooltipDisplayComponent, uiTooltipComponent) = CreateUiCanvasWithTooltip();
auto [uiCanvasComponent, uiTooltipDisplayComponent, uiTooltipComponent] = CreateUiCanvasWithTooltip();
uiTooltipDisplayComponent->SetTriggerMode(UiTooltipDisplayInterface::TriggerMode::OnPress);
AZ::Entity* uiTooltipEntity = uiTooltipComponent->GetEntity();
@@ -277,11 +269,9 @@ namespace UnitTest
SSystemGlobalEnvironment* prevEnv = gEnv;
gEnv = &env;
gEnv->pTimer = &m_timer;
gEnv->pLyShine = nullptr;
UiCanvasComponent* uiCanvasComponent;
UiTooltipDisplayComponent* uiTooltipDisplayComponent;
UiTooltipComponent* uiTooltipComponent;
std::tie(uiCanvasComponent, uiTooltipDisplayComponent, uiTooltipComponent) = CreateUiCanvasWithTooltip();
auto [uiCanvasComponent, uiTooltipDisplayComponent, uiTooltipComponent] = CreateUiCanvasWithTooltip();
uiTooltipDisplayComponent->SetTriggerMode(UiTooltipDisplayInterface::TriggerMode::OnClick);
AZ::Entity* uiTooltipEntity = uiTooltipComponent->GetEntity();
@@ -11,8 +11,11 @@ set(FILES
Include/LyShine/Draw2d.h
Source/LyShine.cpp
Source/LyShine.h
Source/LyShinePassDataBus.h
Source/LyShineDebug.cpp
Source/LyShineDebug.h
Source/LyShinePass.cpp
Source/LyShinePass.h
Source/StringUtfUtils.h
Source/UiImageComponent.cpp
Source/UiImageComponent.h
@@ -28,6 +31,7 @@ set(FILES
Source/LyShineLoadScreen.h
Source/RenderGraph.cpp
Source/RenderGraph.h
Source/RenderToTextureBus.h
Source/TextMarkup.cpp
Source/TextMarkup.h
Source/UiButtonComponent.cpp
@@ -0,0 +1,20 @@
{
"Name": "LyShinePass",
"TemplateName": "LyShineParentTemplate",
"Connections": [
{
"LocalSlot": "ColorInputOutput",
"AttachmentRef": {
"Pass": "DebugOverlayPass",
"Attachment": "InputOutput"
}
},
{
"LocalSlot": "DepthInputOutput",
"AttachmentRef": {
"Pass": "DepthPrePass",
"Attachment": "Depth"
}
}
]
}
@@ -0,0 +1,71 @@
"""
Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
import os
import sys
# Parse arguments
if len(sys.argv) != 3:
print('Incorrect number of args')
exit()
engine_path = sys.argv[1]
if not os.path.exists(engine_path):
print(f'Given path {engine_path} does not exist')
exit()
project_path = sys.argv[2]
if not os.path.exists(project_path):
print(f'Given path {project_path} does not exist')
exit()
sys.path.insert(0, os.path.join(engine_path, 'Gems/Atom/RPI/Tools/'))
from atom_rpi_tools.pass_data import PassTemplate
import atom_rpi_tools.utils as utils
# Folder of this py file
dir_name = os.path.dirname(os.path.realpath(__file__))
# Patch render pipeline to insert a custom LyShine parent pass
# Gem::Atom_Feature_Common gem's path since default render pipeline is comming from this gem
gem_assets_path = os.path.join(engine_path,'Gems/Atom/feature/Common/Assets/')
pipeline_relatvie_path = 'Passes/MainPipeline.pass'
srcRenderPipeline = os.path.join(gem_assets_path, pipeline_relatvie_path)
destRenderPipeline = os.path.join(project_path, pipeline_relatvie_path)
# If the project doesn't have a customized main pipeline
# copy the default render pipeline from Atom_Common_Feature gem to same path in project folder
utils.find_or_copy_file(destRenderPipeline, srcRenderPipeline)
# Load project render pipeline
renderPipeline = PassTemplate(destRenderPipeline)
# Skip if LyShinePass already exist
newPassName = 'LyShinePass'
if renderPipeline.find_pass(newPassName)>-1:
print('Skip merging. LyShinePass already exists')
exit()
# Insert LyShinePass between DebugOverlayPass and UIPass
refPass = 'DebugOverlayPass'
# The data file for new pass request is in the same folder of the py file
newPassRequestFilePath = os.path.join(dir_name, 'LyShinePass.data')
newPassRequestData = utils.load_json_file(newPassRequestFilePath)
insertIndex = renderPipeline.find_pass(refPass) + 1
if insertIndex>-1:
renderPipeline.insert_pass_request(insertIndex, newPassRequestData)
else:
print('Failed to find ', refPass)
exit()
# Update attachment references for the passes following LyShinePass
renderPipeline.replace_references_after(newPassName, 'DebugOverlayPass', 'InputOutput', 'LyShinePass', 'ColorInputOutput')
# Save the updated render pipeline
renderPipeline.save()
@@ -32,6 +32,7 @@ namespace Multiplayer
{
m_networkEditorInterface = AZ::Interface<INetworking>::Get()->CreateNetworkInterface(
AZ::Name(MPEditorInterfaceName), ProtocolType::Tcp, TrustZone::ExternalClientToServer, *this);
m_networkEditorInterface->SetTimeoutEnabled(false);
if (editorsv_isDedicated)
{
uint16_t editorServerPort = DefaultServerEditorPort;

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