Merging latest main

main
karlberg 5 years ago
commit 0ce18593d2

1
.gitignore vendored

@ -18,3 +18,4 @@ _savebackup/
#Output folder for test results when running Automated Tests #Output folder for test results when running Automated Tests
TestResults/** TestResults/**
*.swatches *.swatches
/imgui.ini

@ -1,709 +0,0 @@
#!/usr/bin/env groovy
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
PIPELINE_CONFIG_FILE = 'AutomatedReview/lumberyard.json'
INCREMENTAL_BUILD_SCRIPT_PATH = 'scripts/build/bootstrap/incremental_build_util.py'
EMPTY_JSON = readJSON text: '{}'
ENGINE_REPOSITORY_NAME = 'o3de'
def pipelineProperties = []
def pipelineParameters = [
// Build/clean Parameters
// The CLEAN_OUTPUT_DIRECTORY is used by ci_build scripts. Creating the parameter here passes it as an environment variable to jobs and is consumed that way
booleanParam(defaultValue: false, description: 'Deletes the contents of the output directory before building. This will cause a \"clean\" build. NOTE: does not imply CLEAN_ASSETS', name: 'CLEAN_OUTPUT_DIRECTORY'),
booleanParam(defaultValue: false, description: 'Deletes the contents of the output directories of the AssetProcessor before building.', name: 'CLEAN_ASSETS'),
booleanParam(defaultValue: false, description: 'Deletes the contents of the workspace and forces a complete pull.', name: 'CLEAN_WORKSPACE'),
booleanParam(defaultValue: false, description: 'Recreates the volume used for the workspace. The volume will be created out of a snapshot taken from main.', name: 'RECREATE_VOLUME'),
string(defaultValue: '', description: 'Filters and overrides the list of jobs to run for each of the below platforms (comma-separated). Can\'t be used during a pull request.', name: 'JOB_LIST_OVERRIDE'),
// Pull Request Parameters
string(defaultValue: '', description: '', name: 'DESTINATION_BRANCH'),
string(defaultValue: '', description: '', name: 'DESTINATION_COMMIT'),
string(defaultValue: '', description: '', name: 'PULL_REQUEST_ID'),
string(defaultValue: '', description: '', name: 'REPOSITORY_NAME'),
string(defaultValue: '', description: '', name: 'SOURCE_BRANCH'),
string(defaultValue: '', description: '', name: 'SOURCE_COMMIT')
]
def palSh(cmd, lbl = '', winSlashReplacement = true) {
if (env.IS_UNIX) {
sh label: lbl,
script: cmd
} else if (winSlashReplacement) {
bat label: lbl,
script: cmd.replace('/','\\')
} else {
bat label: lbl,
script: cmd
}
}
def palMkdir(path) {
if (env.IS_UNIX) {
sh label: "Making directories ${path}",
script: "mkdir -p ${path}"
} else {
def win_path = path.replace('/','\\')
bat label: "Making directories ${win_path}",
script: "mkdir ${win_path}."
}
}
def palRm(path) {
if (env.IS_UNIX) {
sh label: "Removing ${path}",
script: "rm ${path}"
} else {
def win_path = path.replace('/','\\')
bat label: "Removing ${win_path}",
script: "del ${win_path}"
}
}
def palRmDir(path) {
if (env.IS_UNIX) {
sh label: "Removing ${path}",
script: "rm -rf ${path}"
} else {
def win_path = path.replace('/','\\')
bat label: "Removing ${win_path}",
script: "rd /s /q ${win_path}"
}
}
def IsJobEnabled(buildTypeMap, pipelineName, platformName) {
def job_list_override = params.JOB_LIST_OVERRIDE.tokenize(',')
if(params.PULL_REQUEST_ID) { // dont allow pull requests to filter platforms/jobs
if(buildTypeMap.value.TAGS) {
return buildTypeMap.value.TAGS.contains(pipelineName)
}
} else if (!job_list_override.isEmpty()) {
return params[platformName] && job_list_override.contains(buildTypeMap.key);
} else {
if (params[platformName]) {
if(buildTypeMap.value.TAGS) {
return buildTypeMap.value.TAGS.contains(pipelineName)
}
}
}
return false
}
def GetRunningPipelineName(JENKINS_JOB_NAME) {
// If the job name has an underscore
def job_parts = JENKINS_JOB_NAME.tokenize('/')[0].tokenize('_')
if (job_parts.size() > 1) {
return [job_parts.take(job_parts.size() - 1).join('_'), job_parts[job_parts.size()-1]]
}
return [job_parts[0], 'default']
}
@NonCPS
def RegexMatcher(str, regex) {
def matcher = (str =~ regex)
return matcher ? matcher.group(1) : null
}
def LoadPipelineConfig(String pipelineName, String branchName, String scmType) {
echo 'Loading pipeline config'
if (scmType == 'codecommit') {
PullFilesFromGit(PIPELINE_CONFIG_FILE, branchName, true, ENGINE_REPOSITORY_NAME)
}
def pipelineConfig = {}
pipelineConfig = readJSON file: PIPELINE_CONFIG_FILE
palRm(PIPELINE_CONFIG_FILE)
pipelineConfig.platforms = EMPTY_JSON
// Load the pipeline configs per platform
pipelineConfig.PIPELINE_CONFIGS.each { pipeline_config ->
def platform_regex = pipeline_config.replace('.','\\.').replace('*', '(.*)')
if (!env.IS_UNIX) {
platform_regex = platform_regex.replace('/','\\\\')
}
echo "Downloading platform pipeline configs ${pipeline_config}"
if (scmType == 'codecommit') {
PullFilesFromGit(pipeline_config, branchName, false, ENGINE_REPOSITORY_NAME)
}
echo "Searching platform pipeline configs in ${pipeline_config} using ${platform_regex}"
for (pipeline_config_path in findFiles(glob: pipeline_config)) {
echo "\tFound platform pipeline config ${pipeline_config_path}"
def platform = RegexMatcher(pipeline_config_path, platform_regex)
if(platform) {
pipelineConfig.platforms[platform] = EMPTY_JSON
pipelineConfig.platforms[platform].PIPELINE_ENV = readJSON file: pipeline_config_path.toString()
}
palRm(pipeline_config_path.toString())
}
}
// Load the build configs
pipelineConfig.BUILD_CONFIGS.each { build_config ->
def platform_regex = build_config.replace('.','\\.').replace('*', '(.*)')
if (!env.IS_UNIX) {
platform_regex = platform_regex.replace('/','\\\\')
}
echo "Downloading configs ${build_config}"
if (scmType == 'codecommit') {
PullFilesFromGit(build_config, branchName, false, ENGINE_REPOSITORY_NAME)
}
echo "Searching configs in ${build_config} using ${platform_regex}"
for (build_config_path in findFiles(glob: build_config)) {
echo "\tFound config ${build_config_path}"
def platform = RegexMatcher(build_config_path, platform_regex)
if(platform) {
pipelineConfig.platforms[platform].build_types = readJSON file: build_config_path.toString()
}
}
}
return pipelineConfig
}
def GetSCMType() {
def gitUrl = scm.getUserRemoteConfigs()[0].getUrl()
if (gitUrl ==~ /https:\/\/git-codecommit.*/) {
return 'codecommit'
} else if (gitUrl ==~ /https:\/\/github.com.*/) {
return 'github'
}
return 'unknown'
}
def GetBuildEnvVars(Map platformEnv, Map buildTypeEnv, String pipelineName) {
def envVarMap = [:]
platformPipelineEnv = platformEnv['ENV'] ?: [:]
platformPipelineEnv.each { var ->
envVarMap[var.key] = var.value
}
platformEnvOverride = platformEnv['PIPELINE_ENV_OVERRIDE'] ?: [:]
platformPipelineEnvOverride = platformEnvOverride[pipelineName] ?: [:]
platformPipelineEnvOverride.each { var ->
envVarMap[var.key] = var.value
}
buildTypeEnv.each { var ->
// This may override the above one if there is an entry defined by the job
envVarMap[var.key] = var.value
}
// Environment that only applies to to Jenkins tweaks.
// For 3rdParty downloads, we store them in the EBS volume so we can reuse them across node
// instances. This allow us to scale up and down without having to re-download 3rdParty
envVarMap['LY_PACKAGE_DOWNLOAD_CACHE_LOCATION'] = "${envVarMap['WORKSPACE']}/3rdParty/downloaded_packages"
envVarMap['LY_PACKAGE_UNPACK_LOCATION'] = "${envVarMap['WORKSPACE']}/3rdParty/packages"
return envVarMap
}
def GetEnvStringList(Map envVarMap) {
def strList = []
envVarMap.each { var ->
strList.add("${var.key}=${var.value}")
}
return strList
}
// Pulls/downloads files from the repo through codecommit. Despite Glob matching is NOT supported, '*' is supported
// as a folder or filename (not a portion, it has to be the whole folder or filename)
def PullFilesFromGit(String filenamePath, String branchName, boolean failIfNotFound = true, String repositoryName = env.DEFAULT_REPOSITORY_NAME) {
echo "PullFilesFromGit filenamePath=${filenamePath} branchName=${branchName} repositoryName=${repositoryName}"
def folderPathParts = filenamePath.tokenize('/')
def filename = folderPathParts[folderPathParts.size()-1]
folderPathParts.remove(folderPathParts.size()-1) // remove the filename
def folderPath = folderPathParts.join('/')
if (folderPath.contains('*')) {
def currentPath = ''
for (int i = 0; i < folderPathParts.size(); i++) {
if (folderPathParts[i] == '*') {
palMkdir(currentPath)
retry(3) { palSh("aws codecommit get-folder --repository-name ${repositoryName} --commit-specifier ${branchName} --folder-path ${currentPath} > ${currentPath}/.codecommit", "GetFolder ${currentPath}") }
def folderInfo = readJSON file: "${currentPath}/.codecommit"
folderInfo.subFolders.each { folder ->
def newSubPath = currentPath + '/' + folder.relativePath
for (int j = i+1; j < folderPathParts.size(); j++) {
newSubPath = newSubPath + '/' + folderPathParts[j]
}
newSubPath = newSubPath + '/' + filename
PullFilesFromGit(newSubPath, branchName, false, repositoryName)
}
palRm("${currentPath}/.codecommit")
}
if (i == 0) {
currentPath = folderPathParts[i]
} else {
currentPath = currentPath + '/' + folderPathParts[i]
}
}
} else if (filename.contains('*')) {
palMkdir(folderPath)
retry(3) { palSh("aws codecommit get-folder --repository-name ${repositoryName} --commit-specifier ${branchName} --folder-path ${folderPath} > ${folderPath}/.codecommit", "GetFolder ${folderPath}") }
def folderInfo = readJSON file: "${folderPath}/.codecommit"
folderInfo.files.each { file ->
PullFilesFromGit("${folderPath}/${filename}", branchName, false, repositoryName)
}
palRm("${folderPath}/.codecommit")
} else {
def errorFile = "${folderPath}/error.txt"
palMkdir(folderPath)
retry(3) {
try {
if(env.IS_UNIX) {
sh label: "Downloading ${filenamePath}",
script: "aws codecommit get-file --repository-name ${repositoryName} --commit-specifier ${branchName} --file-path ${filenamePath} --query fileContent --output text 2>${errorFile} > ${filenamePath}_encoded"
sh label: 'Decoding',
script: "base64 --decode ${filenamePath}_encoded > ${filenamePath}"
} else {
errorFile = errorFile.replace('/','\\')
win_filenamePath = filenamePath.replace('/', '\\')
bat label: "Downloading ${win_filenamePath}",
script: "aws codecommit get-file --repository-name ${repositoryName} --commit-specifier ${branchName} --file-path ${filenamePath} --query fileContent --output text 2>${errorFile} > ${win_filenamePath}_encoded"
bat label: 'Decoding',
script: "certutil -decode ${win_filenamePath}_encoded ${win_filenamePath}"
}
palRm("${filenamePath}_encoded")
} catch (Exception ex) {
def error = ''
if(fileExists(errorFile)) {
error = readFile errorFile
}
if (!error || !(!failIfNotFound && error.contains('FileDoesNotExistException'))) {
palRm("${errorFile} ${filenamePath}.encoded ${filenamePath}")
throw new Exception("Could not get file: ${filenamePath}, ex: ${ex}, stderr: ${error}")
}
}
palRm(errorFile)
}
}
}
def SetLfsCredentials(cmd, lbl = '') {
if (env.IS_UNIX) {
sh label: lbl,
script: cmd
} else {
bat label: lbl,
script: cmd
}
}
def CheckoutBootstrapScripts(String branchName) {
checkout([$class: "GitSCM",
branches: [[name: "*/${branchName}"]],
doGenerateSubmoduleConfigurations: false,
extensions: [
[
$class: "SparseCheckoutPaths",
sparseCheckoutPaths: [
[ $class: "SparseCheckoutPath", path: "AutomatedReview/" ],
[ $class: "SparseCheckoutPath", path: "scripts/build/bootstrap/" ],
[ $class: "SparseCheckoutPath", path: "Tools/build/JenkinsScripts/build/Platform" ]
]
],
[
$class: "CloneOption", depth: 1, noTags: false, reference: "", shallow: true
]
],
submoduleCfg: [],
userRemoteConfigs: scm.userRemoteConfigs
])
}
def CheckoutRepo(boolean disableSubmodules = false) {
dir(ENGINE_REPOSITORY_NAME) {
palSh('git lfs uninstall', 'Git LFS Uninstall') // Prevent git from pulling lfs objects during checkout
if(fileExists('.git')) {
// If the repository after checkout is locked, likely we took a snapshot while git was running,
// to leave the repo in a usable state, garbagecollect. This also helps in situations where
def indexLockFile = '.git/index.lock'
if(fileExists(indexLockFile)) {
palSh('git gc', 'Git GarbageCollect')
}
if(fileExists(indexLockFile)) { // if it is still there, remove it
palRm(indexLockFile)
}
}
}
def random = new Random()
def retryAttempt = 0
retry(5) {
if (retryAttempt > 0) {
sleep random.nextInt(60 * retryAttempt) // Stagger checkouts to prevent HTTP 429 (Too Many Requests) response from CodeCommit
}
retryAttempt = retryAttempt + 1
if(params.PULL_REQUEST_ID) {
// This is a pull request build. Perform merge with destination branch before building.
dir(ENGINE_REPOSITORY_NAME) {
checkout scm: [
$class: 'GitSCM',
branches: scm.branches,
extensions: [
[$class: 'PreBuildMerge', options: [mergeRemote: 'origin', mergeTarget: params.DESTINATION_BRANCH]],
[$class: 'SubmoduleOption', disableSubmodules: disableSubmodules, recursiveSubmodules: true],
[$class: 'CheckoutOption', timeout: 60]
],
userRemoteConfigs: scm.userRemoteConfigs
]
}
} else {
dir(ENGINE_REPOSITORY_NAME) {
checkout scm: [
$class: 'GitSCM',
branches: scm.branches,
extensions: [
[$class: 'SubmoduleOption', disableSubmodules: disableSubmodules, recursiveSubmodules: true],
[$class: 'CheckoutOption', timeout: 60]
],
userRemoteConfigs: scm.userRemoteConfigs
]
}
}
}
// Add folder where we will store the 3rdParty downloads and packages
if(!fileExists('3rdParty')) {
palMkdir('3rdParty')
}
dir(ENGINE_REPOSITORY_NAME) {
// Run lfs in a separate step. Jenkins is unable to load the credentials for the custom LFS endpoint
withCredentials([usernamePassword(credentialsId: "${env.GITHUB_USER}", passwordVariable: 'accesstoken', usernameVariable: 'username')]) {
SetLfsCredentials("git config -f .lfsconfig lfs.url https://${username}:${accesstoken}@${env.LFS_URL}", 'Set credentials')
}
palSh('git lfs install', 'Git LFS Install')
palSh('git lfs pull', 'Git LFS Pull')
// CHANGE_ID is used by some scripts to identify uniquely the current change (usually metric jobs)
palSh('git rev-parse HEAD > commitid', 'Getting commit id')
env.CHANGE_ID = readFile file: 'commitid'
env.CHANGE_ID = env.CHANGE_ID.trim()
palRm('commitid')
}
}
def PreBuildCommonSteps(Map pipelineConfig, String projectName, String pipeline, String branchName, String platform, String buildType, String workspace, boolean mount = true, boolean disableSubmodules = false) {
echo 'Starting pre-build common steps...'
if (mount) {
unstash name: 'incremental_build_script'
def pythonCmd = ''
if(env.IS_UNIX) pythonCmd = 'sudo -E python -u '
else pythonCmd = 'python -u '
if(env.RECREATE_VOLUME.toBoolean()) {
palSh("${pythonCmd} ${INCREMENTAL_BUILD_SCRIPT_PATH} --action delete --project ${projectName} --pipeline ${pipeline} --branch ${branchName} --platform ${platform} --build_type ${buildType}", 'Deleting volume')
}
timeout(5) {
palSh("${pythonCmd} ${INCREMENTAL_BUILD_SCRIPT_PATH} --action mount --project ${projectName} --pipeline ${pipeline} --branch ${branchName} --platform ${platform} --build_type ${buildType}", 'Mounting volume')
}
if(env.IS_UNIX) {
sh label: 'Setting volume\'s ownership',
script: """
if sudo test ! -d "${workspace}"; then
sudo mkdir -p ${workspace}
cd ${workspace}/..
sudo chown -R lybuilder:root .
fi
"""
}
}
// Cleanup previous repo location, we are currently at the root of the workspace, if we have a .git folder
// we need to cleanup. Once all branches take this relocation, we can remove this
if(env.CLEAN_WORKSPACE.toBoolean() || fileExists("${workspace}/.git")) {
if(fileExists(workspace)) {
palRmDir(workspace)
}
}
dir(workspace) {
CheckoutRepo(disableSubmodules)
// Get python
dir(ENGINE_REPOSITORY_NAME) {
if(env.IS_UNIX) {
sh label: 'Getting python',
script: 'python/get_python.sh'
} else {
bat label: 'Getting python',
script: 'python/get_python.bat'
}
if(env.CLEAN_OUTPUT_DIRECTORY.toBoolean() || env.CLEAN_ASSETS.toBoolean()) {
def command = "${pipelineConfig.BUILD_ENTRY_POINT} --platform ${platform} --type clean"
if (env.IS_UNIX) {
sh label: "Running ${platform} clean",
script: "${pipelineConfig.PYTHON_DIR}/python.sh -u ${command}"
} else {
bat label: "Running ${platform} clean",
script: "${pipelineConfig.PYTHON_DIR}/python.cmd -u ${command}".replace('/','\\')
}
}
}
}
}
def Build(Map options, String platform, String type, String workspace) {
def command = "${options.BUILD_ENTRY_POINT} --platform ${platform} --type ${type}"
dir("${workspace}/${ENGINE_REPOSITORY_NAME}") {
if (env.IS_UNIX) {
sh label: "Running ${platform} ${type}",
script: "${options.PYTHON_DIR}/python.sh -u ${command}"
} else {
bat label: "Running ${platform} ${type}",
script: "${options.PYTHON_DIR}/python.cmd -u ${command}".replace('/','\\')
}
}
}
def TestMetrics(Map options, String workspace, String branchName, String repoName, String buildJobName, String outputDirectory, String configuration) {
catchError(buildResult: null, stageResult: null) {
def cmakeBuildDir = [workspace, ENGINE_REPOSITORY_NAME, outputDirectory].join('/')
dir("${workspace}/${ENGINE_REPOSITORY_NAME}") {
checkout scm: [
$class: 'GitSCM',
branches: [[name: '*/main']],
extensions: [[$class: 'RelativeTargetDirectory', relativeTargetDir: 'mars']],
userRemoteConfigs: [[url: "${env.MARS_REPO}", name: 'mars', credentialsId: "${env.GITHUB_USER}"]]
]
withCredentials([usernamePassword(credentialsId: "${env.SERVICE_USER}", passwordVariable: 'apitoken', usernameVariable: 'username')]) {
def command = "${options.PYTHON_DIR}/python.cmd -u mars/scripts/python/ctest_test_metric_scraper.py -e jenkins.creds.user ${username} -e jenkins.creds.pass ${apitoken} ${cmakeBuildDir} ${branchName} %BUILD_NUMBER% AR ${configuration} ${repoName} "
bat label: "Publishing ${buildJobName} Test Metrics",
script: command
}
}
}
}
def PostBuildCommonSteps(String workspace, boolean mount = true) {
echo 'Starting post-build common steps...'
if(params.PULL_REQUEST_ID) {
dir("${workspace}/${ENGINE_REPOSITORY_NAME}") {
if(fileExists('.git')) {
palSh('git reset --hard HEAD', 'Discard PR merge, git reset')
}
}
}
if (mount) {
def pythonCmd = ''
if(env.IS_UNIX) pythonCmd = 'sudo -E python -u '
else pythonCmd = 'python -u '
try {
timeout(5) {
palSh("${pythonCmd} ${INCREMENTAL_BUILD_SCRIPT_PATH} --action unmount", 'Unmounting volume')
}
} catch (Exception e) {
echo "Unmount script error ${e}"
}
}
}
def CreateSetupStage(Map pipelineConfig, String projectName, String pipelineName, String branchName, String platformName, String jobName, Map environmentVars) {
return {
stage("Setup") {
PreBuildCommonSteps(pipelineConfig, projectName, pipelineName, branchName, platformName, jobName, environmentVars['WORKSPACE'], environmentVars['MOUNT_VOLUME'])
}
}
}
def CreateBuildStage(Map pipelineConfig, String platformName, String jobName, Map environmentVars) {
return {
stage("${jobName}") {
Build(pipelineConfig, platformName, jobName, environmentVars['WORKSPACE'])
}
}
}
def CreateTestMetricsStage(Map pipelineConfig, String branchName, Map environmentVars, String buildJobName, String outputDirectory, String configuration) {
return {
stage("${buildJobName}_metrics") {
TestMetrics(pipelineConfig, environmentVars['WORKSPACE'], branchName, env.DEFAULT_REPOSITORY_NAME, buildJobName, outputDirectory, configuration)
}
}
}
def CreateTeardownStage(Map environmentVars) {
return {
stage("Teardown") {
PostBuildCommonSteps(environmentVars['WORKSPACE'], environmentVars['MOUNT_VOLUME'])
}
}
}
def projectName = ''
def pipelineName = ''
def branchName = ''
def pipelineConfig = {}
// Start Pipeline
try {
stage('Setup Pipeline') {
node('controller') {
def envVarList = []
if(isUnix()) {
envVarList.add('IS_UNIX=1')
}
withEnv(envVarList) {
timestamps {
(projectName, pipelineName) = GetRunningPipelineName(env.JOB_NAME) // env.JOB_NAME is the name of the job given by Jenkins
scmType = GetSCMType()
if(env.BRANCH_NAME) {
branchName = env.BRANCH_NAME
} else {
branchName = scm.branches[0].name // for non-multibranch pipelines
env.BRANCH_NAME = branchName // so scripts that read this environment have it (e.g. incremental_build_util.py)
}
pipelineProperties.add(disableConcurrentBuilds())
echo "Running \"${pipelineName}\" for \"${branchName}\"..."
if (scmType == 'github') {
CheckoutBootstrapScripts(branchName)
}
// Load configs
pipelineConfig = LoadPipelineConfig(pipelineName, branchName, scmType)
// Add each platform as a parameter that the user can disable if needed
pipelineConfig.platforms.each { platform ->
pipelineParameters.add(booleanParam(defaultValue: true, description: '', name: platform.key))
}
pipelineProperties.add(parameters(pipelineParameters))
properties(pipelineProperties)
// Stash the INCREMENTAL_BUILD_SCRIPT_PATH since all nodes will use it
if (scmType == 'codecommit') {
PullFilesFromGit(INCREMENTAL_BUILD_SCRIPT_PATH, branchName, true, ENGINE_REPOSITORY_NAME)
}
stash name: 'incremental_build_script',
includes: INCREMENTAL_BUILD_SCRIPT_PATH
}
}
}
}
if(env.BUILD_NUMBER == '1') {
// Exit pipeline early on the intial build. This allows Jenkins to load the pipeline for the branch and enables users
// to select build parameters on their first actual build. See https://issues.jenkins.io/browse/JENKINS-41929
currentBuild.result = 'SUCCESS'
return
}
// Build and Post-Build Testing Stage
def buildConfigs = [:]
// Platform Builds run on EC2
pipelineConfig.platforms.each { platform ->
platform.value.build_types.each { build_job ->
if (IsJobEnabled(build_job, pipelineName, platform.key)) { // User can filter jobs, jobs are tagged by pipeline
def envVars = GetBuildEnvVars(platform.value.PIPELINE_ENV ?: EMPTY_JSON, build_job.value.PIPELINE_ENV ?: EMPTY_JSON, pipelineName)
envVars['JOB_NAME'] = "${branchName}_${platform.key}_${build_job.key}" // backwards compatibility, some scripts rely on this
def nodeLabel = envVars['NODE_LABEL']
buildConfigs["${platform.key} [${build_job.key}]"] = {
node("${nodeLabel}") {
if(isUnix()) { // Has to happen inside a node
envVars['IS_UNIX'] = 1
}
withEnv(GetEnvStringList(envVars)) {
timeout(time: envVars['TIMEOUT'], unit: 'MINUTES', activity: true) {
try {
def build_job_name = build_job.key
CreateSetupStage(pipelineConfig, projectName, pipelineName, branchName, platform.key, build_job.key, envVars).call()
if(build_job.value.steps) { //this is a pipe with many steps so create all the build stages
build_job.value.steps.each { build_step ->
build_job_name = build_step
CreateBuildStage(pipelineConfig, platform.key, build_step, envVars).call()
}
} else {
CreateBuildStage(pipelineConfig, platform.key, build_job.key, envVars).call()
}
if (env.MARS_REPO && platform.key == 'Windows' && build_job_name.startsWith('test')) {
def output_directory = platform.value.build_types[build_job_name].PARAMETERS.OUTPUT_DIRECTORY
def configuration = platform.value.build_types[build_job_name].PARAMETERS.CONFIGURATION
CreateTestMetricsStage(pipelineConfig, branchName, envVars, build_job_name, output_directory, configuration).call()
}
}
catch(Exception e) {
// https://github.com/jenkinsci/jenkins/blob/master/core/src/main/java/hudson/model/Result.java
// {SUCCESS,UNSTABLE,FAILURE,NOT_BUILT,ABORTED}
def currentResult = envVars['ON_FAILURE_MARK'] ?: 'FAILURE'
if (currentResult == 'FAILURE') {
currentBuild.result = 'FAILURE'
error "FAILURE: ${e}"
} else if (currentResult == 'UNSTABLE') {
currentBuild.result = 'UNSTABLE'
unstable(message: "UNSTABLE: ${e}")
}
}
finally {
CreateTeardownStage(envVars).call()
}
}
}
}
}
}
}
}
timestamps {
stage('Build') {
parallel buildConfigs // Run parallel builds
}
echo 'All builds successful'
}
}
catch(Exception e) {
error "Exception: ${e}"
}
finally {
try {
if(env.SNS_TOPIC) {
snsPublish(
topicArn: env.SNS_TOPIC,
subject:'Build Result',
message:"${currentBuild.currentResult}:${params.REPOSITORY_NAME}:${params.SOURCE_BRANCH}:${params.SOURCE_COMMIT}:${params.DESTINATION_COMMIT}:${params.PULL_REQUEST_ID}:${BUILD_URL}:${env.RECREATE_VOLUME}:${env.CLEAN_OUTPUT_DIRECTORY}:${env.CLEAN_ASSETS}"
)
}
step([
$class: 'Mailer',
notifyEveryUnstableBuild: true,
sendToIndividuals: true,
recipients: emailextrecipients([
[$class: 'CulpritsRecipientProvider'],
[$class: 'RequesterRecipientProvider']
])
])
} catch(Exception e) {
}
}

@ -1,12 +0,0 @@
{
"BUILD_ENTRY_POINT": "Tools/build/JenkinsScripts/build/ci_build.py",
"PIPELINE_CONFIGS": [
"Tools/build/JenkinsScripts/build/Platform/*/pipeline.json",
"restricted/*/Tools/build/JenkinsScripts/build/pipeline.json"
],
"BUILD_CONFIGS": [
"Tools/build/JenkinsScripts/build/Platform/*/build_config.json",
"restricted/*/Tools/build/JenkinsScripts/build/build_config.json"
],
"PYTHON_DIR": "python"
}

@ -9,12 +9,38 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# #
file(READ "${CMAKE_CURRENT_LIST_DIR}/project.json" project_json) #! Adds the --project-path argument to the VS IDE debugger command arguments
function(add_vs_debugger_arguments)
# Inject the project root into the --project-path argument into the Visual Studio Debugger arguments by defaults
list(APPEND app_targets AutomatedTesting.GameLauncher AutomatedTesting.ServerLauncher)
list(APPEND app_targets AssetBuilder AssetProcessor AssetProcessorBatch Editor)
foreach(app_target IN LISTS app_targets)
if (TARGET ${app_target})
set_property(TARGET ${app_target} APPEND PROPERTY VS_DEBUGGER_COMMAND_ARGUMENTS "--project-path=\"${CMAKE_CURRENT_LIST_DIR}\"")
endif()
endforeach()
endfunction()
string(JSON project_target_name ERROR_VARIABLE json_error GET ${project_json} "project_name") if(NOT PROJECT_NAME)
if(${json_error}) cmake_minimum_required(VERSION 3.19)
message(FATAL_ERROR "Unable to read key 'project_name' from 'project.json'") project(AutomatedTesting
endif() LANGUAGES C CXX
VERSION 1.0.0.0
)
include(EngineFinder.cmake OPTIONAL)
find_package(o3de REQUIRED)
o3de_initialize()
add_vs_debugger_arguments()
else()
# Add the project_name to global LY_PROJECTS_TARGET_NAME property
file(READ "${CMAKE_CURRENT_LIST_DIR}/project.json" project_json)
set_property(GLOBAL APPEND PROPERTY LY_PROJECTS_TARGET_NAME ${project_target_name}) string(JSON project_target_name ERROR_VARIABLE json_error GET ${project_json} "project_name")
add_subdirectory(Gem) if(json_error)
message(FATAL_ERROR "Unable to read key 'project_name' from 'project.json'")
endif()
set_property(GLOBAL APPEND PROPERTY LY_PROJECTS_TARGET_NAME ${project_target_name})
add_subdirectory(Gem)
endif()

@ -42,7 +42,6 @@ set(GEM_DEPENDENCIES
Gem::SurfaceData Gem::SurfaceData
Gem::GradientSignal Gem::GradientSignal
Gem::Vegetation Gem::Vegetation
Gem::Atom_RHI.Private Gem::Atom_RHI.Private
Gem::Atom_RPI.Private Gem::Atom_RPI.Private
Gem::Atom_Feature_Common Gem::Atom_Feature_Common
@ -54,4 +53,5 @@ set(GEM_DEPENDENCIES
Gem::ImguiAtom Gem::ImguiAtom
Gem::Atom_AtomBridge Gem::Atom_AtomBridge
Gem::AtomFont Gem::AtomFont
Gem::Blast
) )

@ -68,4 +68,5 @@ set(GEM_DEPENDENCIES
Gem::ImguiAtom Gem::ImguiAtom
Gem::AtomFont Gem::AtomFont
Gem::AtomToolsFramework.Editor Gem::AtomToolsFramework.Editor
Gem::Blast.Editor
) )

@ -27,28 +27,28 @@ from base import TestAutomationBase
class TestAutomation(TestAutomationBase): class TestAutomation(TestAutomationBase):
def test_ActorSplitsAfterCollision(self, request, workspace, editor, launcher_platform): def test_ActorSplitsAfterCollision(self, request, workspace, editor, launcher_platform):
from . import ActorSplitsAfterCollision as test_module from . import ActorSplitsAfterCollision as test_module
self._run_test(request, workspace, editor, test_module, expected_lines=[], unexpected_lines=["Assert"]) self._run_test(request, workspace, editor, test_module)
def test_ActorSplitsAfterRadialDamage(self, request, workspace, editor, launcher_platform): def test_ActorSplitsAfterRadialDamage(self, request, workspace, editor, launcher_platform):
from . import ActorSplitsAfterRadialDamage as test_module from . import ActorSplitsAfterRadialDamage as test_module
self._run_test(request, workspace, editor, test_module, expected_lines=[], unexpected_lines=["Assert"]) self._run_test(request, workspace, editor, test_module)
def test_ActorSplitsAfterCapsuleDamage(self, request, workspace, editor, launcher_platform): def test_ActorSplitsAfterCapsuleDamage(self, request, workspace, editor, launcher_platform):
from . import ActorSplitsAfterCapsuleDamage as test_module from . import ActorSplitsAfterCapsuleDamage as test_module
self._run_test(request, workspace, editor, test_module, expected_lines=[], unexpected_lines=["Assert"]) self._run_test(request, workspace, editor, test_module)
def test_ActorSplitsAfterImpactSpreadDamage(self, request, workspace, editor, launcher_platform): def test_ActorSplitsAfterImpactSpreadDamage(self, request, workspace, editor, launcher_platform):
from . import ActorSplitsAfterImpactSpreadDamage as test_module from . import ActorSplitsAfterImpactSpreadDamage as test_module
self._run_test(request, workspace, editor, test_module, expected_lines=[], unexpected_lines=["Assert"]) self._run_test(request, workspace, editor, test_module)
def test_ActorSplitsAfterShearDamage(self, request, workspace, editor, launcher_platform): def test_ActorSplitsAfterShearDamage(self, request, workspace, editor, launcher_platform):
from . import ActorSplitsAfterShearDamage as test_module from . import ActorSplitsAfterShearDamage as test_module
self._run_test(request, workspace, editor, test_module, expected_lines=[], unexpected_lines=["Assert"]) self._run_test(request, workspace, editor, test_module)
def test_ActorSplitsAfterTriangleDamage(self, request, workspace, editor, launcher_platform): def test_ActorSplitsAfterTriangleDamage(self, request, workspace, editor, launcher_platform):
from . import ActorSplitsAfterTriangleDamage as test_module from . import ActorSplitsAfterTriangleDamage as test_module
self._run_test(request, workspace, editor, test_module, expected_lines=[], unexpected_lines=["Assert"]) self._run_test(request, workspace, editor, test_module)
def test_ActorSplitsAfterStressDamage(self, request, workspace, editor, launcher_platform): def test_ActorSplitsAfterStressDamage(self, request, workspace, editor, launcher_platform):
from . import ActorSplitsAfterStressDamage as test_module from . import ActorSplitsAfterStressDamage as test_module
self._run_test(request, workspace, editor, test_module, expected_lines=[], unexpected_lines=["Assert"]) self._run_test(request, workspace, editor, test_module)

@ -135,20 +135,18 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS)
endif() endif()
## Blast ## ## Blast ##
# Disabled until AutomatedTesting runs with Atom. if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS)
# if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS) ly_add_pytest(
# ly_add_pytest( NAME AutomatedTesting::BlastTests
# NAME AutomatedTesting::BlastTests TEST_SERIAL TRUE
# TEST_SERIAL TRUE PATH ${CMAKE_CURRENT_LIST_DIR}/Blast/TestSuite_Active.py
# PATH ${CMAKE_CURRENT_LIST_DIR}/Blast/TestSuite_Active.py TIMEOUT 3600
# TIMEOUT 500 RUNTIME_DEPENDENCIES
# RUNTIME_DEPENDENCIES Legacy::Editor
# Legacy::Editor AZ::AssetProcessor
# Legacy::CryRenderNULL AutomatedTesting.Assets
# AZ::AssetProcessor )
# AutomatedTesting.Assets endif()
# )
# endif()
############# #############

@ -0,0 +1,28 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
// Please read README.md for an explanation on why scenesrg.srgi and viewsrg.srgi are
// located in this folder (And how you can optionally customize your own scenesrg.srgi
// and viewsrg.srgi in your game project).
#include <Atom/Features/SrgSemantics.azsli>
partial ShaderResourceGroup RayTracingSceneSrg : SRG_RayTracingScene
{
/* Intentionally Empty. Helps define the SrgSemantic for RayTracingSceneSrg once.*/
};
#define AZ_COLLECTING_PARTIAL_SRGS
#include <Atom/Feature/Common/Assets/ShaderResourceGroups/RayTracingSceneSrgAll.azsli>
#undef AZ_COLLECTING_PARTIAL_SRGS

@ -1,6 +1,6 @@
<ObjectStream version="3"> <ObjectStream version="3">
<Class name="BlastGlobalConfiguration" version="1" type="{0B9DB6DD-0008-4EF6-9D75-141061144353}"> <Class name="BlastGlobalConfiguration" version="1" type="{0B9DB6DD-0008-4EF6-9D75-141061144353}">
<Class name="Asset" field="BlastMaterialLibrary" value="id={251AC171-6B9C-562D-A235-4EF5E1AE6871}:0,type={55F38C86-0767-4E7F-830A-A4BF624BE4DA},hint={assets/destruction/automated_testing.blastmaterial}" version="1" type="{77A19D40-8731-4D3C-9041-1B43047366A4}"/> <Class name="Asset" field="BlastMaterialLibrary" value="id={251AC171-6B9C-562D-A235-4EF5E1AE6871}:0,type={55F38C86-0767-4E7F-830A-A4BF624BE4DA},hint={assets/destruction/automated_testing.blastmaterial},loadBehavior=1" version="2" type="{77A19D40-8731-4D3C-9041-1B43047366A4}"/>
<Class name="unsigned int" field="StressSolverIterations" value="180" type="{43DA906B-7DEF-4CA8-9790-854106D3F983}"/> <Class name="unsigned int" field="StressSolverIterations" value="180" type="{43DA906B-7DEF-4CA8-9790-854106D3F983}"/>
</Class> </Class>
</ObjectStream> </ObjectStream>

@ -922,7 +922,7 @@ void CVars::Init()
"Will not render CGFs past the given amount of drawcalls\n" "Will not render CGFs past the given amount of drawcalls\n"
"(<=0 off (default), >0 draw calls limit)"); "(<=0 off (default), >0 draw calls limit)");
REGISTER_CVAR(e_CheckOctreeObjectsBoxSize, 1, VF_NULL, "CryWarning for crazy sized COctreeNode m_objectsBoxes"); REGISTER_CVAR(e_CheckOctreeObjectsBoxSize, 1, VF_NULL, "Warning for crazy sized COctreeNode m_objectsBoxes");
REGISTER_CVAR(e_DebugGeomPrep, 0, VF_NULL, "enable logging of Geom preparation"); REGISTER_CVAR(e_DebugGeomPrep, 0, VF_NULL, "enable logging of Geom preparation");
DefineConstIntCVar(e_GeomCaches, 1, VF_NULL, "Activates drawing of geometry caches"); DefineConstIntCVar(e_GeomCaches, 1, VF_NULL, "Activates drawing of geometry caches");
REGISTER_CVAR(e_GeomCacheBufferSize, 128, VF_CHEAT, "Geometry cache stream buffer upper limit size in MB. Default: 128"); REGISTER_CVAR(e_GeomCacheBufferSize, 128, VF_CHEAT, "Geometry cache stream buffer upper limit size in MB. Default: 128");

@ -35,7 +35,7 @@ namespace AZ
} }
AZ::OSString msgBoxMessage; AZ::OSString msgBoxMessage;
msgBoxMessage.append("CrySystem could not initialize correctly for the following reason(s):"); msgBoxMessage.append("O3DE could not initialize correctly for the following reason(s):");
for (const AZ::OSString& errMsg : m_errorStringsCollected) for (const AZ::OSString& errMsg : m_errorStringsCollected)
{ {
@ -47,7 +47,7 @@ namespace AZ
Trace::Output(nullptr, msgBoxMessage.c_str()); Trace::Output(nullptr, msgBoxMessage.c_str());
Trace::Output(nullptr, "\n==================================================================\n"); Trace::Output(nullptr, "\n==================================================================\n");
EBUS_EVENT(AZ::NativeUI::NativeUIRequestBus, DisplayOkDialog, "CrySystem Initialization Failed", msgBoxMessage.c_str(), false); EBUS_EVENT(AZ::NativeUI::NativeUIRequestBus, DisplayOkDialog, "O3DE Initialization Failed", msgBoxMessage.c_str(), false);
} }
} // namespace Debug } // namespace Debug
} // namespace AZ } // namespace AZ

@ -605,7 +605,7 @@ void CSystem::DebugStats([[maybe_unused]] bool checkpoint, [[maybe_unused]] bool
{ {
if (!dbgmodules[i].handle) if (!dbgmodules[i].handle)
{ {
CryLogAlways("WARNING: <CrySystem> CSystem::DebugStats: NULL handle for %s", dbgmodules[i].name.c_str()); CryLogAlways("WARNING: CSystem::DebugStats: NULL handle for %s", dbgmodules[i].name.c_str());
nolib++; nolib++;
continue; continue;
} }
@ -642,7 +642,7 @@ void CSystem::DebugStats([[maybe_unused]] bool checkpoint, [[maybe_unused]] bool
} }
else else
{ {
CryLogAlways("WARNING: <CrySystem> CSystem::DebugStats: could not retrieve function from DLL %s", dbgmodules[i].name.c_str()); CryLogAlways("WARNING: CSystem::DebugStats: could not retrieve function from DLL %s", dbgmodules[i].name.c_str());
nolib++; nolib++;
}; };
#endif #endif
@ -1066,7 +1066,7 @@ void CSystem::FatalError(const char* format, ...)
if (szSysErrorMessage) if (szSysErrorMessage)
{ {
CryLogAlways("<CrySystem> Last System Error: %s", szSysErrorMessage); CryLogAlways("Last System Error: %s", szSysErrorMessage);
} }
if (GetUserCallback()) if (GetUserCallback())

@ -1117,7 +1117,7 @@ namespace AZ
return asset; return asset;
} }
void AssetManager::UpdateDebugStatus(AZ::Data::Asset<AZ::Data::AssetData> asset) void AssetManager::UpdateDebugStatus(const AZ::Data::Asset<AZ::Data::AssetData>& asset)
{ {
if(!m_debugAssetEvents) if(!m_debugAssetEvents)
{ {

@ -358,7 +358,7 @@ namespace AZ
Asset<AssetData> GetAssetInternal(const AssetId& assetId, const AssetType& assetType, AssetLoadBehavior assetReferenceLoadBehavior, const AssetLoadParameters& loadParams = AssetLoadParameters{}, AssetInfo assetInfo = AssetInfo(), bool signalLoaded = false); Asset<AssetData> GetAssetInternal(const AssetId& assetId, const AssetType& assetType, AssetLoadBehavior assetReferenceLoadBehavior, const AssetLoadParameters& loadParams = AssetLoadParameters{}, AssetInfo assetInfo = AssetInfo(), bool signalLoaded = false);
void UpdateDebugStatus(AZ::Data::Asset<AZ::Data::AssetData> asset); void UpdateDebugStatus(const AZ::Data::Asset<AZ::Data::AssetData>& asset);
/** /**
* Gets a root asset and dependencies as individual async loads if necessary. * Gets a root asset and dependencies as individual async loads if necessary.

@ -19,9 +19,6 @@ namespace AZ
{ {
class Vector3; class Vector3;
//! Do not allow the scale to be zero to avoid problems with inverting scale.
static constexpr float MinNonUniformScale = 1e-3f;
using NonUniformScaleChangedEvent = AZ::Event<const AZ::Vector3&>; using NonUniformScaleChangedEvent = AZ::Event<const AZ::Vector3&>;
//! Requests for working with non-uniform scale. //! Requests for working with non-uniform scale.

@ -26,7 +26,7 @@ namespace AZ
{ {
class Transform; class Transform;
using TransformChangedEvent = Event<Transform, Transform>; using TransformChangedEvent = Event<const Transform&, const Transform&>;
using ParentChangedEvent = Event<EntityId, EntityId>; using ParentChangedEvent = Event<EntityId, EntityId>;

@ -42,9 +42,8 @@ namespace AZ
} }
} }
#define AZ_TRACE_METHOD_NAME_CATEGORY(name, category) AZ::Debug::EventTrace::ScopedSlice AZ_JOIN(ScopedSlice__, __LINE__)(name, category);
#ifdef AZ_PROFILE_TELEMETRY #ifdef AZ_PROFILE_TELEMETRY
# define AZ_TRACE_METHOD_NAME_CATEGORY(name, category) AZ::Debug::EventTrace::ScopedSlice AZ_JOIN(ScopedSlice__, __LINE__)(name, category);
# define AZ_TRACE_METHOD_NAME(name) \ # define AZ_TRACE_METHOD_NAME(name) \
AZ_TRACE_METHOD_NAME_CATEGORY(name, "") \ AZ_TRACE_METHOD_NAME_CATEGORY(name, "") \
AZ_PROFILE_SCOPE(AZ::Debug::ProfileCategory::AzTrace, name) AZ_PROFILE_SCOPE(AZ::Debug::ProfileCategory::AzTrace, name)
@ -53,6 +52,7 @@ namespace AZ
AZ_TRACE_METHOD_NAME_CATEGORY(AZ_FUNCTION_SIGNATURE, "") \ AZ_TRACE_METHOD_NAME_CATEGORY(AZ_FUNCTION_SIGNATURE, "") \
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzTrace) AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzTrace)
#else #else
# define AZ_TRACE_METHOD_NAME_CATEGORY(name, category)
# define AZ_TRACE_METHOD_NAME(name) AZ_TRACE_METHOD_NAME_CATEGORY(name, "") # define AZ_TRACE_METHOD_NAME(name) AZ_TRACE_METHOD_NAME_CATEGORY(name, "")
# define AZ_TRACE_METHOD() AZ_TRACE_METHOD_NAME(AZ_FUNCTION_SIGNATURE) # define AZ_TRACE_METHOD() AZ_TRACE_METHOD_NAME(AZ_FUNCTION_SIGNATURE)
#endif #endif

@ -38,6 +38,13 @@ namespace AZ
bool CompareValueData(const void* lhs, const void* rhs) override; bool CompareValueData(const void* lhs, const void* rhs) override;
}; };
//! Limits for transform scale values.
//! The scale should not be zero to avoid problems with inverting.
//! @{
static constexpr float MinTransformScale = 1e-2f;
static constexpr float MaxTransformScale = 1e9f;
//! @}
//! The basic transformation class, represented using a quaternion rotation, vector scale and vector translation. //! The basic transformation class, represented using a quaternion rotation, vector scale and vector translation.
//! By design, cannot represent skew transformations. //! By design, cannot represent skew transformations.
class Transform class Transform

@ -0,0 +1,59 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/UnitTest/UnitTest.h>
#include <AzCore/Settings/SettingsRegistry.h>
#include <gmock/gmock.h>
namespace AZ
{
class MockSettingsRegistry;
using NiceSettingsRegistrySimpleMock = ::testing::NiceMock<MockSettingsRegistry>;
class MockSettingsRegistry
: public AZ::SettingsRegistryInterface
{
public:
MOCK_CONST_METHOD1(GetType, Type(AZStd::string_view));
MOCK_CONST_METHOD2(Visit, bool(Visitor&, AZStd::string_view));
MOCK_CONST_METHOD2(Visit, bool(const VisitorCallback&, AZStd::string_view));
MOCK_METHOD1(RegisterNotifier, NotifyEventHandler(const NotifyCallback&));
MOCK_METHOD1(RegisterNotifier, NotifyEventHandler(NotifyCallback&&));
MOCK_CONST_METHOD2(Get, bool(bool&, AZStd::string_view));
MOCK_CONST_METHOD2(Get, bool(s64&, AZStd::string_view));
MOCK_CONST_METHOD2(Get, bool(u64&, AZStd::string_view));
MOCK_CONST_METHOD2(Get, bool(double&, AZStd::string_view));
MOCK_CONST_METHOD2(Get, bool(AZStd::string&, AZStd::string_view));
MOCK_CONST_METHOD2(Get, bool(FixedValueString&, AZStd::string_view));
MOCK_CONST_METHOD3(GetObject, bool(void*, Uuid, AZStd::string_view));
MOCK_METHOD2(Set, bool(AZStd::string_view, bool));
MOCK_METHOD2(Set, bool(AZStd::string_view, s64));
MOCK_METHOD2(Set, bool(AZStd::string_view, u64));
MOCK_METHOD2(Set, bool(AZStd::string_view, double));
MOCK_METHOD2(Set, bool(AZStd::string_view, AZStd::string_view));
MOCK_METHOD2(Set, bool(AZStd::string_view, const char*));
MOCK_METHOD3(SetObject, bool(AZStd::string_view, const void*, Uuid));
MOCK_METHOD1(Remove, bool(AZStd::string_view));
MOCK_METHOD3(MergeCommandLineArgument, bool(AZStd::string_view, AZStd::string_view, const CommandLineArgumentSettings&));
MOCK_METHOD2(MergeSettings, bool(AZStd::string_view, Format));
MOCK_METHOD4(MergeSettingsFile, bool(AZStd::string_view, Format, AZStd::string_view, AZStd::vector<char>*));
MOCK_METHOD5(
MergeSettingsFolder,
bool(AZStd::string_view, const Specializations&, AZStd::string_view, AZStd::string_view, AZStd::vector<char>*));
};
} // namespace AZ

@ -15,4 +15,5 @@ set(FILES
UnitTest/UnitTest.h UnitTest/UnitTest.h
UnitTest/TestTypes.h UnitTest/TestTypes.h
UnitTest/Mocks/MockFileIOBase.h UnitTest/Mocks/MockFileIOBase.h
UnitTest/Mocks/MockSettingsRegistry.h
) )

@ -12,6 +12,7 @@
#include <AzFramework/Components/NonUniformScaleComponent.h> #include <AzFramework/Components/NonUniformScaleComponent.h>
#include <AzCore/Serialization/SerializeContext.h> #include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Math/Transform.h>
#include <AzCore/Math/ToString.h> #include <AzCore/Math/ToString.h>
#include <AzCore/Component/Entity.h> #include <AzCore/Component/Entity.h>
@ -81,13 +82,13 @@ namespace AzFramework
void NonUniformScaleComponent::SetScale(const AZ::Vector3& scale) void NonUniformScaleComponent::SetScale(const AZ::Vector3& scale)
{ {
if (scale.GetMinElement() >= AZ::MinNonUniformScale) if (scale.GetMinElement() >= AZ::MinTransformScale && scale.GetMaxElement() <= AZ::MaxTransformScale)
{ {
m_scale = scale; m_scale = scale;
} }
else else
{ {
AZ::Vector3 clampedScale = scale.GetMax(AZ::Vector3(AZ::MinNonUniformScale)); AZ::Vector3 clampedScale = scale.GetClamp(AZ::Vector3(AZ::MinTransformScale), AZ::Vector3(AZ::MaxTransformScale));
AZ_Warning("Non-uniform Scale Component", false, "SetScale value was clamped from %s to %s for entity %s", AZ_Warning("Non-uniform Scale Component", false, "SetScale value was clamped from %s to %s for entity %s",
AZ::ToString(scale).c_str(), AZ::ToString(clampedScale).c_str(), GetEntity()->GetName().c_str()); AZ::ToString(scale).c_str(), AZ::ToString(clampedScale).c_str(), GetEntity()->GetName().c_str());
m_scale = clampedScale; m_scale = clampedScale;

@ -114,6 +114,9 @@ namespace AzNetworking
void ClearUnusedBits(); void ClearUnusedBits();
ContainerType m_container; ContainerType m_container;
template <AZStd::size_t, typename ElementType>
friend class FixedSizeVectorBitset;
}; };
} }

@ -192,19 +192,11 @@ namespace AzNetworking
template <AZStd::size_t CAPACITY, typename ElementType> template <AZStd::size_t CAPACITY, typename ElementType>
inline void FixedSizeVectorBitset<CAPACITY, ElementType>::ClearUnusedBits() inline void FixedSizeVectorBitset<CAPACITY, ElementType>::ClearUnusedBits()
{ {
constexpr ElementType AllOnes = static_cast<ElementType>(~0);
const ElementType LastUsedBits = (GetSize() % BitsetType::ElementTypeBits);
#pragma warning(push)
#pragma warning(disable : 4293) // shift count negative or too big, undefined behaviour
#pragma warning(disable : 6326) // constant constant comparison
const ElementType ShiftAmount = (LastUsedBits == 0) ? 0 : BitsetType::ElementTypeBits - LastUsedBits;
const ElementType ClearBitMask = AllOnes >> ShiftAmount;
#pragma warning(pop)
uint32_t usedElementSize = (GetSize() + BitsetType::ElementTypeBits - 1) / BitsetType::ElementTypeBits; uint32_t usedElementSize = (GetSize() + BitsetType::ElementTypeBits - 1) / BitsetType::ElementTypeBits;
for (uint32_t i = usedElementSize + 1; i < CAPACITY; ++i) for (uint32_t i = usedElementSize + 1; i < BitsetType::ElementCount; ++i)
{ {
m_bitset.GetContainer()[i] = 0; m_bitset.GetContainer()[i] = 0;
} }
m_bitset.GetContainer()[m_bitset.GetContainer().size() - 1] &= ClearBitMask; m_bitset.ClearUnusedBits();
} }
} }

@ -270,7 +270,7 @@ namespace AzNetworking
value.StoreToFloat3(values); value.StoreToFloat3(values);
serializer.Serialize(values[0], "xValue"); serializer.Serialize(values[0], "xValue");
serializer.Serialize(values[1], "yValue"); serializer.Serialize(values[1], "yValue");
serializer.Serialize(values[1], "zValue"); serializer.Serialize(values[2], "zValue");
value = AZ::Vector3::CreateFromFloat3(values); value = AZ::Vector3::CreateFromFloat3(values);
return serializer.IsValid(); return serializer.IsValid();
} }
@ -285,8 +285,8 @@ namespace AzNetworking
value.StoreToFloat4(values); value.StoreToFloat4(values);
serializer.Serialize(values[0], "xValue"); serializer.Serialize(values[0], "xValue");
serializer.Serialize(values[1], "yValue"); serializer.Serialize(values[1], "yValue");
serializer.Serialize(values[1], "zValue"); serializer.Serialize(values[2], "zValue");
serializer.Serialize(values[1], "wValue"); serializer.Serialize(values[3], "wValue");
value = AZ::Vector4::CreateFromFloat4(values); value = AZ::Vector4::CreateFromFloat4(values);
return serializer.IsValid(); return serializer.IsValid();
} }
@ -301,8 +301,8 @@ namespace AzNetworking
value.StoreToFloat4(values); value.StoreToFloat4(values);
serializer.Serialize(values[0], "xValue"); serializer.Serialize(values[0], "xValue");
serializer.Serialize(values[1], "yValue"); serializer.Serialize(values[1], "yValue");
serializer.Serialize(values[1], "zValue"); serializer.Serialize(values[2], "zValue");
serializer.Serialize(values[1], "wValue"); serializer.Serialize(values[3], "wValue");
value = AZ::Quaternion::CreateFromFloat4(values); value = AZ::Quaternion::CreateFromFloat4(values);
return serializer.IsValid(); return serializer.IsValid();
} }

@ -256,6 +256,8 @@ namespace AzToolsFramework
m_userSettings = AZ::UserSettings::CreateFind<AssetEditorWidgetUserSettings>(k_assetEditorWidgetSettings, AZ::UserSettings::CT_LOCAL); m_userSettings = AZ::UserSettings::CreateFind<AssetEditorWidgetUserSettings>(k_assetEditorWidgetSettings, AZ::UserSettings::CT_LOCAL);
UpdateRecentFileListState();
QObject::connect(m_recentFileMenu, &QMenu::aboutToShow, this, &AssetEditorWidget::PopulateRecentMenu); QObject::connect(m_recentFileMenu, &QMenu::aboutToShow, this, &AssetEditorWidget::PopulateRecentMenu);
} }
@ -952,7 +954,8 @@ namespace AzToolsFramework
void AssetEditorWidget::AddRecentPath(const AZStd::string& recentPath) void AssetEditorWidget::AddRecentPath(const AZStd::string& recentPath)
{ {
m_userSettings->AddRecentPath(recentPath); m_userSettings->AddRecentPath(recentPath);
UpdateRecentFileListState();
} }
void AssetEditorWidget::PopulateRecentMenu() void AssetEditorWidget::PopulateRecentMenu()
@ -989,6 +992,21 @@ namespace AzToolsFramework
m_saveAsAssetAction->setEnabled(true); m_saveAsAssetAction->setEnabled(true);
} }
void AssetEditorWidget::UpdateRecentFileListState()
{
if (m_recentFileMenu)
{
if (!m_userSettings || m_userSettings->m_recentPaths.empty())
{
m_recentFileMenu->setEnabled(false);
}
else
{
m_recentFileMenu->setEnabled(true);
}
}
}
} // namespace AssetEditor } // namespace AssetEditor
} // namespace AzToolsFramework } // namespace AzToolsFramework

@ -122,6 +122,8 @@ namespace AzToolsFramework
void OnCatalogAssetAdded(const AZ::Data::AssetId& assetId) override; void OnCatalogAssetAdded(const AZ::Data::AssetId& assetId) override;
void OnCatalogAssetRemoved(const AZ::Data::AssetId& assetId, const AZ::Data::AssetInfo& assetInfo) override; void OnCatalogAssetRemoved(const AZ::Data::AssetId& assetId, const AZ::Data::AssetInfo& assetInfo) override;
void UpdateRecentFileListState();
private: private:
void DirtyAsset(); void DirtyAsset();

@ -58,7 +58,7 @@ namespace AzToolsFramework
m_prefabUndoCache.Destroy(); m_prefabUndoCache.Destroy();
} }
PrefabOperationResult PrefabPublicHandler::CreatePrefab(const AZStd::vector<AZ::EntityId>& entityIds, AZStd::string_view filePath) PrefabOperationResult PrefabPublicHandler::CreatePrefab(const AZStd::vector<AZ::EntityId>& entityIds, AZ::IO::PathView filePath)
{ {
// Retrieve entityList from entityIds // Retrieve entityList from entityIds
EntityList inputEntityList; EntityList inputEntityList;

@ -42,7 +42,7 @@ namespace AzToolsFramework
void UnregisterPrefabPublicHandlerInterface(); void UnregisterPrefabPublicHandlerInterface();
// PrefabPublicInterface... // PrefabPublicInterface...
PrefabOperationResult CreatePrefab(const AZStd::vector<AZ::EntityId>& entityIds, AZStd::string_view filePath) override; PrefabOperationResult CreatePrefab(const AZStd::vector<AZ::EntityId>& entityIds, AZ::IO::PathView filePath) override;
PrefabOperationResult InstantiatePrefab(AZStd::string_view filePath, AZ::EntityId parent, AZ::Vector3 position) override; PrefabOperationResult InstantiatePrefab(AZStd::string_view filePath, AZ::EntityId parent, AZ::Vector3 position) override;
PrefabOperationResult SavePrefab(AZ::IO::Path filePath) override; PrefabOperationResult SavePrefab(AZ::IO::Path filePath) override;
PrefabEntityResult CreateEntity(AZ::EntityId parentId, const AZ::Vector3& position) override; PrefabEntityResult CreateEntity(AZ::EntityId parentId, const AZ::Vector3& position) override;

@ -49,7 +49,7 @@ namespace AzToolsFramework
* @param filePath The path for the new prefab file. * @param filePath The path for the new prefab file.
* @return An outcome object; on failure, it comes with an error message detailing the cause of the error. * @return An outcome object; on failure, it comes with an error message detailing the cause of the error.
*/ */
virtual PrefabOperationResult CreatePrefab(const AZStd::vector<AZ::EntityId>& entityIds, AZStd::string_view filePath) = 0; virtual PrefabOperationResult CreatePrefab(const AZStd::vector<AZ::EntityId>& entityIds, AZ::IO::PathView filePath) = 0;
/** /**
* Instantiate a prefab from a prefab file. * Instantiate a prefab from a prefab file.

@ -13,6 +13,7 @@
#include <AzToolsFramework/ToolsComponents/EditorNonUniformScaleComponent.h> #include <AzToolsFramework/ToolsComponents/EditorNonUniformScaleComponent.h>
#include <AzCore/Serialization/EditContext.h> #include <AzCore/Serialization/EditContext.h>
#include <AzFramework/Components/NonUniformScaleComponent.h> #include <AzFramework/Components/NonUniformScaleComponent.h>
#include <AzCore/Math/Transform.h>
#include <AzCore/Math/ToString.h> #include <AzCore/Math/ToString.h>
namespace AzToolsFramework namespace AzToolsFramework
@ -44,7 +45,9 @@ namespace AzToolsFramework
->DataElement( ->DataElement(
AZ::Edit::UIHandlers::Default, &EditorNonUniformScaleComponent::m_scale, "Non-uniform Scale", AZ::Edit::UIHandlers::Default, &EditorNonUniformScaleComponent::m_scale, "Non-uniform Scale",
"Non-uniform scale for this entity only (does not propagate through hierarchy)") "Non-uniform scale for this entity only (does not propagate through hierarchy)")
->Attribute(AZ::Edit::Attributes::Min, AZ::MinNonUniformScale) ->Attribute(AZ::Edit::Attributes::Min, AZ::MinTransformScale)
->Attribute(AZ::Edit::Attributes::Max, AZ::MaxTransformScale)
->Attribute(AZ::Edit::Attributes::Step, 0.1f)
->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorNonUniformScaleComponent::OnScaleChanged) ->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorNonUniformScaleComponent::OnScaleChanged)
; ;
} }
@ -106,13 +109,13 @@ namespace AzToolsFramework
void EditorNonUniformScaleComponent::SetScale(const AZ::Vector3& scale) void EditorNonUniformScaleComponent::SetScale(const AZ::Vector3& scale)
{ {
if (scale.GetMinElement() >= AZ::MinNonUniformScale) if (scale.GetMinElement() >= AZ::MinTransformScale && scale.GetMaxElement() <= AZ::MaxTransformScale)
{ {
m_scale = scale; m_scale = scale;
} }
else else
{ {
AZ::Vector3 clampedScale = scale.GetMax(AZ::Vector3(AZ::MinNonUniformScale)); AZ::Vector3 clampedScale = scale.GetClamp(AZ::Vector3(AZ::MinTransformScale), AZ::Vector3(AZ::MaxTransformScale));
AZ_Warning("Editor Non-uniform Scale Component", false, "SetScale value was clamped from %s to %s for entity %s", AZ_Warning("Editor Non-uniform Scale Component", false, "SetScale value was clamped from %s to %s for entity %s",
AZ::ToString(scale).c_str(), AZ::ToString(clampedScale).c_str(), GetEntity()->GetName().c_str()); AZ::ToString(scale).c_str(), AZ::ToString(clampedScale).c_str(), GetEntity()->GetName().c_str());
m_scale = clampedScale; m_scale = clampedScale;

@ -1276,7 +1276,6 @@ namespace AzToolsFramework
Attribute(AZ::Edit::Attributes::SliceFlags, AZ::Edit::SliceFlags::NotPushableOnSliceRoot)-> Attribute(AZ::Edit::Attributes::SliceFlags, AZ::Edit::SliceFlags::NotPushableOnSliceRoot)->
DataElement(TransformScaleHandler, &EditorTransform::m_scale, "Scale", "Local Scale")-> DataElement(TransformScaleHandler, &EditorTransform::m_scale, "Scale", "Local Scale")->
Attribute(AZ::Edit::Attributes::Step, 0.1f)-> Attribute(AZ::Edit::Attributes::Step, 0.1f)->
Attribute(AZ::Edit::Attributes::Min, 0.01f)->
Attribute(AZ::Edit::Attributes::ReadOnly, &EditorTransform::m_locked) Attribute(AZ::Edit::Attributes::ReadOnly, &EditorTransform::m_locked)
; ;
} }

@ -12,6 +12,7 @@
#include "AzToolsFramework_precompiled.h" #include "AzToolsFramework_precompiled.h"
#include <ToolsComponents/TransformScalePropertyHandler.h> #include <ToolsComponents/TransformScalePropertyHandler.h>
#include <AzCore/Math/Transform.h>
#include <AzCore/Math/Vector3.h> #include <AzCore/Math/Vector3.h>
namespace AzToolsFramework namespace AzToolsFramework
@ -36,8 +37,8 @@ namespace AzToolsFramework
AzToolsFramework::PropertyEditorGUIMessages::Bus::Broadcast(&AzToolsFramework::PropertyEditorGUIMessages::RequestWrite, newCtrl); AzToolsFramework::PropertyEditorGUIMessages::Bus::Broadcast(&AzToolsFramework::PropertyEditorGUIMessages::RequestWrite, newCtrl);
}); });
newCtrl->setMinimum(0.01f); newCtrl->setMinimum(AZ::MinTransformScale);
newCtrl->setMaximum(std::numeric_limits<float>::max()); newCtrl->setMaximum(AZ::MaxTransformScale);
return newCtrl; return newCtrl;
} }

@ -24,6 +24,7 @@
#include <AzToolsFramework/AssetBrowser/AssetBrowserBus.h> #include <AzToolsFramework/AssetBrowser/AssetBrowserBus.h>
#include <AzToolsFramework/AssetBrowser/AssetSelectionModel.h> #include <AzToolsFramework/AssetBrowser/AssetSelectionModel.h>
#include <AzToolsFramework/AssetBrowser/Entries/SourceAssetBrowserEntry.h> #include <AzToolsFramework/AssetBrowser/Entries/SourceAssetBrowserEntry.h>
#include <AzToolsFramework/Prefab/PrefabLoaderInterface.h>
#include <AzToolsFramework/ToolsComponents/EditorLayerComponentBus.h> #include <AzToolsFramework/ToolsComponents/EditorLayerComponentBus.h>
#include <AzToolsFramework/UI/EditorEntityUi/EditorEntityUiInterface.h> #include <AzToolsFramework/UI/EditorEntityUi/EditorEntityUiInterface.h>
#include <AzToolsFramework/UI/Prefab/PrefabIntegrationInterface.h> #include <AzToolsFramework/UI/Prefab/PrefabIntegrationInterface.h>
@ -39,9 +40,12 @@ namespace AzToolsFramework
{ {
namespace Prefab namespace Prefab
{ {
EditorEntityUiInterface* PrefabIntegrationManager::s_editorEntityUiInterface = nullptr; EditorEntityUiInterface* PrefabIntegrationManager::s_editorEntityUiInterface = nullptr;
PrefabPublicInterface* PrefabIntegrationManager::s_prefabPublicInterface = nullptr; PrefabPublicInterface* PrefabIntegrationManager::s_prefabPublicInterface = nullptr;
PrefabEditInterface* PrefabIntegrationManager::s_prefabEditInterface = nullptr; PrefabEditInterface* PrefabIntegrationManager::s_prefabEditInterface = nullptr;
PrefabLoaderInterface* PrefabIntegrationManager::s_prefabLoaderInterface = nullptr;
const AZStd::string PrefabIntegrationManager::s_prefabFileExtension = ".prefab"; const AZStd::string PrefabIntegrationManager::s_prefabFileExtension = ".prefab";
void PrefabUserSettings::Reflect(AZ::ReflectContext* context) void PrefabUserSettings::Reflect(AZ::ReflectContext* context)
@ -79,6 +83,13 @@ namespace AzToolsFramework
return; return;
} }
s_prefabLoaderInterface = AZ::Interface<PrefabLoaderInterface>::Get();
if (s_prefabLoaderInterface == nullptr)
{
AZ_Assert(false, "Prefab - could not get PrefabLoaderInterface on PrefabIntegrationManager construction.");
return;
}
EditorContextMenuBus::Handler::BusConnect(); EditorContextMenuBus::Handler::BusConnect();
PrefabInstanceContainerNotificationBus::Handler::BusConnect(); PrefabInstanceContainerNotificationBus::Handler::BusConnect();
AZ::Interface<PrefabIntegrationInterface>::Register(this); AZ::Interface<PrefabIntegrationInterface>::Register(this);
@ -320,14 +331,15 @@ namespace AzToolsFramework
GenerateSuggestedFilenameFromEntities(prefabRootEntities, suggestedName); GenerateSuggestedFilenameFromEntities(prefabRootEntities, suggestedName);
if (!QueryUserForPrefabSaveLocation(suggestedName, targetDirectory, AZ_CRC("PrefabUserSettings"), activeWindow, prefabName, prefabFilePath)) if (!QueryUserForPrefabSaveLocation(
suggestedName, targetDirectory, AZ_CRC("PrefabUserSettings"), activeWindow, prefabName, prefabFilePath))
{ {
// User canceled prefab creation, or error prevented continuation. // User canceled prefab creation, or error prevented continuation.
return; return;
} }
} }
auto createPrefabOutcome = s_prefabPublicInterface->CreatePrefab(selectedEntities, prefabFilePath); auto createPrefabOutcome = s_prefabPublicInterface->CreatePrefab(selectedEntities, s_prefabLoaderInterface->GetRelativePathToProject(prefabFilePath.data()));
if (!createPrefabOutcome.IsSuccess()) if (!createPrefabOutcome.IsSuccess())
{ {

@ -29,6 +29,9 @@ namespace AzToolsFramework
{ {
namespace Prefab namespace Prefab
{ {
class PrefabLoaderInterface;
//! Structure for saving/retrieving user settings related to prefab workflows. //! Structure for saving/retrieving user settings related to prefab workflows.
class PrefabUserSettings class PrefabUserSettings
: public AZ::UserSettings : public AZ::UserSettings
@ -129,6 +132,7 @@ namespace AzToolsFramework
static EditorEntityUiInterface* s_editorEntityUiInterface; static EditorEntityUiInterface* s_editorEntityUiInterface;
static PrefabPublicInterface* s_prefabPublicInterface; static PrefabPublicInterface* s_prefabPublicInterface;
static PrefabEditInterface* s_prefabEditInterface; static PrefabEditInterface* s_prefabEditInterface;
static PrefabLoaderInterface* s_prefabLoaderInterface;
}; };
} }
} }

@ -65,7 +65,7 @@ namespace AzToolsFramework
if (!contextMenu.m_menu->isEmpty()) if (!contextMenu.m_menu->isEmpty())
{ {
contextMenu.m_menu->popup(QCursor::pos()); contextMenu.m_menu->exec(QCursor::pos());
} }
} }
} }

@ -1603,7 +1603,7 @@ namespace AzToolsFramework
const AZ::Vector3 uniformScale = AZ::Vector3(action.m_start.m_sign * sumVectorElements(action.LocalScaleOffset())); const AZ::Vector3 uniformScale = AZ::Vector3(action.m_start.m_sign * sumVectorElements(action.LocalScaleOffset()));
const AZ::Vector3 scale = (AZ::Vector3::CreateOne() + const AZ::Vector3 scale = (AZ::Vector3::CreateOne() +
(uniformScale / initialScale)).GetMax(AZ::Vector3(0.01f)); (uniformScale / initialScale)).GetClamp(AZ::Vector3(AZ::MinTransformScale), AZ::Vector3(AZ::MaxTransformScale));
const AZ::Transform scaleTransform = AZ::Transform::CreateScale(scale); const AZ::Transform scaleTransform = AZ::Transform::CreateScale(scale);
if (action.m_modifiers.Alt()) if (action.m_modifiers.Alt())

@ -313,7 +313,7 @@ namespace O3DELauncher
return "Failed to initialize the CrySystem Interface"; return "Failed to initialize the CrySystem Interface";
case ReturnCode::ErrCryEnvironment: case ReturnCode::ErrCryEnvironment:
return "Failed to initialize the CryEngine global environment"; return "Failed to initialize the global environment";
case ReturnCode::ErrAssetProccessor: case ReturnCode::ErrAssetProccessor:
return "Failed to connect to AssetProcessor while the /Amazon/AzCore/Bootstrap/wait_for_connect value is 1\n." return "Failed to connect to AssetProcessor while the /Amazon/AzCore/Bootstrap/wait_for_connect value is 1\n."

@ -39,15 +39,15 @@ namespace
} }
@interface LumberyardApplicationDelegate_iOS : NSObject<UIApplicationDelegate> @interface O3DEApplicationDelegate_iOS : NSObject<UIApplicationDelegate>
{ {
} }
@end // LumberyardApplicationDelegate_iOS Interface @end // O3DEApplicationDelegate_iOS Interface
@implementation LumberyardApplicationDelegate_iOS @implementation O3DEApplicationDelegate_iOS
- (int)runLumberyardApplication - (int)runO3DEApplication
{ {
#if AZ_TESTS_ENABLED #if AZ_TESTS_ENABLED
@ -55,7 +55,7 @@ namespace
return static_cast<int>(ReturnCode::ErrUnitTestNotSupported); return static_cast<int>(ReturnCode::ErrUnitTestNotSupported);
#else #else
using namespace LumberyardLauncher; using namespace O3DELauncher;
PlatformMainInfo mainInfo; PlatformMainInfo mainInfo;
mainInfo.m_updateResourceLimits = IncreaseResourceLimits; mainInfo.m_updateResourceLimits = IncreaseResourceLimits;
@ -79,19 +79,19 @@ namespace
#endif // AZ_TESTS_ENABLED #endif // AZ_TESTS_ENABLED
} }
- (void)launchLumberyardApplication - (void)launchO3DEApplication
{ {
const int exitCode = [self runLumberyardApplication]; const int exitCode = [self runO3DEApplication];
exit(exitCode); exit(exitCode);
} }
- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions - (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions
{ {
// prevent the lumberyard runtime from running when launched in a xctest environment, otherwise the // prevent the o3de runtime from running when launched in a xctest environment, otherwise the
// testing framework will kill the "app" due to the lengthy bootstrap process // testing framework will kill the "app" due to the lengthy bootstrap process
if ([[NSProcessInfo processInfo] environment][@"XCTestConfigurationFilePath"] == nil) if ([[NSProcessInfo processInfo] environment][@"XCTestConfigurationFilePath"] == nil)
{ {
[self performSelector:@selector(launchLumberyardApplication) withObject:nil afterDelay:0.0]; [self performSelector:@selector(launchO3DEApplication) withObject:nil afterDelay:0.0];
} }
return YES; return YES;
} }
@ -132,4 +132,4 @@ namespace
&AzFramework::IosLifecycleEvents::Bus::Events::OnDidReceiveMemoryWarning); &AzFramework::IosLifecycleEvents::Bus::Events::OnDidReceiveMemoryWarning);
} }
@end // LumberyardApplicationDelegate_iOS Implementation @end // O3DEApplicationDelegate_iOS Implementation

@ -16,12 +16,12 @@
#include <AzFramework/Input/Buses/Notifications/RawInputNotificationBus_Platform.h> #include <AzFramework/Input/Buses/Notifications/RawInputNotificationBus_Platform.h>
@interface LumberyardApplication_iOS : UIApplication @interface O3DEApplication_iOS : UIApplication
{ {
} }
@end // LumberyardApplication_iOS Interface @end // O3DEApplication_iOS Interface
@implementation LumberyardApplication_iOS @implementation O3DEApplication_iOS
- (void)touchesBegan: (NSSet<UITouch*>*)touches withEvent: (UIEvent*)event - (void)touchesBegan: (NSSet<UITouch*>*)touches withEvent: (UIEvent*)event
{ {
@ -65,4 +65,4 @@
[self touchesEnded: touches withEvent: event]; [self touchesEnded: touches withEvent: event];
} }
@end // LumberyardApplication_iOS Implementation @end // O3DEApplication_iOS Implementation

@ -13,8 +13,8 @@ set(FILES
Launcher_iOS.mm Launcher_iOS.mm
Launcher_Traits_iOS.h Launcher_Traits_iOS.h
Launcher_Traits_Platform.h Launcher_Traits_Platform.h
LumberyardApplication_iOS.mm O3DEApplication_iOS.mm
LumberyardApplicationDelegate_iOS.mm O3DEApplicationDelegate_iOS.mm
../Common/Apple/Launcher_Apple.mm ../Common/Apple/Launcher_Apple.mm
../Common/Apple/Launcher_Apple.h ../Common/Apple/Launcher_Apple.h
../Common/UnixLike/Launcher_UnixLike.cpp ../Common/UnixLike/Launcher_UnixLike.cpp

@ -18,6 +18,21 @@ foreach(project_name project_path IN ZIP_LISTS LY_PROJECTS_TARGET_NAME LY_PROJEC
# If the project_path is relative, it is evaluated relative to the ${LY_ROOT_FOLDER} # If the project_path is relative, it is evaluated relative to the ${LY_ROOT_FOLDER}
# Otherwise the the absolute project_path is returned with symlinks resolved # Otherwise the the absolute project_path is returned with symlinks resolved
file(REAL_PATH ${project_path} project_real_path BASE_DIRECTORY ${LY_ROOT_FOLDER}) file(REAL_PATH ${project_path} project_real_path BASE_DIRECTORY ${LY_ROOT_FOLDER})
if(NOT project_name)
if(NOT EXISTS ${project_real_path}/project.json)
message(FATAL_ERROR "The specified project path of ${project_real_path} does not contain a project.json file")
else()
# Add the project_name to global LY_PROJECTS_TARGET_NAME property
file(READ "${project_real_path}/project.json" project_json)
string(JSON project_name ERROR_VARIABLE json_error GET ${project_json} "project_name")
if(json_error)
message(FATAL_ERROR "There is an error reading the \"project_name\" key from the '${project_real_path}/project.json' file: ${json_error}")
endif()
message(WARNING "The project located at path ${project_real_path} has a valid \"project name\" of '${project_name}' read from it's project.json file."
" This indicates that the ${project_real_path}/CMakeLists.txt is not properly appending the \"project name\" "
"to the LY_PROJECTS_TARGET_NAME global property. Other configuration errors might occur")
endif()
endif()
################################################################################ ################################################################################
# Monolithic game # Monolithic game
################################################################################ ################################################################################

@ -5702,7 +5702,7 @@ extern "C" int AZ_DLL_EXPORT CryEditMain(int argc, char* argv[])
int exitCode = 0; int exitCode = 0;
BOOL didCryEditStart = CCryEditApp::instance()->InitInstance(); BOOL didCryEditStart = CCryEditApp::instance()->InitInstance();
AZ_Error("Editor", didCryEditStart, "CryEditor did not initialize correctly, and will close." AZ_Error("Editor", didCryEditStart, "O3DE Editor did not initialize correctly, and will close."
"\nThis could be because of incorrectly configured components, or missing required gems." "\nThis could be because of incorrectly configured components, or missing required gems."
"\nSee other errors for more details."); "\nSee other errors for more details.");

@ -1931,7 +1931,7 @@ void CCryEditDoc::Fetch(const QString& holdName, const QString& relativeHoldPath
if (!LoadXmlArchiveArray(arrXmlAr, holdFilename, holdPath)) if (!LoadXmlArchiveArray(arrXmlAr, holdFilename, holdPath))
{ {
QMessageBox::critical(QApplication::activeWindow(), "Error", "The temporary 'Hold' level failed to load successfully. Your level might be corrupted, you should restart the Editor.", QMessageBox::Ok); QMessageBox::critical(QApplication::activeWindow(), "Error", "The temporary 'Hold' level failed to load successfully. Your level might be corrupted, you should restart the Editor.", QMessageBox::Ok);
AZ_Error("CryEditDoc", false, "Fetch failed to load the Xml Archive"); AZ_Error("EditDoc", false, "Fetch failed to load the Xml Archive");
return; return;
} }

@ -96,6 +96,11 @@ bool LegacyViewportCameraControllerInstance::HandleMouseMove(
speedScale *= gSettings.cameraFastMoveSpeed; speedScale *= gSettings.cameraFastMoveSpeed;
} }
if (m_inMoveMode || m_inOrbitMode || m_inRotateMode || m_inZoomMode)
{
m_totalMouseMoveDelta += (QPoint(currentMousePos.m_x, currentMousePos.m_y)-QPoint(previousMousePos.m_x, previousMousePos.m_y)).manhattanLength();
}
if ((m_inRotateMode && m_inMoveMode) || m_inZoomMode) if ((m_inRotateMode && m_inMoveMode) || m_inZoomMode)
{ {
Matrix34 m = AZTransformToLYTransform(viewportContext->GetCameraTransform()); Matrix34 m = AZTransformToLYTransform(viewportContext->GetCameraTransform());
@ -342,13 +347,16 @@ bool LegacyViewportCameraControllerInstance::HandleInputChannelEvent(const AzFra
m_inRotateMode = true; m_inRotateMode = true;
} }
shouldConsumeEvent = true;
shouldCaptureCursor = true; shouldCaptureCursor = true;
// Record how much the cursor has been moved to see if we should own the mouse up event.
m_totalMouseMoveDelta = 0;
} }
else if (state == InputChannel::State::Ended) else if (state == InputChannel::State::Ended)
{ {
m_inZoomMode = false; m_inZoomMode = false;
m_inRotateMode = false; m_inRotateMode = false;
// If we've moved the cursor more than a couple pixels, we should eat this mouse up event to prevent the context menu controller from seeing it.
shouldConsumeEvent = m_totalMouseMoveDelta > 2;
shouldCaptureCursor = false; shouldCaptureCursor = false;
} }
} }

@ -58,6 +58,7 @@ namespace SandboxEditor
bool m_inMoveMode = false; bool m_inMoveMode = false;
bool m_inOrbitMode = false; bool m_inOrbitMode = false;
bool m_inZoomMode = false; bool m_inZoomMode = false;
int m_totalMouseMoveDelta = 0;
float m_orbitDistance = 10.f; float m_orbitDistance = 10.f;
float m_moveSpeed = 1.f; float m_moveSpeed = 1.f;
AZ::Vector3 m_orbitTarget = {}; AZ::Vector3 m_orbitTarget = {};

@ -37,7 +37,7 @@
#include <AzFramework/API/AtomActiveInterface.h> #include <AzFramework/API/AtomActiveInterface.h>
#include <AzCore/Console/IConsole.h> #include <AzCore/Console/IConsole.h>
AZ_CVAR(bool, ed_useAtomNativeViewport, false, nullptr, AZ::ConsoleFunctorFlags::Null, "Use the new Atom-native Editor viewport (experimental, not yet stable"); AZ_CVAR(bool, ed_useAtomNativeViewport, true, nullptr, AZ::ConsoleFunctorFlags::Null, "Use the new Atom-native Editor viewport (experimental, not yet stable");
bool CViewManager::IsMultiViewportEnabled() bool CViewManager::IsMultiViewportEnabled()
{ {

@ -21,8 +21,8 @@
#include <QApplication> #include <QApplication>
static const auto ManipulatorPriority = AzFramework::ViewportControllerPriority::Highest; static const auto ManipulatorPriority = AzFramework::ViewportControllerPriority::High;
static const auto InteractionPriority = AzFramework::ViewportControllerPriority::High; static const auto InteractionPriority = AzFramework::ViewportControllerPriority::Low;
namespace SandboxEditor namespace SandboxEditor
{ {

@ -42,6 +42,7 @@
#include <SceneAPI/SceneCore/DataTypes/Rules/IMeshAdvancedRule.h> #include <SceneAPI/SceneCore/DataTypes/Rules/IMeshAdvancedRule.h>
#include <SceneAPI/SceneCore/DataTypes/Rules/ILodRule.h> #include <SceneAPI/SceneCore/DataTypes/Rules/ILodRule.h>
#include <SceneAPI/SceneCore/DataTypes/Rules/ISkeletonProxyRule.h> #include <SceneAPI/SceneCore/DataTypes/Rules/ISkeletonProxyRule.h>
#include <SceneAPI/SceneCore/DataTypes/Rules/IScriptProcessorRule.h>
#include <SceneAPI/SceneCore/DataTypes/GraphData/IAnimationData.h> #include <SceneAPI/SceneCore/DataTypes/GraphData/IAnimationData.h>
#include <SceneAPI/SceneCore/DataTypes/GraphData/IBlendShapeData.h> #include <SceneAPI/SceneCore/DataTypes/GraphData/IBlendShapeData.h>
#include <SceneAPI/SceneCore/DataTypes/GraphData/IBoneData.h> #include <SceneAPI/SceneCore/DataTypes/GraphData/IBoneData.h>
@ -168,6 +169,7 @@ namespace AZ
context->Class<AZ::SceneAPI::DataTypes::IMeshAdvancedRule, AZ::SceneAPI::DataTypes::IRule>()->Version(1); context->Class<AZ::SceneAPI::DataTypes::IMeshAdvancedRule, AZ::SceneAPI::DataTypes::IRule>()->Version(1);
context->Class<AZ::SceneAPI::DataTypes::ILodRule, AZ::SceneAPI::DataTypes::IRule>()->Version(1); context->Class<AZ::SceneAPI::DataTypes::ILodRule, AZ::SceneAPI::DataTypes::IRule>()->Version(1);
context->Class<AZ::SceneAPI::DataTypes::ISkeletonProxyRule, AZ::SceneAPI::DataTypes::IRule>()->Version(1); context->Class<AZ::SceneAPI::DataTypes::ISkeletonProxyRule, AZ::SceneAPI::DataTypes::IRule>()->Version(1);
context->Class<AZ::SceneAPI::DataTypes::IScriptProcessorRule, AZ::SceneAPI::DataTypes::IRule>()->Version(1);
// Register graph data interfaces // Register graph data interfaces
context->Class<AZ::SceneAPI::DataTypes::IAnimationData, AZ::SceneAPI::DataTypes::IGraphObject>()->Version(1); context->Class<AZ::SceneAPI::DataTypes::IAnimationData, AZ::SceneAPI::DataTypes::IGraphObject>()->Version(1);
context->Class<AZ::SceneAPI::DataTypes::IBlendShapeData, AZ::SceneAPI::DataTypes::IGraphObject>()->Version(1); context->Class<AZ::SceneAPI::DataTypes::IBlendShapeData, AZ::SceneAPI::DataTypes::IGraphObject>()->Version(1);

@ -12,6 +12,7 @@
#pragma once #pragma once
#include <SceneAPI/SceneData/SceneDataConfiguration.h>
#include <AzCore/std/string/string.h> #include <AzCore/std/string/string.h>
#include <SceneAPI/SceneCore/Components/BehaviorComponent.h> #include <SceneAPI/SceneCore/Components/BehaviorComponent.h>
#include <SceneAPI/SceneCore/Events/AssetImportRequest.h> #include <SceneAPI/SceneCore/Events/AssetImportRequest.h>
@ -27,7 +28,7 @@ namespace AZ
{ {
namespace Behaviors namespace Behaviors
{ {
class ScriptProcessorRuleBehavior class SCENE_DATA_CLASS ScriptProcessorRuleBehavior
: public SceneCore::BehaviorComponent : public SceneCore::BehaviorComponent
, public Events::AssetImportRequestBus::Handler , public Events::AssetImportRequestBus::Handler
{ {
@ -36,12 +37,12 @@ namespace AZ
~ScriptProcessorRuleBehavior() override = default; ~ScriptProcessorRuleBehavior() override = default;
void Activate() override; SCENE_DATA_API void Activate() override;
void Deactivate() override; SCENE_DATA_API void Deactivate() override;
static void Reflect(ReflectContext* context); static void Reflect(ReflectContext* context);
// AssetImportRequestBus::Handler // AssetImportRequestBus::Handler
Events::ProcessingResult UpdateManifest( SCENE_DATA_API Events::ProcessingResult UpdateManifest(
Containers::Scene& scene, Containers::Scene& scene,
ManifestAction action, ManifestAction action,
RequestingApplication requester) override; RequestingApplication requester) override;

@ -26,7 +26,6 @@
#include <SceneAPI/SceneData/Rules/LodRule.h> #include <SceneAPI/SceneData/Rules/LodRule.h>
#include <SceneAPI/SceneData/Rules/MaterialRule.h> #include <SceneAPI/SceneData/Rules/MaterialRule.h>
#include <SceneAPI/SceneData/Rules/StaticMeshAdvancedRule.h> #include <SceneAPI/SceneData/Rules/StaticMeshAdvancedRule.h>
#include <SceneAPI/SceneData/Rules/ScriptProcessorRule.h>
#include <SceneAPI/SceneData/Rules/SkeletonProxyRule.h> #include <SceneAPI/SceneData/Rules/SkeletonProxyRule.h>
#include <SceneAPI/SceneData/Rules/SkinMeshAdvancedRule.h> #include <SceneAPI/SceneData/Rules/SkinMeshAdvancedRule.h>
#include <SceneAPI/SceneData/Rules/SkinRule.h> #include <SceneAPI/SceneData/Rules/SkinRule.h>
@ -55,7 +54,6 @@ namespace AZ
{ {
AZ_TraceContext("Object Type", target.RTTI_GetTypeName()); AZ_TraceContext("Object Type", target.RTTI_GetTypeName());
modifiers.push_back(SceneData::CommentRule::TYPEINFO_Uuid()); modifiers.push_back(SceneData::CommentRule::TYPEINFO_Uuid());
modifiers.push_back(SceneData::ScriptProcessorRule::TYPEINFO_Uuid());
if (target.RTTI_IsTypeOf(DataTypes::IMeshGroup::TYPEINFO_Uuid())) if (target.RTTI_IsTypeOf(DataTypes::IMeshGroup::TYPEINFO_Uuid()))
{ {

@ -13,19 +13,23 @@
#include <AzTest/AzTest.h> #include <AzTest/AzTest.h>
#include <SceneAPI/SceneCore/Containers/SceneManifest.h> #include <SceneAPI/SceneCore/Containers/SceneManifest.h>
#include <SceneAPI/SceneCore/Containers/Scene.h>
#include <SceneAPI/SceneCore/DataTypes/Rules/IScriptProcessorRule.h>
#include <SceneAPI/SceneData/ReflectionRegistrar.h> #include <SceneAPI/SceneData/ReflectionRegistrar.h>
#include <SceneAPI/SceneData/Rules/CoordinateSystemRule.h> #include <SceneAPI/SceneData/Rules/CoordinateSystemRule.h>
#include <SceneAPI/SceneData/Behaviors/ScriptProcessorRuleBehavior.h>
#include <AzCore/Math/Quaternion.h>
#include <AzCore/Name/NameDictionary.h> #include <AzCore/Name/NameDictionary.h>
#include <AzCore/RTTI/BehaviorContext.h> #include <AzCore/RTTI/BehaviorContext.h>
#include <AzCore/RTTI/ReflectionManager.h> #include <AzCore/RTTI/ReflectionManager.h>
#include <AzCore/Serialization/Json/RegistrationContext.h>
#include <AzCore/Serialization/Json/JsonSystemComponent.h> #include <AzCore/Serialization/Json/JsonSystemComponent.h>
#include <AzCore/Serialization/Json/RegistrationContext.h>
#include <AzCore/std/smart_ptr/make_shared.h> #include <AzCore/std/smart_ptr/make_shared.h>
#include <AzCore/std/smart_ptr/shared_ptr.h> #include <AzCore/std/smart_ptr/shared_ptr.h>
#include <AzCore/UnitTest/Mocks/MockSettingsRegistry.h>
#include <AzCore/UnitTest/TestTypes.h> #include <AzCore/UnitTest/TestTypes.h>
#include <AzFramework/FileFunc/FileFunc.h> #include <AzFramework/FileFunc/FileFunc.h>
#include <AzCore/Math/Quaternion.h>
namespace AZ namespace AZ
{ {
@ -94,6 +98,19 @@ namespace AZ
m_jsonSystemComponent = AZStd::make_unique<JsonSystemComponent>(); m_jsonSystemComponent = AZStd::make_unique<JsonSystemComponent>();
m_jsonSystemComponent->Reflect(m_jsonRegistrationContext.get()); m_jsonSystemComponent->Reflect(m_jsonRegistrationContext.get());
m_data.reset(new DataMembers);
using FixedValueString = AZ::SettingsRegistryInterface::FixedValueString;
ON_CALL(m_data->m_settings, Get(::testing::Matcher<FixedValueString&>(::testing::_), testing::_))
.WillByDefault([](FixedValueString& value, AZStd::string_view) -> bool
{
value = "mock_path";
return true;
});
AZ::SettingsRegistry::Register(&m_data->m_settings);
} }
void TearDown() override void TearDown() override
@ -106,9 +123,19 @@ namespace AZ
m_jsonRegistrationContext.reset(); m_jsonRegistrationContext.reset();
m_jsonSystemComponent.reset(); m_jsonSystemComponent.reset();
AZ::SettingsRegistry::Unregister(&m_data->m_settings);
m_data.reset();
AZ::NameDictionary::Destroy(); AZ::NameDictionary::Destroy();
UnitTest::AllocatorsFixture::TearDown(); UnitTest::AllocatorsFixture::TearDown();
} }
struct DataMembers
{
AZ::NiceSettingsRegistrySimpleMock m_settings;
};
AZStd::unique_ptr<DataMembers> m_data;
}; };
TEST_F(SceneManifest_JSON, LoadFromString_BlankManifest_HasDefaultParts) TEST_F(SceneManifest_JSON, LoadFromString_BlankManifest_HasDefaultParts)
@ -223,5 +250,30 @@ namespace AZ
EXPECT_THAT(jsonText.c_str(), ::testing::HasSubstr(R"(3.0)")); EXPECT_THAT(jsonText.c_str(), ::testing::HasSubstr(R"(3.0)"));
EXPECT_THAT(jsonText.c_str(), ::testing::HasSubstr(R"("scale": 10.0)")); EXPECT_THAT(jsonText.c_str(), ::testing::HasSubstr(R"("scale": 10.0)"));
} }
TEST_F(SceneManifest_JSON, ScriptProcessorRule_LoadWithEmptyScriptFilename_ReturnsEarly)
{
using namespace SceneAPI::Containers;
using namespace SceneAPI::Events;
constexpr const char* jsonManifest = { R"JSON(
{
"values": [
{
"$type": "ScriptProcessorRule",
"scriptFilename": ""
}
]
})JSON" };
auto scene = AZ::SceneAPI::Containers::Scene("mock");
auto result = scene.GetManifest().LoadFromString(jsonManifest, m_serializeContext.get(), m_jsonRegistrationContext.get());
EXPECT_TRUE(result.IsSuccess());
EXPECT_FALSE(scene.GetManifest().IsEmpty());
auto scriptProcessorRuleBehavior = AZ::SceneAPI::Behaviors::ScriptProcessorRuleBehavior();
auto update = scriptProcessorRuleBehavior.UpdateManifest(scene, AssetImportRequest::Update, AssetImportRequest::Generic);
EXPECT_EQ(update, ProcessingResult::Ignored);
}
} }
} }

@ -25,6 +25,7 @@ namespace ImageProcessingAtom
// EBusTraits overrides // EBusTraits overrides
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single; static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single; static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
typedef AZStd::recursive_mutex MutexType;
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
// Loads an image from a source file path // Loads an image from a source file path

@ -102,7 +102,8 @@ namespace AZ
// Register Shader Resource Group Layout Builder // Register Shader Resource Group Layout Builder
AssetBuilderSDK::AssetBuilderDesc srgLayoutBuilderDescriptor; AssetBuilderSDK::AssetBuilderDesc srgLayoutBuilderDescriptor;
srgLayoutBuilderDescriptor.m_name = "Shader Resource Group Layout Builder"; srgLayoutBuilderDescriptor.m_name = "Shader Resource Group Layout Builder";
srgLayoutBuilderDescriptor.m_version = 52; // ATOM-14780 srgLayoutBuilderDescriptor.m_version = 53; // ATOM-15196
srgLayoutBuilderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern("*.azsl", AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard)); srgLayoutBuilderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern("*.azsl", AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard));
srgLayoutBuilderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern("*.azsli", AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard)); srgLayoutBuilderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern("*.azsli", AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard));
srgLayoutBuilderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern(AZStd::string::format("*.%s", SrgLayoutBuilder::MergedPartialSrgsExtension), AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard)); srgLayoutBuilderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern(AZStd::string::format("*.%s", SrgLayoutBuilder::MergedPartialSrgsExtension), AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard));
@ -117,7 +118,7 @@ namespace AZ
// Register Shader Asset Builder // Register Shader Asset Builder
AssetBuilderSDK::AssetBuilderDesc shaderAssetBuilderDescriptor; AssetBuilderSDK::AssetBuilderDesc shaderAssetBuilderDescriptor;
shaderAssetBuilderDescriptor.m_name = "Shader Asset Builder"; shaderAssetBuilderDescriptor.m_name = "Shader Asset Builder";
shaderAssetBuilderDescriptor.m_version = 96; // SPEC-6065 shaderAssetBuilderDescriptor.m_version = 97; // ATOM-15196
// .shader file changes trigger rebuilds // .shader file changes trigger rebuilds
shaderAssetBuilderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern( AZStd::string::format("*.%s", RPI::ShaderSourceData::Extension), AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard)); shaderAssetBuilderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern( AZStd::string::format("*.%s", RPI::ShaderSourceData::Extension), AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard));
shaderAssetBuilderDescriptor.m_busId = azrtti_typeid<ShaderAssetBuilder>(); shaderAssetBuilderDescriptor.m_busId = azrtti_typeid<ShaderAssetBuilder>();
@ -132,7 +133,7 @@ namespace AZ
shaderVariantAssetBuilderDescriptor.m_name = "Shader Variant Asset Builder"; shaderVariantAssetBuilderDescriptor.m_name = "Shader Variant Asset Builder";
// Both "Shader Variant Asset Builder" and "Shader Asset Builder" produce ShaderVariantAsset products. If you update // Both "Shader Variant Asset Builder" and "Shader Asset Builder" produce ShaderVariantAsset products. If you update
// ShaderVariantAsset you will need to update BOTH version numbers, not just "Shader Variant Asset Builder". // ShaderVariantAsset you will need to update BOTH version numbers, not just "Shader Variant Asset Builder".
shaderVariantAssetBuilderDescriptor.m_version = 17; // SPEC-6065 shaderVariantAssetBuilderDescriptor.m_version = 18; // ATOM-15196
shaderVariantAssetBuilderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern(AZStd::string::format("*.%s", RPI::ShaderVariantListSourceData::Extension), AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard)); shaderVariantAssetBuilderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern(AZStd::string::format("*.%s", RPI::ShaderVariantListSourceData::Extension), AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard));
shaderVariantAssetBuilderDescriptor.m_busId = azrtti_typeid<ShaderVariantAssetBuilder>(); shaderVariantAssetBuilderDescriptor.m_busId = azrtti_typeid<ShaderVariantAssetBuilder>();
shaderVariantAssetBuilderDescriptor.m_createJobFunction = AZStd::bind(&ShaderVariantAssetBuilder::CreateJobs, &m_shaderVariantAssetBuilder, AZStd::placeholders::_1, AZStd::placeholders::_2); shaderVariantAssetBuilderDescriptor.m_createJobFunction = AZStd::bind(&ShaderVariantAssetBuilder::CreateJobs, &m_shaderVariantAssetBuilder, AZStd::placeholders::_1, AZStd::placeholders::_2);

@ -646,8 +646,8 @@ namespace AZ
return 0; // Nothing to draw. return 0; // Nothing to draw.
} }
auto vertexBuffer = RPI::DynamicDrawInterface::Get()->GetDynamicBuffer(totalVtxBufferSize); auto vertexBuffer = RPI::DynamicDrawInterface::Get()->GetDynamicBuffer(totalVtxBufferSize, RHI::Alignment::InputAssembly);
auto indexBuffer = RPI::DynamicDrawInterface::Get()->GetDynamicBuffer(totalIdxBufferSize); auto indexBuffer = RPI::DynamicDrawInterface::Get()->GetDynamicBuffer(totalIdxBufferSize, RHI::Alignment::InputAssembly);
if (!vertexBuffer || !indexBuffer) if (!vertexBuffer || !indexBuffer)
{ {

@ -186,7 +186,7 @@ namespace AZ
{ {
for (const RPI::Pass* pass : passes) for (const RPI::Pass* pass : passes)
{ {
m_timestampEntries.push_back({ pass->GetName(), pass->GetTimestampResult().GetTimestampInNanoseconds() }); m_timestampEntries.push_back({pass->GetName(), pass->GetLatestTimestampResult().GetDurationInNanoseconds()});
} }
} }
@ -223,7 +223,7 @@ namespace AZ
{ {
for (const RPI::Pass* pass : passes) for (const RPI::Pass* pass : passes)
{ {
m_pipelineStatisticsEntries.push_back({ pass->GetName(), pass->GetPipelineStatisticsResult() }); m_pipelineStatisticsEntries.push_back({pass->GetName(), pass->GetLatestPipelineStatisticsResult()});
} }
} }

@ -66,6 +66,7 @@ namespace AZ
// load the RayTracingSceneSrg asset // load the RayTracingSceneSrg asset
Data::Asset<RPI::ShaderResourceGroupAsset> rayTracingSceneSrgAsset = Data::Asset<RPI::ShaderResourceGroupAsset> rayTracingSceneSrgAsset =
RPI::AssetUtils::LoadAssetByProductPath<RPI::ShaderResourceGroupAsset>("shaderlib/raytracingscenesrg_raytracingscenesrg.azsrg", RPI::AssetUtils::TraceLevel::Error); RPI::AssetUtils::LoadAssetByProductPath<RPI::ShaderResourceGroupAsset>("shaderlib/raytracingscenesrg_raytracingscenesrg.azsrg", RPI::AssetUtils::TraceLevel::Error);
AZ_Assert(rayTracingSceneSrgAsset.IsReady(), "Failed to load RayTracingSceneSrg asset");
m_rayTracingSceneSrg = RPI::ShaderResourceGroup::Create(rayTracingSceneSrgAsset); m_rayTracingSceneSrg = RPI::ShaderResourceGroup::Create(rayTracingSceneSrgAsset);
} }

@ -30,38 +30,42 @@ namespace AZ
{ {
None = 0, None = 0,
/// Supports input assembly access through a IndexBufferView or StreamBufferView. /// Supports input assembly access through a IndexBufferView or StreamBufferView. This flag is for buffers that are not updated often
InputAssembly = AZ_BIT(0), InputAssembly = AZ_BIT(0),
/// Supports input assembly access through a IndexBufferView or StreamBufferView. This flag is for buffers that are updated frequently
DynamicInputAssembly = AZ_BIT(1),
/// Supports constant access through a ShaderResourceGroup. /// Supports constant access through a ShaderResourceGroup.
Constant = AZ_BIT(1), Constant = AZ_BIT(2),
/// Supports read access through a ShaderResourceGroup. /// Supports read access through a ShaderResourceGroup.
ShaderRead = AZ_BIT(2), ShaderRead = AZ_BIT(3),
/// Supports write access through ShaderResourceGroup. /// Supports write access through ShaderResourceGroup.
ShaderWrite = AZ_BIT(3), ShaderWrite = AZ_BIT(4),
/// Supports read-write access through a ShaderResourceGroup. /// Supports read-write access through a ShaderResourceGroup.
ShaderReadWrite = ShaderRead | ShaderWrite, ShaderReadWrite = ShaderRead | ShaderWrite,
/// Supports read access for GPU copy operations. /// Supports read access for GPU copy operations.
CopyRead = AZ_BIT(4), CopyRead = AZ_BIT(5),
/// Supports write access for GPU copy operations. /// Supports write access for GPU copy operations.
CopyWrite = AZ_BIT(5), CopyWrite = AZ_BIT(6),
/// Supports predication access for conditional rendering. /// Supports predication access for conditional rendering.
Predication = AZ_BIT(6), Predication = AZ_BIT(7),
/// Supports indirect buffer access for indirect draw/dispatch. /// Supports indirect buffer access for indirect draw/dispatch.
Indirect = AZ_BIT(7), Indirect = AZ_BIT(8),
/// Supports ray tracing acceleration structure usage. /// Supports ray tracing acceleration structure usage.
RayTracingAccelerationStructure = AZ_BIT(8), RayTracingAccelerationStructure = AZ_BIT(9),
/// Supports ray tracing shader table usage. /// Supports ray tracing shader table usage.
RayTracingShaderTable = AZ_BIT(9) RayTracingShaderTable = AZ_BIT(10)
}; };
AZ_DEFINE_ENUM_BITWISE_OPERATORS(AZ::RHI::BufferBindFlags); AZ_DEFINE_ENUM_BITWISE_OPERATORS(AZ::RHI::BufferBindFlags);

@ -54,7 +54,7 @@ namespace AZ
if (SerializeContext* serializeContext = azrtti_cast<SerializeContext*>(context)) if (SerializeContext* serializeContext = azrtti_cast<SerializeContext*>(context))
{ {
serializeContext->Class<ReflectSystemComponent, AZ::Component>() serializeContext->Class<ReflectSystemComponent, AZ::Component>()
->Version(2); ->Version(3);
} }
ReflectNamedEnums(context); ReflectNamedEnums(context);
@ -266,6 +266,7 @@ namespace AZ
serializeContext->Enum<BufferBindFlags>() serializeContext->Enum<BufferBindFlags>()
->Value("None", BufferBindFlags::None) ->Value("None", BufferBindFlags::None)
->Value("InputAssembly", BufferBindFlags::InputAssembly) ->Value("InputAssembly", BufferBindFlags::InputAssembly)
->Value("DynamicInputAssembly", BufferBindFlags::DynamicInputAssembly)
->Value("Constant", BufferBindFlags::Constant) ->Value("Constant", BufferBindFlags::Constant)
->Value("CopyRead", BufferBindFlags::CopyRead) ->Value("CopyRead", BufferBindFlags::CopyRead)
->Value("CopyWrite", BufferBindFlags::CopyWrite) ->Value("CopyWrite", BufferBindFlags::CopyWrite)

@ -39,7 +39,7 @@ namespace AZ
// needs to be a multiple of elementsize as well as divisible by DX12::Alignment types. // needs to be a multiple of elementsize as well as divisible by DX12::Alignment types.
m_usePageAllocator = false; m_usePageAllocator = false;
if (!RHI::CheckBitsAny(descriptor.m_bindFlags, RHI::BufferBindFlags::ShaderWrite | RHI::BufferBindFlags::CopyWrite | RHI::BufferBindFlags::InputAssembly)) if (!RHI::CheckBitsAny(descriptor.m_bindFlags, RHI::BufferBindFlags::ShaderWrite | RHI::BufferBindFlags::CopyWrite | RHI::BufferBindFlags::InputAssembly | RHI::BufferBindFlags::DynamicInputAssembly))
{ {
m_usePageAllocator = true; m_usePageAllocator = true;

@ -39,7 +39,7 @@ namespace AZ
{ {
m_device = &device; m_device = &device;
if (RHI::CheckBitsAll(descriptor.m_bindFlags, RHI::BufferBindFlags::InputAssembly)) if(RHI::CheckBitsAny(descriptor.m_bindFlags, RHI::BufferBindFlags::InputAssembly | RHI::BufferBindFlags::DynamicInputAssembly))
{ {
m_readOnlyState |= D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER | D3D12_RESOURCE_STATE_INDEX_BUFFER; m_readOnlyState |= D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER | D3D12_RESOURCE_STATE_INDEX_BUFFER;
} }

@ -670,7 +670,13 @@ namespace AZ
} }
else else
{ {
result &= AddExistingResourceEntry("texture", resourceStartPos, regId, argBufferStr); bool isAdditionSuccessfull = AddExistingResourceEntry("texture", resourceStartPos, regId, argBufferStr);
if(!isAdditionSuccessfull)
{
//In metal depth textures use keyword depth2d/depth2d_array/depthcube/depthcube_array/depth2d_ms/depth2d_ms_array
isAdditionSuccessfull |= AddExistingResourceEntry("depth", resourceStartPos, regId, argBufferStr);
}
result &= isAdditionSuccessfull;
} }
} }
return result; return result;
@ -827,10 +833,13 @@ namespace AZ
AZStd::string& argBufferStr) const AZStd::string& argBufferStr) const
{ {
size_t prevEndOfLine = argBufferStr.rfind("\n", resourceStartPos); size_t prevEndOfLine = argBufferStr.rfind("\n", resourceStartPos);
size_t nextEndOfLine = argBufferStr.find("\n", resourceStartPos);
size_t startOfEntryPos = argBufferStr.find(resourceStr, prevEndOfLine); size_t startOfEntryPos = argBufferStr.find(resourceStr, prevEndOfLine);
if(startOfEntryPos == AZStd::string::npos)
//Check to see if a valid entry is found.
if(startOfEntryPos == AZStd::string::npos || startOfEntryPos > nextEndOfLine)
{ {
AZ_Error(MetalShaderPlatformName, false, "Entry-> %s not found within Descriptor set %s", resourceStr, argBufferStr.c_str()); AZ_Error(MetalShaderPlatformName, startOfEntryPos != AZStd::string::npos, "Entry-> %s not found within Descriptor set %s", resourceStr, argBufferStr.c_str());
return false; return false;
} }
else else

@ -295,7 +295,7 @@ namespace AZ
const RHI::Size sourceSize = RHI::Size(subresourceLayout.m_size.m_width, heightToCopy, 1); const RHI::Size sourceSize = RHI::Size(subresourceLayout.m_size.m_width, heightToCopy, 1);
const RHI::Origin sourceOrigin = RHI::Origin(0, destHeight, depth); const RHI::Origin sourceOrigin = RHI::Origin(0, destHeight, depth);
CopyBufferToImage(framePacket, image, stagingRowPitch, stagingSlicePitch, CopyBufferToImage(framePacket, image, stagingRowPitch, bytesCopied,
curMip, arraySlice, sourceSize, sourceOrigin); curMip, arraySlice, sourceSize, sourceOrigin);
framePacket->m_dataOffset += stagingSize; framePacket->m_dataOffset += stagingSize;

@ -210,6 +210,12 @@ namespace AZ
{ {
return GetCPUGPUMemoryMode(); return GetCPUGPUMemoryMode();
} }
//This flag is used for IA buffers that is updated frequently and hence shared mmory is the best fit
if (RHI::CheckBitsAll(descriptor.m_bindFlags, RHI::BufferBindFlags::DynamicInputAssembly))
{
return MTLStorageModeShared;
}
return GetCPUGPUMemoryMode(); return GetCPUGPUMemoryMode();
} }

@ -107,6 +107,7 @@ namespace AZ
bool forceUnique = RHI::CheckBitsAny( bool forceUnique = RHI::CheckBitsAny(
bufferDescriptor.m_bindFlags, bufferDescriptor.m_bindFlags,
RHI::BufferBindFlags::InputAssembly | RHI::BufferBindFlags::InputAssembly |
RHI::BufferBindFlags::DynamicInputAssembly |
RHI::BufferBindFlags::RayTracingAccelerationStructure | RHI::BufferBindFlags::RayTracingAccelerationStructure |
RHI::BufferBindFlags::RayTracingShaderTable); RHI::BufferBindFlags::RayTracingShaderTable);

@ -685,7 +685,7 @@ namespace AZ
using BindFlags = RHI::BufferBindFlags; using BindFlags = RHI::BufferBindFlags;
VkBufferUsageFlags usageFlags{ 0 }; VkBufferUsageFlags usageFlags{ 0 };
if (RHI::CheckBitsAny(bindFlags, BindFlags::InputAssembly)) if (RHI::CheckBitsAny(bindFlags, BindFlags::InputAssembly | BindFlags::DynamicInputAssembly))
{ {
usageFlags |= usageFlags |=
VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT |
@ -932,7 +932,7 @@ namespace AZ
VkPipelineStageFlags GetResourcePipelineStateFlags(const RHI::BufferBindFlags& bindFlags) VkPipelineStageFlags GetResourcePipelineStateFlags(const RHI::BufferBindFlags& bindFlags)
{ {
VkPipelineStageFlags stagesFlags = {}; VkPipelineStageFlags stagesFlags = {};
if (RHI::CheckBitsAny(bindFlags, RHI::BufferBindFlags::InputAssembly)) if (RHI::CheckBitsAny(bindFlags, RHI::BufferBindFlags::InputAssembly | RHI::BufferBindFlags::DynamicInputAssembly))
{ {
stagesFlags |= VK_PIPELINE_STAGE_DRAW_INDIRECT_BIT | VK_PIPELINE_STAGE_VERTEX_INPUT_BIT; stagesFlags |= VK_PIPELINE_STAGE_DRAW_INDIRECT_BIT | VK_PIPELINE_STAGE_VERTEX_INPUT_BIT;
} }
@ -1042,7 +1042,7 @@ namespace AZ
VkAccessFlags GetResourceAccessFlags(const RHI::BufferBindFlags& bindFlags) VkAccessFlags GetResourceAccessFlags(const RHI::BufferBindFlags& bindFlags)
{ {
VkAccessFlags accessFlags = {}; VkAccessFlags accessFlags = {};
if (RHI::CheckBitsAny(bindFlags, RHI::BufferBindFlags::InputAssembly)) if (RHI::CheckBitsAny(bindFlags, RHI::BufferBindFlags::InputAssembly | RHI::BufferBindFlags::DynamicInputAssembly))
{ {
accessFlags |= VK_ACCESS_INDIRECT_COMMAND_READ_BIT | VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT | VK_ACCESS_INDEX_READ_BIT; accessFlags |= VK_ACCESS_INDIRECT_COMMAND_READ_BIT | VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT | VK_ACCESS_INDEX_READ_BIT;
} }

@ -8,6 +8,6 @@
"BudgetInBytes": 25165824, "BudgetInBytes": 25165824,
"BufferPoolHeapMemoryLevel": "Host", "BufferPoolHeapMemoryLevel": "Host",
"BufferPoolhostMemoryAccess": "Write", "BufferPoolhostMemoryAccess": "Write",
"BufferPoolBindFlags": "InputAssembly" "BufferPoolBindFlags": "DynamicInputAssembly"
} }
} }

@ -36,7 +36,7 @@ namespace AZ
//! buffer->Write(data, size); //! buffer->Write(data, size);
//! // Use the buffer view for DrawItem or etc. //! // Use the buffer view for DrawItem or etc.
//! } //! }
//! Note: DynamicBuffer should only be used for InputAssembly buffer or Constant buffer (not supported yet). //! Note: DynamicBuffer should only be used for DynamicInputAssembly buffer or Constant buffer (not supported yet).
class DynamicBuffer class DynamicBuffer
: public AZStd::intrusive_base : public AZStd::intrusive_base
{ {

@ -11,6 +11,7 @@
*/ */
#pragma once #pragma once
#include <Atom/RHI.Reflect/AttachmentEnums.h>
#include <Atom/RHI.Reflect/QueryPoolDescriptor.h> #include <Atom/RHI.Reflect/QueryPoolDescriptor.h>
#include <AtomCore/std/containers/array_view.h> #include <AtomCore/std/containers/array_view.h>
@ -43,15 +44,19 @@ namespace AZ
{ {
public: public:
TimestampResult() = default; TimestampResult() = default;
TimestampResult(uint64_t timestampInTicks); TimestampResult(uint64_t beginTick, uint64_t endTick, RHI::HardwareQueueClass hardwareQueueClass);
TimestampResult(uint64_t timestampQueryResultLow, uint64_t timestampQueryResultHigh);
TimestampResult(AZStd::array_view<TimestampResult>&& timestampResultArray);
uint64_t GetTimestampInNanoseconds() const; uint64_t GetDurationInNanoseconds() const;
uint64_t GetTimestampInTicks() const; uint64_t GetDurationInTicks() const;
uint64_t GetTimestampBeginInTicks() const;
void Add(const TimestampResult& extent);
private: private:
uint64_t m_timestampInTicks = 0u; // the timestamp of begin and duration in ticks.
uint64_t m_begin = 0;
uint64_t m_duration = 0;
RHI::HardwareQueueClass m_hardwareQueueClass = RHI::HardwareQueueClass::Graphics;
}; };
//! The structure that is used to read back the results form the PipelineStatistics queries //! The structure that is used to read back the results form the PipelineStatistics queries

@ -122,7 +122,6 @@ namespace AZ
private: private:
// RPI::Pass overrides... // RPI::Pass overrides...
TimestampResult GetTimestampResultInternal() const override;
PipelineStatisticsResult GetPipelineStatisticsResultInternal() const override; PipelineStatisticsResult GetPipelineStatisticsResultInternal() const override;
// --- Hierarchy related functions --- // --- Hierarchy related functions ---

@ -211,11 +211,11 @@ namespace AZ
//! Prints the pass //! Prints the pass
virtual void DebugPrint() const; virtual void DebugPrint() const;
//! Return the Timestamp result of this pass //! Return the latest Timestamp result of this pass
TimestampResult GetTimestampResult() const; TimestampResult GetLatestTimestampResult() const;
//! Return the PipelineStatistic result of this pass //! Return the latest PipelineStatistic result of this pass
PipelineStatisticsResult GetPipelineStatisticsResult() const; PipelineStatisticsResult GetLatestPipelineStatisticsResult() const;
//! Enables/Disables Timestamp queries for this pass //! Enables/Disables Timestamp queries for this pass
virtual void SetTimestampQueryEnabled(bool enable); virtual void SetTimestampQueryEnabled(bool enable);

@ -74,8 +74,9 @@ namespace AZ
const RHI::BufferView* Buffer::GetBufferView() const const RHI::BufferView* Buffer::GetBufferView() const
{ {
if (m_rhiBuffer->GetDescriptor().m_bindFlags == RHI::BufferBindFlags::InputAssembly) if(RHI::CheckBitsAny(m_rhiBuffer->GetDescriptor().m_bindFlags, RHI::BufferBindFlags::InputAssembly | RHI::BufferBindFlags::DynamicInputAssembly))
{ {
AZ_Assert(false, "Input assembly buffer doesn't need a regular buffer view, it requires a stream or index buffer view."); AZ_Assert(false, "Input assembly buffer doesn't need a regular buffer view, it requires a stream or index buffer view.");
return nullptr; return nullptr;
} }
@ -203,11 +204,11 @@ namespace AZ
void Buffer::InitBufferView() void Buffer::InitBufferView()
{ {
// Skip buffer view creation for input assembly buffers // Skip buffer view creation for input assembly buffers
if (m_rhiBuffer->GetDescriptor().m_bindFlags == RHI::BufferBindFlags::InputAssembly) if(RHI::CheckBitsAny(m_rhiBuffer->GetDescriptor().m_bindFlags, RHI::BufferBindFlags::InputAssembly | RHI::BufferBindFlags::DynamicInputAssembly))
{ {
return; return;
} }
m_bufferView = m_rhiBuffer->GetBufferView(m_bufferViewDescriptor); m_bufferView = m_rhiBuffer->GetBufferView(m_bufferViewDescriptor);
if(!m_bufferView.get()) if(!m_bufferView.get())

@ -100,12 +100,12 @@ namespace AZ
bufferPoolDesc.m_hostMemoryAccess = RHI::HostMemoryAccess::Write; bufferPoolDesc.m_hostMemoryAccess = RHI::HostMemoryAccess::Write;
break; break;
case CommonBufferPoolType::StaticInputAssembly: case CommonBufferPoolType::StaticInputAssembly:
bufferPoolDesc.m_bindFlags = RHI::BufferBindFlags::InputAssembly; bufferPoolDesc.m_bindFlags = RHI::BufferBindFlags::InputAssembly | RHI::BufferBindFlags::ShaderRead;
bufferPoolDesc.m_heapMemoryLevel = RHI::HeapMemoryLevel::Device; bufferPoolDesc.m_heapMemoryLevel = RHI::HeapMemoryLevel::Device;
bufferPoolDesc.m_hostMemoryAccess = RHI::HostMemoryAccess::Write; bufferPoolDesc.m_hostMemoryAccess = RHI::HostMemoryAccess::Write;
break; break;
case CommonBufferPoolType::DynamicInputAssembly: case CommonBufferPoolType::DynamicInputAssembly:
bufferPoolDesc.m_bindFlags = RHI::BufferBindFlags::InputAssembly; bufferPoolDesc.m_bindFlags = RHI::BufferBindFlags::DynamicInputAssembly | RHI::BufferBindFlags::ShaderRead;
bufferPoolDesc.m_heapMemoryLevel = RHI::HeapMemoryLevel::Host; bufferPoolDesc.m_heapMemoryLevel = RHI::HeapMemoryLevel::Host;
bufferPoolDesc.m_hostMemoryAccess = RHI::HostMemoryAccess::Write; bufferPoolDesc.m_hostMemoryAccess = RHI::HostMemoryAccess::Write;
break; break;

@ -63,6 +63,7 @@ namespace AZ
// [GFX TODO][ATOM-13182] Add unit tests for DynamicBufferAllocator's Allocate function // [GFX TODO][ATOM-13182] Add unit tests for DynamicBufferAllocator's Allocate function
RHI::Ptr<DynamicBuffer> DynamicBufferAllocator::Allocate(uint32_t size, [[maybe_unused]]uint32_t alignment) RHI::Ptr<DynamicBuffer> DynamicBufferAllocator::Allocate(uint32_t size, [[maybe_unused]]uint32_t alignment)
{ {
size = RHI::AlignUp(size, alignment);
uint32_t allocatePosition = 0; uint32_t allocatePosition = 0;
//m_ringBufferStartAddress can be null for Null back end //m_ringBufferStartAddress can be null for Null back end

@ -21,41 +21,39 @@ namespace AZ
namespace RPI namespace RPI
{ {
// --- TimestampResult --- // --- TimestampResult ---
TimestampResult::TimestampResult(uint64_t beginTick, uint64_t endTick, RHI::HardwareQueueClass hardwareQueueClass)
TimestampResult::TimestampResult(uint64_t timestampInTicks)
{ {
m_timestampInTicks = timestampInTicks; AZ_Assert(endTick >= beginTick, "TimestampResult: bad inputs");
m_begin = beginTick;
m_duration = endTick - beginTick;
m_hardwareQueueClass = hardwareQueueClass;
} }
TimestampResult::TimestampResult(uint64_t timestampQueryResultLow, uint64_t timestampQueryResultHigh) uint64_t TimestampResult::GetDurationInNanoseconds() const
{ {
const uint64_t low = AZStd::min(timestampQueryResultLow, timestampQueryResultHigh); const RHI::Ptr<RHI::Device> device = RHI::GetRHIDevice();
const uint64_t high = AZStd::max(timestampQueryResultLow, timestampQueryResultHigh); const AZStd::chrono::microseconds timeInMicroseconds = device->GpuTimestampToMicroseconds(m_duration, m_hardwareQueueClass);
const auto timeInNanoseconds = AZStd::chrono::nanoseconds(timeInMicroseconds);
m_timestampInTicks = high - low; return static_cast<uint64_t>(timeInNanoseconds.count());
} }
TimestampResult::TimestampResult(AZStd::array_view<TimestampResult>&& timestampResultArray) uint64_t TimestampResult::GetDurationInTicks() const
{ {
// Loop through all the child passes, and accumulate all the timestampTicks return m_duration;
for (const TimestampResult& timestampResult : timestampResultArray)
{
m_timestampInTicks += timestampResult.m_timestampInTicks;
}
} }
uint64_t TimestampResult::GetTimestampInNanoseconds() const uint64_t TimestampResult::GetTimestampBeginInTicks() const
{ {
const RHI::Ptr<RHI::Device> device = RHI::GetRHIDevice(); return m_begin;
const AZStd::chrono::microseconds timeInMicroseconds = device->GpuTimestampToMicroseconds(m_timestampInTicks, RHI::HardwareQueueClass::Graphics);
const auto timeInNanoseconds = AZStd::chrono::nanoseconds(timeInMicroseconds);
return static_cast<uint64_t>(timeInNanoseconds.count());
} }
uint64_t TimestampResult::GetTimestampInTicks() const void TimestampResult::Add(const TimestampResult& extent)
{ {
return m_timestampInTicks; uint64_t end1 = m_begin + m_duration;
uint64_t end2 = extent.m_begin + extent.m_duration;
m_begin = m_begin < extent.m_begin ? m_begin : extent.m_begin;
m_duration = (end1 > end2 ? end1 : end2) - m_begin;
} }
// --- PipelineStatisticsResult --- // --- PipelineStatisticsResult ---

@ -393,19 +393,6 @@ namespace AZ
} }
} }
TimestampResult ParentPass::GetTimestampResultInternal() const
{
AZStd::vector<TimestampResult> timestampResultArray;
timestampResultArray.reserve(m_children.size());
// Calculate the Timestamp result by summing all of its child's TimestampResults
for (const Ptr<Pass>& childPass : m_children)
{
timestampResultArray.emplace_back(childPass->GetTimestampResult());
}
return TimestampResult(timestampResultArray);
}
PipelineStatisticsResult ParentPass::GetPipelineStatisticsResultInternal() const PipelineStatisticsResult ParentPass::GetPipelineStatisticsResultInternal() const
{ {
AZStd::vector<PipelineStatisticsResult> pipelineStatisticsResultArray; AZStd::vector<PipelineStatisticsResult> pipelineStatisticsResultArray;
@ -414,7 +401,7 @@ namespace AZ
// Calculate the PipelineStatistics result by summing all of its child's PipelineStatistics // Calculate the PipelineStatistics result by summing all of its child's PipelineStatistics
for (const Ptr<Pass>& childPass : m_children) for (const Ptr<Pass>& childPass : m_children)
{ {
pipelineStatisticsResultArray.emplace_back(childPass->GetPipelineStatisticsResult()); pipelineStatisticsResultArray.emplace_back(childPass->GetLatestPipelineStatisticsResult());
} }
return PipelineStatisticsResult(pipelineStatisticsResultArray); return PipelineStatisticsResult(pipelineStatisticsResultArray);
} }

@ -1273,24 +1273,14 @@ namespace AZ
} }
} }
TimestampResult Pass::GetTimestampResult() const TimestampResult Pass::GetLatestTimestampResult() const
{ {
if (IsEnabled() && IsTimestampQueryEnabled()) return GetTimestampResultInternal();
{
return GetTimestampResultInternal();
}
return TimestampResult();
} }
PipelineStatisticsResult Pass::GetPipelineStatisticsResult() const PipelineStatisticsResult Pass::GetLatestPipelineStatisticsResult() const
{ {
if (IsEnabled() && IsPipelineStatisticsQueryEnabled()) return GetPipelineStatisticsResultInternal();
{
return GetPipelineStatisticsResultInternal();
}
return PipelineStatisticsResult();
} }
TimestampResult Pass::GetTimestampResultInternal() const TimestampResult Pass::GetTimestampResultInternal() const

@ -174,7 +174,7 @@ namespace AZ
} }
else if (GetAttachmentType() == RHI::AttachmentType::Buffer) else if (GetAttachmentType() == RHI::AttachmentType::Buffer)
{ {
bool isInputAssembly = RHI::CheckBitsAny(m_descriptor.m_buffer.m_bindFlags, RHI::BufferBindFlags::InputAssembly); bool isInputAssembly = RHI::CheckBitsAny(m_descriptor.m_buffer.m_bindFlags, RHI::BufferBindFlags::InputAssembly | RHI::BufferBindFlags::DynamicInputAssembly);
bool isConstant = RHI::CheckBitsAny(m_descriptor.m_buffer.m_bindFlags, RHI::BufferBindFlags::Constant); bool isConstant = RHI::CheckBitsAny(m_descriptor.m_buffer.m_bindFlags, RHI::BufferBindFlags::Constant);
// Since InputAssembly and Constant cannot be inferred they are set manually. If those flags are set we don't want to add inferred flags on top as it may have a performance penalty // Since InputAssembly and Constant cannot be inferred they are set manually. If those flags are set we don't want to add inferred flags on top as it may have a performance penalty

@ -539,7 +539,7 @@ namespace AZ
const uint32_t TimestampResultQueryCount = 2u; const uint32_t TimestampResultQueryCount = 2u;
uint64_t timestampResult[TimestampResultQueryCount] = {0}; uint64_t timestampResult[TimestampResultQueryCount] = {0};
query->GetLatestResult(&timestampResult, sizeof(uint64_t) * TimestampResultQueryCount); query->GetLatestResult(&timestampResult, sizeof(uint64_t) * TimestampResultQueryCount);
m_timestampResult = TimestampResult(timestampResult[0], timestampResult[1]); m_timestampResult = TimestampResult(timestampResult[0], timestampResult[1], RHI::HardwareQueueClass::Graphics);
}); });
ExecuteOnPipelineStatisticsQuery([this](RHI::Ptr<Query> query) ExecuteOnPipelineStatisticsQuery([this](RHI::Ptr<Query> query)

@ -173,6 +173,7 @@ namespace AZ
m_serviceThread.join(); m_serviceThread.join();
Data::AssetBus::MultiHandler::BusDisconnect(); Data::AssetBus::MultiHandler::BusDisconnect();
m_newShaderVariantPendingRequests.clear();
m_shaderVariantTreePendingRequests.clear(); m_shaderVariantTreePendingRequests.clear();
m_shaderVariantPendingRequests.clear(); m_shaderVariantPendingRequests.clear();
m_shaderVariantData.clear(); m_shaderVariantData.clear();

@ -109,8 +109,8 @@ namespace MaterialEditor
AZ::RHI::Ptr<AZ::RPI::ParentPass> rootPass = AZ::RPI::PassSystemInterface::Get()->GetRootPass(); AZ::RHI::Ptr<AZ::RPI::ParentPass> rootPass = AZ::RPI::PassSystemInterface::Get()->GetRootPass();
if (rootPass) if (rootPass)
{ {
AZ::RPI::TimestampResult timestampResult = rootPass->GetTimestampResult(); AZ::RPI::TimestampResult timestampResult = rootPass->GetLatestTimestampResult();
double gpuFrameTimeMs = aznumeric_cast<double>(timestampResult.GetTimestampInNanoseconds()) / 1000000; double gpuFrameTimeMs = aznumeric_cast<double>(timestampResult.GetDurationInNanoseconds()) / 1000000;
m_gpuFrameTimeMs.PushSample(gpuFrameTimeMs); m_gpuFrameTimeMs.PushSample(gpuFrameTimeMs);
} }
} }

@ -93,7 +93,9 @@ namespace AZ
ImGuiPipelineStatisticsView(); ImGuiPipelineStatisticsView();
//! Draw the PipelineStatistics window. //! Draw the PipelineStatistics window.
void DrawPipelineStatisticsWindow(bool& draw, const PassEntry* rootPassEntry, AZStd::unordered_map<AZ::Name, PassEntry>& m_timestampEntryDatabase); void DrawPipelineStatisticsWindow(bool& draw, const PassEntry* rootPassEntry,
AZStd::unordered_map<AZ::Name, PassEntry>& m_timestampEntryDatabase,
AZ::RHI::Ptr<AZ::RPI::ParentPass> rootPass);
//! Total number of columns (Attribute columns + PassName column). //! Total number of columns (Attribute columns + PassName column).
static const uint32_t HeaderAttributeCount = PassEntry::PipelineStatisticsAttributeCount + 1u; static const uint32_t HeaderAttributeCount = PassEntry::PipelineStatisticsAttributeCount + 1u;
@ -139,6 +141,9 @@ namespace AZ
// ImGui filter used to filter passes by the user's input. // ImGui filter used to filter passes by the user's input.
ImGuiTextFilter m_passFilter; ImGuiTextFilter m_passFilter;
// Pause and showing the pipeline statistics result when it's paused.
bool m_paused = false;
}; };
class ImGuiTimestampView class ImGuiTimestampView
@ -180,9 +185,19 @@ namespace AZ
Count Count
}; };
// Timestamp refresh type .
enum class RefreshType : int32_t
{
Realtime = 0,
OncePerSecond,
Count
};
public: public:
//! Draw the Timestamp window. //! Draw the Timestamp window.
void DrawTimestampWindow(bool& draw, const PassEntry* rootPassEntry, AZStd::unordered_map<Name, PassEntry>& m_timestampEntryDatabase); void DrawTimestampWindow(bool& draw, const PassEntry* rootPassEntry,
AZStd::unordered_map<Name, PassEntry>& m_timestampEntryDatabase,
AZ::RHI::Ptr<AZ::RPI::ParentPass> rootPass);
private: private:
// Draw option for the hierarchical view of the passes. // Draw option for the hierarchical view of the passes.
@ -223,6 +238,20 @@ namespace AZ
// ImGui filter used to filter passes. // ImGui filter used to filter passes.
ImGuiTextFilter m_passFilter; ImGuiTextFilter m_passFilter;
// Pause and showing the timestamp result when it's paused.
bool m_paused = false;
// Hide non-parent passes which has 0 execution time.
bool m_hideZeroPasses = false;
// Show pass execution timeline
bool m_showTimeline = false;
// Controls how often the timestamp data is refreshed
RefreshType m_refreshType = RefreshType::OncePerSecond;
AZStd::sys_time_t m_lastUpdateTimeMicroSecond;
}; };
class ImGuiGpuProfiler class ImGuiGpuProfiler

@ -105,9 +105,9 @@ namespace AZ
// [GFX TODO][ATOM-4001] Cache the timestamp and PipelineStatistics results. // [GFX TODO][ATOM-4001] Cache the timestamp and PipelineStatistics results.
// Get the query results from the passes. // Get the query results from the passes.
m_timestampResult = pass->GetTimestampResult(); m_timestampResult = pass->GetLatestTimestampResult();
const RPI::PipelineStatisticsResult rps = pass->GetPipelineStatisticsResult(); const RPI::PipelineStatisticsResult rps = pass->GetLatestPipelineStatisticsResult();
m_pipelineStatistics = { rps.m_vertexCount, rps.m_primitiveCount, rps.m_vertexShaderInvocationCount, m_pipelineStatistics = { rps.m_vertexCount, rps.m_primitiveCount, rps.m_vertexShaderInvocationCount,
rps.m_rasterizedPrimitiveCount, rps.m_renderedPrimitiveCount, rps.m_pixelShaderInvocationCount, rps.m_computeShaderInvocationCount }; rps.m_rasterizedPrimitiveCount, rps.m_renderedPrimitiveCount, rps.m_pixelShaderInvocationCount, rps.m_computeShaderInvocationCount };
@ -153,7 +153,9 @@ namespace AZ
} }
inline void ImGuiPipelineStatisticsView::DrawPipelineStatisticsWindow(bool& draw, const PassEntry* rootPassEntry, AZStd::unordered_map<Name, PassEntry>& passEntryDatabase) inline void ImGuiPipelineStatisticsView::DrawPipelineStatisticsWindow(bool& draw,
const PassEntry* rootPassEntry, AZStd::unordered_map<Name, PassEntry>& passEntryDatabase,
AZ::RHI::Ptr<RPI::ParentPass> rootPass)
{ {
// Early out if nothing is supposed to be drawn // Early out if nothing is supposed to be drawn
if (!draw) if (!draw)
@ -188,12 +190,6 @@ namespace AZ
continue; continue;
} }
// Filter out disabled passes for the PipelineStatistics window if necessary.
if (!m_showDisabledPasses && !passEntry.IsPipelineStatisticsEnabled())
{
continue;
}
// Filter out parent passes if necessary. // Filter out parent passes if necessary.
if (!m_showParentPasses && passEntry.m_isParent) if (!m_showParentPasses && passEntry.m_isParent)
{ {
@ -230,6 +226,13 @@ namespace AZ
// Start drawing the PipelineStatistics window. // Start drawing the PipelineStatistics window.
if (ImGui::Begin("PipelineStatistics Window", &draw, ImGuiWindowFlags_NoResize)) if (ImGui::Begin("PipelineStatistics Window", &draw, ImGuiWindowFlags_NoResize))
{ {
// Pause/unpause the profiling
if (ImGui::Button(m_paused ? "Resume" : "Pause"))
{
m_paused = !m_paused;
rootPass->SetPipelineStatisticsQueryEnabled(!m_paused);
}
ImGui::Columns(2, "HeaderColumns"); ImGui::Columns(2, "HeaderColumns");
// Draw the statistics of the RootPass. // Draw the statistics of the RootPass.
@ -426,23 +429,16 @@ namespace AZ
} }
AZStd::string label; AZStd::string label;
if (passEntry->IsPipelineStatisticsEnabled()) if (rootEntry && m_showAttributeContribution)
{ {
if (rootEntry && m_showAttributeContribution) label = AZStd::string::format("%llu (%u%%)",
{ static_cast<AZ::u64>(passEntry->m_pipelineStatistics[attributeIdx]),
label = AZStd::string::format("%llu (%u%%)", static_cast<uint32_t>(normalized * 100.0f));
static_cast<AZ::u64>(passEntry->m_pipelineStatistics[attributeIdx]),
static_cast<uint32_t>(normalized * 100.0f));
}
else
{
label = AZStd::string::format("%llu",
static_cast<AZ::u64>(passEntry->m_pipelineStatistics[attributeIdx]));
}
} }
else else
{ {
label = "-"; label = AZStd::string::format("%llu",
static_cast<AZ::u64>(passEntry->m_pipelineStatistics[attributeIdx]));
} }
if (rootEntry) if (rootEntry)
@ -523,7 +519,9 @@ namespace AZ
// --- ImGuiTimestampView --- // --- ImGuiTimestampView ---
inline void ImGuiTimestampView::DrawTimestampWindow(bool& draw, const PassEntry* rootPassEntry, AZStd::unordered_map<Name, PassEntry>& timestampEntryDatabase) inline void ImGuiTimestampView::DrawTimestampWindow(
bool& draw, const PassEntry* rootPassEntry, AZStd::unordered_map<Name, PassEntry>& timestampEntryDatabase,
AZ::RHI::Ptr<RPI::ParentPass> rootPass)
{ {
// Early out if nothing is supposed to be drawn // Early out if nothing is supposed to be drawn
if (!draw) if (!draw)
@ -534,10 +532,28 @@ namespace AZ
// Clear the references from the previous frame. // Clear the references from the previous frame.
m_passEntryReferences.clear(); m_passEntryReferences.clear();
// pass entry grid based on its timestamp
AZStd::vector<PassEntry*> sortedPassEntries;
AZStd::vector<AZStd::vector<PassEntry*>> sortedPassGrid;
// Set the child of the parent, only if it passes the filter. // Set the child of the parent, only if it passes the filter.
for (auto& passEntryIt : timestampEntryDatabase) for (auto& passEntryIt : timestampEntryDatabase)
{ {
PassEntry* passEntry = &passEntryIt.second; PassEntry* passEntry = &passEntryIt.second;
// Collect all pass entries with non-zero durations
if (passEntry->m_timestampResult.GetDurationInTicks() > 0)
{
sortedPassEntries.push_back(passEntry);
}
// Skip the pass if the pass' timestamp duration is 0
if (m_hideZeroPasses && (!passEntry->m_isParent) && passEntry->m_timestampResult.GetDurationInTicks() == 0)
{
continue;
}
// Only add pass if it pass the filter.
if (m_passFilter.PassFilter(passEntry->m_name.GetCStr())) if (m_passFilter.PassFilter(passEntry->m_name.GetCStr()))
{ {
if (passEntry->m_parent && !passEntry->m_linked) if (passEntry->m_parent && !passEntry->m_linked)
@ -545,19 +561,94 @@ namespace AZ
passEntry->m_parent->LinkChild(passEntry); passEntry->m_parent->LinkChild(passEntry);
} }
AZ_Assert(m_passEntryReferences.size() < TimestampEntryCount, "Too many PassEntry references. Increase the size of the array."); AZ_Assert(
m_passEntryReferences.size() < TimestampEntryCount,
"Too many PassEntry references. Increase the size of the array.");
m_passEntryReferences.push_back(passEntry); m_passEntryReferences.push_back(passEntry);
} }
} }
// Sort the pass entries based on their starting time and duration
AZStd::sort(sortedPassEntries.begin(), sortedPassEntries.end(), [](const PassEntry* passEntry1, const PassEntry* passEntry2) {
if (passEntry1->m_timestampResult.GetTimestampBeginInTicks() == passEntry2->m_timestampResult.GetTimestampBeginInTicks())
{
return passEntry1->m_timestampResult.GetDurationInTicks() < passEntry2->m_timestampResult.GetDurationInTicks();
}
return passEntry1->m_timestampResult.GetTimestampBeginInTicks() < passEntry2->m_timestampResult.GetTimestampBeginInTicks();
});
// calculate the total GPU duration.
RPI::TimestampResult gpuTimestamp;
if (sortedPassEntries.size() > 0)
{
gpuTimestamp = sortedPassEntries.front()->m_timestampResult;
gpuTimestamp.Add(sortedPassEntries.back()->m_timestampResult);
}
// Add a pass to the pass grid which none of the pass's timestamp range won't overlap each other.
// Search each row until the pass can be added to the end of row without overlap the previous one.
for (auto& passEntry : sortedPassEntries)
{
auto row = sortedPassGrid.begin();
for (; row != sortedPassGrid.end(); row++)
{
if (row->empty())
{
break;
}
auto last = (*row).back();
if (passEntry->m_timestampResult.GetTimestampBeginInTicks() >=
last->m_timestampResult.GetTimestampBeginInTicks() + last->m_timestampResult.GetDurationInTicks())
{
row->push_back(passEntry);
break;
}
}
if (row == sortedPassGrid.end())
{
sortedPassGrid.push_back();
sortedPassGrid.back().push_back(passEntry);
}
}
// Refresh timestamp query
bool needEnable = false;
if (!m_paused)
{
if (m_refreshType == RefreshType::OncePerSecond)
{
auto now = AZStd::GetTimeNowMicroSecond();
if (m_lastUpdateTimeMicroSecond == 0 || now - m_lastUpdateTimeMicroSecond > 1000000)
{
needEnable = true;
m_lastUpdateTimeMicroSecond = now;
}
}
else if (m_refreshType == RefreshType::Realtime)
{
needEnable = true;
}
}
if (rootPass->IsTimestampQueryEnabled() != needEnable)
{
rootPass->SetTimestampQueryEnabled(needEnable);
}
const ImVec2 windowSize(680.0f, 620.0f); const ImVec2 windowSize(680.0f, 620.0f);
ImGui::SetNextWindowSize(windowSize, ImGuiCond_Always); ImGui::SetNextWindowSize(windowSize, ImGuiCond_Always);
if (ImGui::Begin("Timestamp View", &draw, ImGuiWindowFlags_NoResize)) if (ImGui::Begin("Timestamp View", &draw, ImGuiWindowFlags_NoResize))
{ {
// Draw the header. // Draw the header.
{ {
// Pause/unpause the profiling
if (ImGui::Button(m_paused? "Resume":"Pause"))
{
m_paused = !m_paused;
}
// Draw the frame time (GPU). // Draw the frame time (GPU).
const AZStd::string formattedTimestamp = FormatTimestampLabel(rootPassEntry->m_interpolatedTimestampInNanoseconds); const AZStd::string formattedTimestamp = FormatTimestampLabel(gpuTimestamp.GetDurationInNanoseconds());
const AZStd::string headerFrameTime = AZStd::string::format("Total frame duration (GPU): %s", formattedTimestamp.c_str()); const AZStd::string headerFrameTime = AZStd::string::format("Total frame duration (GPU): %s", formattedTimestamp.c_str());
ImGui::Text(headerFrameTime.c_str()); ImGui::Text(headerFrameTime.c_str());
@ -566,6 +657,17 @@ namespace AZ
ImGui::SameLine(); ImGui::SameLine();
ImGui::RadioButton("Flat", reinterpret_cast<int32_t*>(&m_viewType), static_cast<int32_t>(ProfilerViewType::Flat)); ImGui::RadioButton("Flat", reinterpret_cast<int32_t*>(&m_viewType), static_cast<int32_t>(ProfilerViewType::Flat));
// Draw the refresh option
ImGui::RadioButton("Realtime", reinterpret_cast<int32_t*>(&m_refreshType), static_cast<int32_t>(RefreshType::Realtime));
ImGui::SameLine();
ImGui::RadioButton("Once Per Second", reinterpret_cast<int32_t*>(&m_refreshType), static_cast<int32_t>(RefreshType::OncePerSecond));
// Show/hide non-parent passes which have zero execution time
ImGui::Checkbox("Hide Zero Cost Passes", &m_hideZeroPasses);
// Show/hide the timeline bar of all the passes which has non-zero execution time
ImGui::Checkbox("Show Timeline", &m_showTimeline);
// Draw advanced options. // Draw advanced options.
const ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_None; const ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_None;
GpuProfilerImGuiHelper::TreeNode("Advanced options", flags, [this](bool unrolled) GpuProfilerImGuiHelper::TreeNode("Advanced options", flags, [this](bool unrolled)
@ -587,6 +689,56 @@ namespace AZ
ImGui::Separator(); ImGui::Separator();
// Draw the pass entry grid
if (!sortedPassEntries.empty() && m_showTimeline)
{
const float passBarHeight = 20.f;
const float passBarSpace = 3.f;
float areaWidth = ImGui::GetContentRegionAvail().x - 20.f;
if (ImGui::BeginChild("Timeline", ImVec2(areaWidth, (passBarHeight + passBarSpace) * sortedPassGrid.size()), false))
{
// start tick and end tick for the area
uint64_t areaStartTick = sortedPassEntries.front()->m_timestampResult.GetTimestampBeginInTicks();
uint64_t areaEndTick = sortedPassEntries.back()->m_timestampResult.GetTimestampBeginInTicks() +
sortedPassEntries.back()->m_timestampResult.GetDurationInTicks();
uint64_t areaDurationInTicks = areaEndTick - areaStartTick;
float rowStartY = 0.f;
for (auto& row : sortedPassGrid)
{
// row start y
for (auto passEntry : row)
{
// button start and end
float buttonStartX = (passEntry->m_timestampResult.GetTimestampBeginInTicks() - areaStartTick) * areaWidth /
areaDurationInTicks;
float buttonWidth = passEntry->m_timestampResult.GetDurationInTicks() * areaWidth / areaDurationInTicks;
ImGui::SetCursorPosX(buttonStartX);
ImGui::SetCursorPosY(rowStartY);
// Adds a button and the hover colors.
ImGui::Button(passEntry->m_name.GetCStr(), ImVec2(buttonWidth, passBarHeight));
if (ImGui::IsItemHovered())
{
ImGui::BeginTooltip();
ImGui::Text("Name: %s", passEntry->m_name.GetCStr());
ImGui::Text("Path: %s", passEntry->m_path.GetCStr());
ImGui::Text("Duration in ticks: %lu", passEntry->m_timestampResult.GetDurationInTicks());
ImGui::Text("Duration in microsecond: %.3f us", passEntry->m_timestampResult.GetDurationInNanoseconds()/1000.f);
ImGui::EndTooltip();
}
}
rowStartY += passBarHeight + passBarSpace;
}
}
ImGui::EndChild();
ImGui::Separator();
}
// Draw the timestamp view. // Draw the timestamp view.
{ {
static const AZStd::array<const char*, static_cast<int32_t>(TimestampMetricUnit::Count)> MetricUnitText = static const AZStd::array<const char*, static_cast<int32_t>(TimestampMetricUnit::Count)> MetricUnitText =
@ -713,20 +865,18 @@ namespace AZ
const auto drawWorkloadBar = [this](const AZStd::string& entryTime, const PassEntry* entry) const auto drawWorkloadBar = [this](const AZStd::string& entryTime, const PassEntry* entry)
{ {
ImGui::NextColumn(); ImGui::NextColumn();
ImGui::Text(entryTime.c_str()); if (entry->m_isParent)
ImGui::NextColumn();
// Only draw the workload bar when the entry is enabled.
if (entry->IsTimestampEnabled())
{ {
DrawFrameWorkloadBar(NormalizeFrameWorkload(entry->m_interpolatedTimestampInNanoseconds)); ImGui::NextColumn();
ImGui::NextColumn();
} }
else else
{ {
ImGui::ProgressBar(0.0f, ImVec2(-1.0f, 0.0f), "Disabled"); ImGui::Text(entryTime.c_str());
ImGui::NextColumn();
DrawFrameWorkloadBar(NormalizeFrameWorkload(entry->m_interpolatedTimestampInNanoseconds));
ImGui::NextColumn();
} }
ImGui::NextColumn();
}; };
static const auto createHoverMarker = [](const char* text) static const auto createHoverMarker = [](const char* text)
@ -800,23 +950,17 @@ namespace AZ
// Draw the flat view. // Draw the flat view.
for (const PassEntry* entry : m_passEntryReferences) for (const PassEntry* entry : m_passEntryReferences)
{ {
if (entry->m_isParent)
{
continue;
}
const AZStd::string entryTime = FormatTimestampLabel(entry->m_interpolatedTimestampInNanoseconds); const AZStd::string entryTime = FormatTimestampLabel(entry->m_interpolatedTimestampInNanoseconds);
ImGui::Text(entry->m_name.GetCStr()); ImGui::Text(entry->m_name.GetCStr());
ImGui::NextColumn(); ImGui::NextColumn();
ImGui::Text(entryTime.c_str()); ImGui::Text(entryTime.c_str());
ImGui::NextColumn(); ImGui::NextColumn();
DrawFrameWorkloadBar(NormalizeFrameWorkload(entry->m_interpolatedTimestampInNanoseconds));
// Only draw the workload bar if the entry is enabled.
if (entry->IsTimestampEnabled())
{
DrawFrameWorkloadBar(NormalizeFrameWorkload(entry->m_interpolatedTimestampInNanoseconds));
}
else
{
ImGui::ProgressBar(0.0f, ImVec2(-1.0f, 0.0f), "Disabled");
}
ImGui::NextColumn(); ImGui::NextColumn();
} }
} }
@ -890,23 +1034,33 @@ namespace AZ
// Update the PassEntry database. // Update the PassEntry database.
const PassEntry* rootPassEntryRef = CreatePassEntries(rootPass); const PassEntry* rootPassEntryRef = CreatePassEntries(rootPass);
bool wasDraw = draw;
GpuProfilerImGuiHelper::Begin("Gpu Profiler", &draw, ImGuiWindowFlags_NoResize, [this, &rootPass]() GpuProfilerImGuiHelper::Begin("Gpu Profiler", &draw, ImGuiWindowFlags_NoResize, [this, &rootPass]()
{ {
ImGui::Checkbox("Enable TimestampView", &m_drawTimestampView); if (ImGui::Checkbox("Enable TimestampView", &m_drawTimestampView))
{
rootPass->SetTimestampQueryEnabled(m_drawTimestampView);
}
ImGui::Spacing(); ImGui::Spacing();
ImGui::Checkbox("Enable PipelineStatisticsView", &m_drawPipelineStatisticsView); if(ImGui::Checkbox("Enable PipelineStatisticsView", &m_drawPipelineStatisticsView))
{
rootPass->SetPipelineStatisticsQueryEnabled(m_drawPipelineStatisticsView);
}
}); });
// Draw the PipelineStatistics window. // Draw the PipelineStatistics window.
m_timestampView.DrawTimestampWindow(m_drawTimestampView, rootPassEntryRef, m_passEntryDatabase); m_timestampView.DrawTimestampWindow(m_drawTimestampView, rootPassEntryRef, m_passEntryDatabase, rootPass);
// Draw the PipelineStatistics window. // Draw the PipelineStatistics window.
m_pipelineStatisticsView.DrawPipelineStatisticsWindow(m_drawPipelineStatisticsView, rootPassEntryRef, m_passEntryDatabase); m_pipelineStatisticsView.DrawPipelineStatisticsWindow(m_drawPipelineStatisticsView, rootPassEntryRef, m_passEntryDatabase, rootPass);
// [GFX TODO][ATOM-13792] Optimization: ImGui GpuProfiler Pass hierarchy traversal. //closing window
// Enable/Disable the Timestamp and PipelineStatistics on the RootPass if (wasDraw && !draw)
rootPass->SetTimestampQueryEnabled(draw && m_drawTimestampView); {
rootPass->SetPipelineStatisticsQueryEnabled(draw && m_drawPipelineStatisticsView); rootPass->SetTimestampQueryEnabled(false);
rootPass->SetPipelineStatisticsQueryEnabled(false);
}
} }
inline void ImGuiGpuProfiler::InterpolatePassEntries(AZStd::unordered_map<Name, PassEntry>& passEntryDatabase, float weight) const inline void ImGuiGpuProfiler::InterpolatePassEntries(AZStd::unordered_map<Name, PassEntry>& passEntryDatabase, float weight) const
@ -918,7 +1072,7 @@ namespace AZ
{ {
// Interpolate the timestamps. // Interpolate the timestamps.
const double interpolated = Lerp(static_cast<double>(oldEntryIt->second.m_interpolatedTimestampInNanoseconds), const double interpolated = Lerp(static_cast<double>(oldEntryIt->second.m_interpolatedTimestampInNanoseconds),
static_cast<double>(entry.second.m_timestampResult.GetTimestampInNanoseconds()), static_cast<double>(entry.second.m_timestampResult.GetDurationInNanoseconds()),
static_cast<double>(weight)); static_cast<double>(weight));
entry.second.m_interpolatedTimestampInNanoseconds = static_cast<uint64_t>(interpolated); entry.second.m_interpolatedTimestampInNanoseconds = static_cast<uint64_t>(interpolated);
} }

@ -54,8 +54,9 @@ namespace AZ
RPI::SceneDescriptor sceneDesc; RPI::SceneDescriptor sceneDesc;
sceneDesc.m_featureProcessorNames.push_back("AZ::Render::TransformServiceFeatureProcessor"); sceneDesc.m_featureProcessorNames.push_back("AZ::Render::TransformServiceFeatureProcessor");
sceneDesc.m_featureProcessorNames.push_back("AZ::Render::MeshFeatureProcessor"); sceneDesc.m_featureProcessorNames.push_back("AZ::Render::MeshFeatureProcessor");
sceneDesc.m_featureProcessorNames.push_back("AZ::Render::SimplePointLightFeatureProcessor");
sceneDesc.m_featureProcessorNames.push_back("AZ::Render::SimpleSpotLightFeatureProcessor");
sceneDesc.m_featureProcessorNames.push_back("AZ::Render::PointLightFeatureProcessor"); sceneDesc.m_featureProcessorNames.push_back("AZ::Render::PointLightFeatureProcessor");
sceneDesc.m_featureProcessorNames.push_back("AZ::Render::SpotLightFeatureProcessor");
// There is currently a bug where having multiple DirectionalLightFeatureProcessors active can result in shadow flickering [ATOM-13568] // There is currently a bug where having multiple DirectionalLightFeatureProcessors active can result in shadow flickering [ATOM-13568]
// as well as continually rebuilding MeshDrawPackets [ATOM-13633]. Lets just disable the directional light FP for now. // as well as continually rebuilding MeshDrawPackets [ATOM-13633]. Lets just disable the directional light FP for now.
// Possibly re-enable with [GFX TODO][ATOM-13639] // Possibly re-enable with [GFX TODO][ATOM-13639]

@ -179,7 +179,7 @@ namespace Blast
void EditorBlastMeshDataComponent::RegisterModel() void EditorBlastMeshDataComponent::RegisterModel()
{ {
if (m_meshFeatureProcessor && m_meshAssets[0].GetId().IsValid()) if (m_meshFeatureProcessor && !m_meshAssets.empty() && m_meshAssets[0].GetId().IsValid())
{ {
AZ::Render::MaterialAssignmentMap materials; AZ::Render::MaterialAssignmentMap materials;
AZ::Render::MaterialComponentRequestBus::EventResult( AZ::Render::MaterialComponentRequestBus::EventResult(

@ -98,15 +98,16 @@ namespace Camera
AZ_Assert(m_atomCamera, "Attempted to activate Atom camera before component activation"); AZ_Assert(m_atomCamera, "Attempted to activate Atom camera before component activation");
const AZ::Name contextName = atomViewportRequests->GetDefaultViewportContextName(); const AZ::Name contextName = atomViewportRequests->GetDefaultViewportContextName();
atomViewportRequests->PushView(contextName, m_atomCamera);
AZ::RPI::ViewportContextNotificationBus::Handler::BusConnect(contextName);
// Ensure the Atom camera is updated with our current transform state // Ensure the Atom camera is updated with our current transform state
AZ::Transform localTransform; AZ::Transform localTransform;
AZ::TransformBus::EventResult(localTransform, m_entityId, &AZ::TransformBus::Events::GetLocalTM); AZ::TransformBus::EventResult(localTransform, m_entityId, &AZ::TransformBus::Events::GetLocalTM);
AZ::Transform worldTransform; AZ::Transform worldTransform;
AZ::TransformBus::EventResult(worldTransform, m_entityId, &AZ::TransformBus::Events::GetWorldTM); AZ::TransformBus::EventResult(worldTransform, m_entityId, &AZ::TransformBus::Events::GetWorldTM);
OnTransformChanged(localTransform, worldTransform); OnTransformChanged(localTransform, worldTransform);
// Push the Atom camera after we make sure we're up-to-date with our component's transform to ensure the viewport reads the correct state
atomViewportRequests->PushView(contextName, m_atomCamera);
AZ::RPI::ViewportContextNotificationBus::Handler::BusConnect(contextName);
UpdateCamera(); UpdateCamera();
} }
} }

@ -222,6 +222,10 @@ void ImGuiManager::Initialize()
io.DisplaySize.x = 1920; io.DisplaySize.x = 1920;
io.DisplaySize.y = 1080; io.DisplaySize.y = 1080;
// Create a default font
io.Fonts->AddFontDefault();
io.Fonts->Build();
// Broadcast ImGui Ready to Listeners // Broadcast ImGui Ready to Listeners
ImGuiUpdateListenerBus::Broadcast(&IImGuiUpdateListener::OnImGuiInitialize); ImGuiUpdateListenerBus::Broadcast(&IImGuiUpdateListener::OnImGuiInitialize);
m_currentControllerIndex = -1; m_currentControllerIndex = -1;

@ -95,4 +95,20 @@ namespace Multiplayer
private: private:
MultiplayerStats m_stats; MultiplayerStats m_stats;
}; };
inline const char* GetEnumString(MultiplayerAgentType value)
{
switch (value)
{
case MultiplayerAgentType::Uninitialized:
return "Uninitialized";
case MultiplayerAgentType::Client:
return "Client";
case MultiplayerAgentType::ClientServer:
return "ClientServer";
case MultiplayerAgentType::DedicatedServer:
return "DedicatedServer";
}
return "INVALID";
}
} }

@ -1,6 +1,7 @@
#pragma once #pragma once
#include <AzCore/std/containers/list.h> #include <AzCore/std/containers/list.h>
#include <Source/MultiplayerTypes.h>
namespace AZ namespace AZ
{ {
@ -17,7 +18,13 @@ namespace {{ Namespace }}
{% set ComponentName = Component.attrib['Name'] %} {% set ComponentName = Component.attrib['Name'] %}
{{ ComponentName }}, {{ ComponentName }},
{% endfor %} {% endfor %}
Count
}; };
static_assert(ComponentTypes::Count < static_cast<ComponentTypes>(Multiplayer::InvalidNetComponentId), "ComponentId overflow");
//! For reflecting multiplayer components into the serialize, edit, and behaviour contexts.
void CreateComponentDescriptors(AZStd::list<AZ::ComponentDescriptor*>& descriptors); void CreateComponentDescriptors(AZStd::list<AZ::ComponentDescriptor*>& descriptors);
//! For creating multiplayer component network inputs.
void CreateComponentNetworkInput();
} }

@ -33,22 +33,20 @@ const {{ Property.attrib['Type'] }}& Get{{ PropertyName }}() const;
#} #}
{% macro DeclareNetworkPropertySetter(Property) %} {% macro DeclareNetworkPropertySetter(Property) %}
{% set PropertyName = UpperFirst(Property.attrib['Name']) %} {% set PropertyName = UpperFirst(Property.attrib['Name']) %}
{% if Property.attrib['IsPredictable'] | booleanTrue %} {% if Property.attrib['Container'] == 'Array' %}
{% if Property.attrib['Container'] == 'Array' %} void Set{{ PropertyName }}(int32_t index, const {{ Property.attrib['Type'] }}& value);
void Set{{ PropertyName }}(const Multiplayer::NetworkInput&, int32_t index, const {{ Property.attrib['Type'] }}& value); {{ Property.attrib['Type'] }}& Modify{{ PropertyName }}(int32_t index);
{{ Property.attrib['Type'] }}& Modify{{ PropertyName }}(const Multiplayer::NetworkInput&, int32_t index); {% elif Property.attrib['Container'] == 'Vector' %}
{% elif Property.attrib['Container'] == 'Vector' %} void Set{{ PropertyName }}(int32_t index, const {{ Property.attrib['Type'] }}& value);
void Set{{ PropertyName }}(const Multiplayer::NetworkInput&, int32_t index, const {{ Property.attrib['Type'] }}& value); {{ Property.attrib['Type'] }}& Modify{{ PropertyName }}(int32_t index);
{{ Property.attrib['Type'] }}& Modify{{ PropertyName }}(const Multiplayer::NetworkInput&, int32_t index); bool {{ PropertyName }}PushBack(const {{ Property.attrib['Type'] }}& value);
bool {{ PropertyName }}PushBack(const Multiplayer::NetworkInput&, const {{ Property.attrib['Type'] }}& value); bool {{ PropertyName }}PopBack();
bool {{ PropertyName }}PopBack(const Multiplayer::NetworkInput&); void {{ PropertyName }}Clear();
void {{ PropertyName }}Clear(const Multiplayer::NetworkInput&); {% elif Property.attrib['Container'] == 'Object' %}
{% elif Property.attrib['Container'] == 'Object' %} void Set{{ PropertyName }}(const {{ Property.attrib['Type'] }}& value);
void Set{{ PropertyName }}(const Multiplayer::NetworkInput&, const {{ Property.attrib['Type'] }}& value); {{ Property.attrib['Type'] }}& Modify{{ PropertyName }}();
{{ Property.attrib['Type'] }}& Modify{{ PropertyName }}(const Multiplayer::NetworkInput&); {% else %}
{% else %} void Set{{ PropertyName }}(const {{ Property.attrib['Type'] }}& value);
void Set{{ PropertyName }}(const Multiplayer::NetworkInput&, const {{ Property.attrib['Type'] }}& value);
{% endif %}
{% endif %} {% endif %}
{% endmacro %} {% endmacro %}
{# {#
@ -417,6 +415,7 @@ namespace {{ Component.attrib['Namespace'] }}
static const Multiplayer::NetComponentId s_componentId = static_cast<Multiplayer::NetComponentId>({{ Component.attrib['Namespace'] }}::ComponentTypes::{{ Component.attrib['Name'] }}); static const Multiplayer::NetComponentId s_componentId = static_cast<Multiplayer::NetComponentId>({{ Component.attrib['Namespace'] }}::ComponentTypes::{{ Component.attrib['Name'] }});
static void Reflect(AZ::ReflectContext* context); static void Reflect(AZ::ReflectContext* context);
static void ReflectToEditContext(AZ::ReflectContext* context);
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided); static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided);
static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required); static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required);
static void GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent); static void GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent);

@ -73,18 +73,17 @@ void {{ ClassName }}::{{ UpperFirst(Property.attrib['Name']) }}AddEvent(AZ::Even
{# {#
#} #}
{% macro DefineNetworkPropertyPredictableSet(Component, ReplicateFrom, ReplicateTo, ClassName, Property) %} {% macro DefineNetworkPropertySet(Component, ReplicateFrom, ReplicateTo, ClassName, Property) %}
{% if Property.attrib['IsPredictable'] | booleanTrue %} {% if Property.attrib['Container'] == 'Array' %}
{% if Property.attrib['Container'] == 'Array' %} void {{ ClassName }}::Set{{ UpperFirst(Property.attrib['Name']) }}(int32_t index, const {{ Property.attrib['Type'] }}& value)
void {{ ClassName }}::Set{{ UpperFirst(Property.attrib['Name']) }}(const Multiplayer::NetworkInput& inputCommand, int32_t index, const {{ Property.attrib['Type'] }}& value)
{ {
if (GetParent().m_{{ LowerFirst(Property.attrib['Name']) }}[index] != value) if (GetParent().m_{{ LowerFirst(Property.attrib['Name']) }}[index] != value)
{ {
Modify{{ UpperFirst(Property.attrib['Name']) }}(inputCommand, index) = value; Modify{{ UpperFirst(Property.attrib['Name']) }}(index) = value;
} }
} }
{{ Property.attrib['Type'] }}& {{ ClassName }}::Modify{{ UpperFirst(Property.attrib['Name']) }}(const Multiplayer::NetworkInput&, int32_t index) {{ Property.attrib['Type'] }}& {{ ClassName }}::Modify{{ UpperFirst(Property.attrib['Name']) }}(int32_t index)
{ {
int32_t bitIndex = index + static_cast<int32_t>({{ AutoComponentMacros.GetNetPropertiesQualifiedPropertyDirtyEnum(Component.attrib['Name'], ReplicateFrom, ReplicateTo, Property, 'Start') }}); int32_t bitIndex = index + static_cast<int32_t>({{ AutoComponentMacros.GetNetPropertiesQualifiedPropertyDirtyEnum(Component.attrib['Name'], ReplicateFrom, ReplicateTo, Property, 'Start') }});
GetParent().m_currentRecord->m_{{ LowerFirst(AutoComponentMacros.GetNetPropertiesSetName(ReplicateFrom, ReplicateTo)) }}.SetBit(bitIndex, true); GetParent().m_currentRecord->m_{{ LowerFirst(AutoComponentMacros.GetNetPropertiesSetName(ReplicateFrom, ReplicateTo)) }}.SetBit(bitIndex, true);
@ -92,16 +91,16 @@ void {{ ClassName }}::Set{{ UpperFirst(Property.attrib['Name']) }}(const Multipl
return GetParent().m_{{ LowerFirst(Property.attrib['Name']) }}[index]; return GetParent().m_{{ LowerFirst(Property.attrib['Name']) }}[index];
} }
{% elif Property.attrib['Container'] == 'Vector' %} {% elif Property.attrib['Container'] == 'Vector' %}
void {{ ClassName }}::Set{{ UpperFirst(Property.attrib['Name']) }}(const Multiplayer::NetworkInput& inputCommand, int32_t index, const {{ Property.attrib['Type'] }}& value) void {{ ClassName }}::Set{{ UpperFirst(Property.attrib['Name']) }}(int32_t index, const {{ Property.attrib['Type'] }}& value)
{ {
if (GetParent().m_{{ LowerFirst(Property.attrib['Name']) }}[index] != value) if (GetParent().m_{{ LowerFirst(Property.attrib['Name']) }}[index] != value)
{ {
Modify{{ UpperFirst(Property.attrib['Name']) }}(inputCommand, index) = value; Modify{{ UpperFirst(Property.attrib['Name']) }}(index) = value;
} }
} }
{{ Property.attrib['Type'] }}& {{ ClassName }}::Modify{{ UpperFirst(Property.attrib['Name']) }}(const Multiplayer::NetworkInput&, int32_t index) {{ Property.attrib['Type'] }}& {{ ClassName }}::Modify{{ UpperFirst(Property.attrib['Name']) }}(int32_t index)
{ {
int32_t bitIndex = index + static_cast<int32_t>({{ AutoComponentMacros.GetNetPropertiesQualifiedPropertyDirtyEnum(Component.attrib['Name'], ReplicateFrom, ReplicateTo, Property, 'Start') }}); int32_t bitIndex = index + static_cast<int32_t>({{ AutoComponentMacros.GetNetPropertiesQualifiedPropertyDirtyEnum(Component.attrib['Name'], ReplicateFrom, ReplicateTo, Property, 'Start') }});
GetParent().m_currentRecord->m_{{ LowerFirst(AutoComponentMacros.GetNetPropertiesSetName(ReplicateFrom, ReplicateTo)) }}.SetBit(bitIndex, true); GetParent().m_currentRecord->m_{{ LowerFirst(AutoComponentMacros.GetNetPropertiesSetName(ReplicateFrom, ReplicateTo)) }}.SetBit(bitIndex, true);
@ -109,7 +108,7 @@ void {{ ClassName }}::Set{{ UpperFirst(Property.attrib['Name']) }}(const Multipl
return GetParent().m_{{ LowerFirst(Property.attrib['Name']) }}[index]; return GetParent().m_{{ LowerFirst(Property.attrib['Name']) }}[index];
} }
bool {{ ClassName }}::{{ UpperFirst(Property.attrib['Name']) }}PushBack(const Multiplayer::NetworkInput& inputCommand, const {{ Property.attrib['Type'] }} &value) bool {{ ClassName }}::{{ UpperFirst(Property.attrib['Name']) }}PushBack(const {{ Property.attrib['Type'] }} &value)
{ {
int32_t indexToSet = GetParent().m_{{ LowerFirst(Property.attrib['Name']) }}.GetSize(); int32_t indexToSet = GetParent().m_{{ LowerFirst(Property.attrib['Name']) }}.GetSize();
GetParent().m_{{ LowerFirst(Property.attrib['Name']) }}.PushBack(value); GetParent().m_{{ LowerFirst(Property.attrib['Name']) }}.PushBack(value);
@ -134,24 +133,24 @@ void {{ ClassName }}::{{ UpperFirst(Property.attrib['Name']) }}Clear(const Multi
GetParent().MarkDirty(); GetParent().MarkDirty();
} }
{% elif Property.attrib['Container'] == 'Object' %} {% elif Property.attrib['Container'] == 'Object' %}
void {{ ClassName }}::Set{{ UpperFirst(Property.attrib['Name']) }}(const Multiplayer::NetworkInput& inputCommand, const {{ Property.attrib['Type'] }}& value) void {{ ClassName }}::Set{{ UpperFirst(Property.attrib['Name']) }}(const {{ Property.attrib['Type'] }}& value)
{ {
if (GetParent().m_{{ LowerFirst(Property.attrib['Name']) }} != value) if (GetParent().m_{{ LowerFirst(Property.attrib['Name']) }} != value)
{ {
Modify{{ UpperFirst(Property.attrib['Name']) }}(inputCommand) = value; Modify{{ UpperFirst(Property.attrib['Name']) }}() = value;
} }
} }
{{ Property.attrib['Type'] }}& {{ ClassName }}::Modify{{ UpperFirst(Property.attrib['Name']) }}(const Multiplayer::NetworkInput&) {{ Property.attrib['Type'] }}& {{ ClassName }}::Modify{{ UpperFirst(Property.attrib['Name']) }}()
{ {
GetParent().m_currentRecord->m_{{ LowerFirst(AutoComponentMacros.GetNetPropertiesSetName(ReplicateFrom, ReplicateTo)) }}.SetBit(static_cast<int32_t>({{ AutoComponentMacros.GetNetPropertiesQualifiedPropertyDirtyEnum(Component.attrib['Name'], ReplicateFrom, ReplicateTo, Property) }}), true); GetParent().m_currentRecord->m_{{ LowerFirst(AutoComponentMacros.GetNetPropertiesSetName(ReplicateFrom, ReplicateTo)) }}.SetBit(static_cast<int32_t>({{ AutoComponentMacros.GetNetPropertiesQualifiedPropertyDirtyEnum(Component.attrib['Name'], ReplicateFrom, ReplicateTo, Property) }}), true);
GetParent().MarkDirty(); GetParent().MarkDirty();
return GetParent().m_{{ LowerFirst(Property.attrib['Name']) }}{% if Property.attrib['IsRewindable']|booleanTrue %}.Modify(){% endif %}; return GetParent().m_{{ LowerFirst(Property.attrib['Name']) }}{% if Property.attrib['IsRewindable']|booleanTrue %}.Modify(){% endif %};
} }
{% else %} {% else %}
void {{ ClassName }}::Set{{ UpperFirst(Property.attrib['Name']) }}(const Multiplayer::NetworkInput&, const {{ Property.attrib['Type'] }}& value) void {{ ClassName }}::Set{{ UpperFirst(Property.attrib['Name']) }}(const {{ Property.attrib['Type'] }}& value)
{ {
if (GetParent().m_{{ LowerFirst(Property.attrib['Name']) }} != value) if (GetParent().m_{{ LowerFirst(Property.attrib['Name']) }} != value)
{ {
@ -161,7 +160,6 @@ void {{ ClassName }}::Set{{ UpperFirst(Property.attrib['Name']) }}(const Multipl
} }
} }
{% endif %}
{% endif %} {% endif %}
{% endmacro %} {% endmacro %}
{# {#
@ -273,7 +271,7 @@ void {{ ClassName }}::Set{{ UpperFirst(Property.attrib['Name']) }}(const {{ Prop
{% call(Property) AutoComponentMacros.ParseNetworkProperties(Component, ReplicateFrom, ReplicateTo) %} {% call(Property) AutoComponentMacros.ParseNetworkProperties(Component, ReplicateFrom, ReplicateTo) %}
{% if Property.attrib['IsPublic'] | booleanTrue != IsProtected %} {% if Property.attrib['IsPublic'] | booleanTrue != IsProtected %}
{{ DefineNetworkPropertyGet(ClassName, Property, "GetParent().") }} {{ DefineNetworkPropertyGet(ClassName, Property, "GetParent().") }}
{{ DefineNetworkPropertyPredictableSet(Component, ReplicateFrom, ReplicateTo, ClassName, Property) }} {{ DefineNetworkPropertySet(Component, ReplicateFrom, ReplicateTo, ClassName, Property) }}
{% endif %} {% endif %}
{% endcall %} {% endcall %}
{% endmacro %} {% endmacro %}
@ -478,6 +476,7 @@ bool {{ ClassName }}::Serialize{{ AutoComponentMacros.GetNetPropertiesSetName(Re
{%- if networkPropertyCount.update({'value': networkPropertyCount.value + 1}) %}{% endif -%} {%- if networkPropertyCount.update({'value': networkPropertyCount.value + 1}) %}{% endif -%}
{% endcall %} {% endcall %}
{% if networkPropertyCount.value > 0 %} {% if networkPropertyCount.value > 0 %}
MultiplayerStats& stats = AZ::Interface<IMultiplayer>::Get()->GetStats();
// We modify the record if we are writing an update so that we don't notify for a change that really didn't change the value (just a duplicated send from the server) // We modify the record if we are writing an update so that we don't notify for a change that really didn't change the value (just a duplicated send from the server)
[[maybe_unused]] bool modifyRecord = serializer.GetSerializerMode() == AzNetworking::SerializerMode::WriteToObject; [[maybe_unused]] bool modifyRecord = serializer.GetSerializerMode() == AzNetworking::SerializerMode::WriteToObject;
{% call(Property) AutoComponentMacros.ParseNetworkProperties(Component, ReplicateFrom, ReplicateTo) %} {% call(Property) AutoComponentMacros.ParseNetworkProperties(Component, ReplicateFrom, ReplicateTo) %}
@ -509,7 +508,8 @@ bool {{ ClassName }}::Serialize{{ AutoComponentMacros.GetNetPropertiesSetName(Re
static_cast<int32_t>({{ AutoComponentMacros.GetNetPropertiesQualifiedPropertyDirtyEnum(Component.attrib['Name'], ReplicateFrom, ReplicateTo, Property) }}), static_cast<int32_t>({{ AutoComponentMacros.GetNetPropertiesQualifiedPropertyDirtyEnum(Component.attrib['Name'], ReplicateFrom, ReplicateTo, Property) }}),
m_{{ LowerFirst(Property.attrib['Name']) }}, m_{{ LowerFirst(Property.attrib['Name']) }},
"{{ Property.attrib['Name'] }}", "{{ Property.attrib['Name'] }}",
GetNetComponentId() GetNetComponentId(),
stats
); );
{% endif %} {% endif %}
{% endcall %} {% endcall %}
@ -1111,23 +1111,29 @@ namespace {{ Component.attrib['Namespace'] }}
{{ DefineNetworkPropertyReflection(Component, 'Authority', 'Client', ComponentBaseName)|indent(16) -}} {{ DefineNetworkPropertyReflection(Component, 'Authority', 'Client', ComponentBaseName)|indent(16) -}}
{{ DefineNetworkPropertyReflection(Component, 'Authority', 'Autonomous', ComponentBaseName)|indent(16) -}} {{ DefineNetworkPropertyReflection(Component, 'Authority', 'Autonomous', ComponentBaseName)|indent(16) -}}
{{ DefineNetworkPropertyReflection(Component, 'Autonomous', 'Authority', ComponentBaseName)|indent(16) }} {{ DefineNetworkPropertyReflection(Component, 'Autonomous', 'Authority', ComponentBaseName)|indent(16) }}
{{ DefineArchetypePropertyReflection(Component, ComponentBaseName)|indent(16) }} {{ DefineArchetypePropertyReflection(Component, ComponentBaseName)|indent(16) }};
; }
ReflectToEditContext(context);
}
void {{ ComponentBaseName }}::{{ ComponentBaseName }}::ReflectToEditContext(AZ::ReflectContext* context)
{
AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
if (serializeContext)
{
AZ::EditContext* editContext = serializeContext->GetEditContext(); AZ::EditContext* editContext = serializeContext->GetEditContext();
if (editContext) if (editContext)
{ {
editContext->Class<{{ ComponentBaseName }}>("{{ ComponentName }}", "{{ Component.attrib['Description'] }}") editContext->Class<{{ ComponentName }}>("{{ ComponentName }}", "{{ Component.attrib['Description'] }}")
->ClassElement(AZ::Edit::ClassElements::EditorData, "") ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::Category, "Multiplayer") ->Attribute(AZ::Edit::Attributes::Category, "Multiplayer")
->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC_CE("Game")) ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC_CE("Game"))
{{ DefineNetworkPropertyEditReflection(Component, 'Authority', 'Authority', ComponentBaseName)|indent(20) -}} {{ DefineNetworkPropertyEditReflection(Component, 'Authority', 'Authority', ComponentName)|indent(20) -}}
{{ DefineNetworkPropertyEditReflection(Component, 'Authority', 'Server', ComponentBaseName)|indent(20) -}} {{ DefineNetworkPropertyEditReflection(Component, 'Authority', 'Server', ComponentName)|indent(20) -}}
{{ DefineNetworkPropertyEditReflection(Component, 'Authority', 'Client', ComponentBaseName)|indent(20) -}} {{ DefineNetworkPropertyEditReflection(Component, 'Authority', 'Client', ComponentName)|indent(20) -}}
{{ DefineNetworkPropertyEditReflection(Component, 'Authority', 'Autonomous', ComponentBaseName)|indent(20) -}} {{ DefineNetworkPropertyEditReflection(Component, 'Authority', 'Autonomous', ComponentName)|indent(20) -}}
{{ DefineNetworkPropertyEditReflection(Component, 'Autonomous', 'Authority', ComponentBaseName)|indent(20) }} {{ DefineNetworkPropertyEditReflection(Component, 'Autonomous', 'Authority', ComponentName)|indent(20) }}
{{ DefineArchetypePropertyEditReflection(Component, ComponentBaseName)|indent(20) }} {{ DefineArchetypePropertyEditReflection(Component, ComponentName)|indent(20) }};
;
} }
} }
} }

@ -16,7 +16,7 @@
<Include File="Source/NetworkInput/NetworkInputVector.h"/> <Include File="Source/NetworkInput/NetworkInputVector.h"/>
<Include File="AzNetworking/DataStructures/ByteBuffer.h"/> <Include File="AzNetworking/DataStructures/ByteBuffer.h"/>
<NetworkProperty Type="Multiplayer::NetworkInputId" Name="LastInputId" Init="Multiplayer::NetworkInputId{0}" ReplicateFrom="Authority" ReplicateTo="Authority" IsRewindable="false" IsPredictable="false" IsPublic="false" Container="Object" ExposeToEditor="false" GenerateEventBindings="false" /> <NetworkProperty Type="Multiplayer::ClientInputId" Name="LastInputId" Init="Multiplayer::ClientInputId{0}" ReplicateFrom="Authority" ReplicateTo="Authority" IsRewindable="false" IsPredictable="false" IsPublic="false" Container="Object" ExposeToEditor="false" GenerateEventBindings="false" />
<RemoteProcedure Name="SendClientInput" InvokeFrom="Autonomous" HandleOn="Authority" IsPublic="true" IsReliable="false" Description="Client to server move / input RPC"> <RemoteProcedure Name="SendClientInput" InvokeFrom="Autonomous" HandleOn="Authority" IsPublic="true" IsReliable="false" Description="Client to server move / input RPC">
<Param Type="Multiplayer::NetworkInputVector" Name="inputArray" /> <Param Type="Multiplayer::NetworkInputVector" Name="inputArray" />
@ -25,7 +25,7 @@
</RemoteProcedure> </RemoteProcedure>
<RemoteProcedure Name="SendClientInputCorrection" InvokeFrom="Authority" HandleOn="Autonomous" IsPublic="true" IsReliable="false" Description="Autonomous proxy correction RPC"> <RemoteProcedure Name="SendClientInputCorrection" InvokeFrom="Authority" HandleOn="Autonomous" IsPublic="true" IsReliable="false" Description="Autonomous proxy correction RPC">
<Param Type="Multiplayer::NetworkInputId" Name="inputId" /> <Param Type="Multiplayer::ClientInputId" Name="inputId" />
<Param Type="AzNetworking::PacketEncodingBuffer" Name="correction" /> <Param Type="AzNetworking::PacketEncodingBuffer" Name="correction" />
</RemoteProcedure> </RemoteProcedure>

@ -24,7 +24,6 @@ namespace Multiplayer
serializeContext->Class<LocalPredictionPlayerInputComponent, LocalPredictionPlayerInputComponentBase>() serializeContext->Class<LocalPredictionPlayerInputComponent, LocalPredictionPlayerInputComponentBase>()
->Version(1); ->Version(1);
} }
LocalPredictionPlayerInputComponentBase::Reflect(context); LocalPredictionPlayerInputComponentBase::Reflect(context);
} }
@ -48,7 +47,7 @@ namespace Multiplayer
void LocalPredictionPlayerInputComponentController::HandleSendClientInputCorrection void LocalPredictionPlayerInputComponentController::HandleSendClientInputCorrection
( (
[[maybe_unused]] const Multiplayer::NetworkInputId& inputId, [[maybe_unused]] const Multiplayer::ClientInputId& inputId,
[[maybe_unused]] const AzNetworking::PacketEncodingBuffer& correction [[maybe_unused]] const AzNetworking::PacketEncodingBuffer& correction
) )
{ {

@ -42,6 +42,6 @@ namespace Multiplayer
void HandleSendClientInput(const Multiplayer::NetworkInputVector& inputArray, const uint32_t& stateHash, const AzNetworking::PacketEncodingBuffer& clientState) override; void HandleSendClientInput(const Multiplayer::NetworkInputVector& inputArray, const uint32_t& stateHash, const AzNetworking::PacketEncodingBuffer& clientState) override;
void HandleSendMigrateClientInput(const Multiplayer::MigrateNetworkInputVector& inputArray) override; void HandleSendMigrateClientInput(const Multiplayer::MigrateNetworkInputVector& inputArray) override;
void HandleSendClientInputCorrection(const Multiplayer::NetworkInputId& inputId, const AzNetworking::PacketEncodingBuffer& correction) override; void HandleSendClientInputCorrection(const Multiplayer::ClientInputId& inputId, const AzNetworking::PacketEncodingBuffer& correction) override;
}; };
} }

@ -43,8 +43,12 @@ namespace Multiplayer
NetEntityId MultiplayerComponent::GetNetEntityId() const NetEntityId MultiplayerComponent::GetNetEntityId() const
{ {
const NetBindComponent* netBindComponent = GetNetBindComponent(); return m_netBindComponent ? m_netBindComponent->GetNetEntityId() : InvalidNetEntityId;
return netBindComponent ? netBindComponent->GetNetEntityId() : InvalidNetEntityId; }
NetEntityRole MultiplayerComponent::GetNetEntityRole() const
{
return m_netBindComponent ? m_netBindComponent->GetNetEntityRole() : NetEntityRole::InvalidRole;
} }
ConstNetworkEntityHandle MultiplayerComponent::GetEntityHandle() const ConstNetworkEntityHandle MultiplayerComponent::GetEntityHandle() const

@ -62,6 +62,7 @@ namespace Multiplayer
//! @} //! @}
NetEntityId GetNetEntityId() const; NetEntityId GetNetEntityId() const;
NetEntityRole GetNetEntityRole() const;
ConstNetworkEntityHandle GetEntityHandle() const; ConstNetworkEntityHandle GetEntityHandle() const;
NetworkEntityHandle GetEntityHandle(); NetworkEntityHandle GetEntityHandle();
void MarkDirty(); void MarkDirty();
@ -109,7 +110,8 @@ namespace Multiplayer
int32_t bitIndex, int32_t bitIndex,
TYPE& value, TYPE& value,
const char* name, const char* name,
[[maybe_unused]] NetComponentId componentId [[maybe_unused]] NetComponentId componentId,
MultiplayerStats& stats
) )
{ {
if (bitset.GetBit(bitIndex)) if (bitset.GetBit(bitIndex))
@ -119,6 +121,7 @@ namespace Multiplayer
serializer.Serialize(value, name); serializer.Serialize(value, name);
if (modifyRecord && !serializer.GetTrackedChangesFlag()) if (modifyRecord && !serializer.GetTrackedChangesFlag())
{ {
// If the serializer didn't change any values, then lower the flag so we don't unnecessarily notify
bitset.SetBit(bitIndex, false); bitset.SetBit(bitIndex, false);
} }
const uint32_t postUpdateSize = serializer.GetSize(); const uint32_t postUpdateSize = serializer.GetSize();
@ -126,8 +129,7 @@ namespace Multiplayer
const uint32_t updateSize = (postUpdateSize - prevUpdateSize); const uint32_t updateSize = (postUpdateSize - prevUpdateSize);
if (updateSize > 0) if (updateSize > 0)
{ {
MultiplayerStats& stats = AZ::Interface<IMultiplayer>::Get()->GetStats(); if (modifyRecord)
if (serializer.GetSerializerMode() == AzNetworking::SerializerMode::WriteToObject)
{ {
stats.m_propertyUpdatesRecv++; stats.m_propertyUpdatesRecv++;
stats.m_propertyUpdatesRecvBytes += updateSize; stats.m_propertyUpdatesRecvBytes += updateSize;

@ -27,6 +27,11 @@ namespace Multiplayer
return m_owner.GetNetEntityId(); return m_owner.GetNetEntityId();
} }
NetEntityRole MultiplayerController::GetNetEntityRole() const
{
return GetNetBindComponent()->GetNetEntityRole();
}
AZ::Entity* MultiplayerController::GetEntity() const AZ::Entity* MultiplayerController::GetEntity() const
{ {
return m_owner.GetEntity(); return m_owner.GetEntity();

@ -47,6 +47,10 @@ namespace Multiplayer
//! @return the networkId for the entity that owns this controller //! @return the networkId for the entity that owns this controller
NetEntityId GetNetEntityId() const; NetEntityId GetNetEntityId() const;
//! Returns the networkRole for the entity that owns this controller.
//! @return the networkRole for the entity that owns this controller
NetEntityRole GetNetEntityRole() const;
//! Returns the raw AZ::Entity pointer for the entity that owns this controller. //! Returns the raw AZ::Entity pointer for the entity that owns this controller.
//! @return the raw AZ::Entity pointer for the entity that owns this controller //! @return the raw AZ::Entity pointer for the entity that owns this controller
AZ::Entity* GetEntity() const; AZ::Entity* GetEntity() const;

@ -13,6 +13,8 @@
#include <Source/Components/NetworkTransformComponent.h> #include <Source/Components/NetworkTransformComponent.h>
#include <AzCore/Serialization/SerializeContext.h> #include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/EditContext.h> #include <AzCore/Serialization/EditContext.h>
#include <AzCore/EBus/IEventScheduler.h>
#include <AzFramework/Components/TransformComponent.h>
namespace Multiplayer namespace Multiplayer
{ {
@ -24,7 +26,81 @@ namespace Multiplayer
serializeContext->Class<NetworkTransformComponent, NetworkTransformComponentBase>() serializeContext->Class<NetworkTransformComponent, NetworkTransformComponentBase>()
->Version(1); ->Version(1);
} }
NetworkTransformComponentBase::Reflect(context); NetworkTransformComponentBase::Reflect(context);
} }
NetworkTransformComponent::NetworkTransformComponent()
: m_rotationEventHandler([this](const AZ::Quaternion& rotation) { OnRotationChangedEvent(rotation); })
, m_translationEventHandler([this](const AZ::Vector3& translation) { OnTranslationChangedEvent(translation); })
, m_scaleEventHandler([this](const AZ::Vector3& scale) { OnScaleChangedEvent(scale); })
{
;
}
void NetworkTransformComponent::OnInit()
{
;
}
void NetworkTransformComponent::OnActivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating)
{
RotationAddEvent(m_rotationEventHandler);
TranslationAddEvent(m_translationEventHandler);
ScaleAddEvent(m_scaleEventHandler);
}
void NetworkTransformComponent::OnDeactivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating)
{
;
}
void NetworkTransformComponent::OnRotationChangedEvent(const AZ::Quaternion& rotation)
{
AZ::Transform worldTm = GetTransformComponent()->GetWorldTM();
worldTm.SetRotation(rotation);
GetTransformComponent()->SetWorldTM(worldTm);
}
void NetworkTransformComponent::OnTranslationChangedEvent(const AZ::Vector3& translation)
{
AZ::Transform worldTm = GetTransformComponent()->GetWorldTM();
worldTm.SetTranslation(translation);
GetTransformComponent()->SetWorldTM(worldTm);
}
void NetworkTransformComponent::OnScaleChangedEvent(const AZ::Vector3& scale)
{
AZ::Transform worldTm = GetTransformComponent()->GetWorldTM();
worldTm.SetScale(scale);
GetTransformComponent()->SetWorldTM(worldTm);
}
NetworkTransformComponentController::NetworkTransformComponentController(NetworkTransformComponent& parent)
: NetworkTransformComponentControllerBase(parent)
, m_transformChangedHandler([this](const AZ::Transform&, const AZ::Transform& worldTm) { OnTransformChangedEvent(worldTm); })
{
;
}
void NetworkTransformComponentController::OnActivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating)
{
GetParent().GetTransformComponent()->BindTransformChangedEventHandler(m_transformChangedHandler);
OnTransformChangedEvent(GetParent().GetTransformComponent()->GetWorldTM());
}
void NetworkTransformComponentController::OnDeactivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating)
{
;
}
void NetworkTransformComponentController::OnTransformChangedEvent(const AZ::Transform& worldTm)
{
if (GetNetEntityRole() == NetEntityRole::Authority)
{
SetRotation(worldTm.GetRotation());
SetTranslation(worldTm.GetTranslation());
SetScale(worldTm.GetScale());
}
}
} }

@ -13,6 +13,7 @@
#pragma once #pragma once
#include <Source/AutoGen/NetworkTransformComponent.AutoComponent.h> #include <Source/AutoGen/NetworkTransformComponent.AutoComponent.h>
#include <AzCore/Component/TransformBus.h>
namespace Multiplayer namespace Multiplayer
{ {
@ -22,20 +23,36 @@ namespace Multiplayer
public: public:
AZ_MULTIPLAYER_COMPONENT(Multiplayer::NetworkTransformComponent, s_networkTransformComponentConcreteUuid, Multiplayer::NetworkTransformComponentBase); AZ_MULTIPLAYER_COMPONENT(Multiplayer::NetworkTransformComponent, s_networkTransformComponentConcreteUuid, Multiplayer::NetworkTransformComponentBase);
static void Reflect([[maybe_unused]] AZ::ReflectContext* context); static void Reflect(AZ::ReflectContext* context);
void OnInit() override {} NetworkTransformComponent();
void OnActivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) override {}
void OnDeactivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) override {} void OnInit() override;
void OnActivate(Multiplayer::EntityIsMigrating entityIsMigrating) override;
void OnDeactivate(Multiplayer::EntityIsMigrating entityIsMigrating) override;
private:
void OnRotationChangedEvent(const AZ::Quaternion& rotation);
void OnTranslationChangedEvent(const AZ::Vector3& translation);
void OnScaleChangedEvent(const AZ::Vector3& scale);
AZ::Event<AZ::Quaternion>::Handler m_rotationEventHandler;
AZ::Event<AZ::Vector3>::Handler m_translationEventHandler;
AZ::Event<AZ::Vector3>::Handler m_scaleEventHandler;
}; };
class NetworkTransformComponentController class NetworkTransformComponentController
: public NetworkTransformComponentControllerBase : public NetworkTransformComponentControllerBase
{ {
public: public:
NetworkTransformComponentController(NetworkTransformComponent& parent) : NetworkTransformComponentControllerBase(parent) {} NetworkTransformComponentController(NetworkTransformComponent& parent);
void OnActivate(Multiplayer::EntityIsMigrating entityIsMigrating) override;
void OnDeactivate(Multiplayer::EntityIsMigrating entityIsMigrating) override;
private:
void OnTransformChangedEvent(const AZ::Transform& worldTm);
void OnActivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) override {} AZ::TransformChangedEvent::Handler m_transformChangedHandler;
void OnDeactivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) override {}
}; };
} }

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

Loading…
Cancel
Save