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
TestResults/**
*.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.
#
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(${json_error})
message(FATAL_ERROR "Unable to read key 'project_name' from 'project.json'")
endif()
if(NOT PROJECT_NAME)
cmake_minimum_required(VERSION 3.19)
project(AutomatedTesting
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})
add_subdirectory(Gem)
string(JSON project_target_name ERROR_VARIABLE json_error GET ${project_json} "project_name")
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::GradientSignal
Gem::Vegetation
Gem::Atom_RHI.Private
Gem::Atom_RPI.Private
Gem::Atom_Feature_Common
@ -54,4 +53,5 @@ set(GEM_DEPENDENCIES
Gem::ImguiAtom
Gem::Atom_AtomBridge
Gem::AtomFont
Gem::Blast
)

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

@ -27,28 +27,28 @@ from base import TestAutomationBase
class TestAutomation(TestAutomationBase):
def test_ActorSplitsAfterCollision(self, request, workspace, editor, launcher_platform):
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):
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):
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):
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):
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):
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):
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()
## Blast ##
# Disabled until AutomatedTesting runs with Atom.
# if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS)
# ly_add_pytest(
# NAME AutomatedTesting::BlastTests
# TEST_SERIAL TRUE
# PATH ${CMAKE_CURRENT_LIST_DIR}/Blast/TestSuite_Active.py
# TIMEOUT 500
# RUNTIME_DEPENDENCIES
# Legacy::Editor
# Legacy::CryRenderNULL
# AZ::AssetProcessor
# AutomatedTesting.Assets
# )
# endif()
if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS)
ly_add_pytest(
NAME AutomatedTesting::BlastTests
TEST_SERIAL TRUE
PATH ${CMAKE_CURRENT_LIST_DIR}/Blast/TestSuite_Active.py
TIMEOUT 3600
RUNTIME_DEPENDENCIES
Legacy::Editor
AZ::AssetProcessor
AutomatedTesting.Assets
)
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">
<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>
</ObjectStream>

@ -922,7 +922,7 @@ void CVars::Init()
"Will not render CGFs past the given amount of drawcalls\n"
"(<=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");
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");

@ -35,7 +35,7 @@ namespace AZ
}
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)
{
@ -47,7 +47,7 @@ namespace AZ
Trace::Output(nullptr, msgBoxMessage.c_str());
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 AZ

@ -605,7 +605,7 @@ void CSystem::DebugStats([[maybe_unused]] bool checkpoint, [[maybe_unused]] bool
{
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++;
continue;
}
@ -642,7 +642,7 @@ void CSystem::DebugStats([[maybe_unused]] bool checkpoint, [[maybe_unused]] bool
}
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++;
};
#endif
@ -1066,7 +1066,7 @@ void CSystem::FatalError(const char* format, ...)
if (szSysErrorMessage)
{
CryLogAlways("<CrySystem> Last System Error: %s", szSysErrorMessage);
CryLogAlways("Last System Error: %s", szSysErrorMessage);
}
if (GetUserCallback())

@ -1117,7 +1117,7 @@ namespace AZ
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)
{

@ -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);
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.

@ -19,9 +19,6 @@ namespace AZ
{
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&>;
//! Requests for working with non-uniform scale.

@ -26,7 +26,7 @@ namespace AZ
{
class Transform;
using TransformChangedEvent = Event<Transform, Transform>;
using TransformChangedEvent = Event<const Transform&, const Transform&>;
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
# define AZ_TRACE_METHOD_NAME_CATEGORY(name, category) AZ::Debug::EventTrace::ScopedSlice AZ_JOIN(ScopedSlice__, __LINE__)(name, category);
# define AZ_TRACE_METHOD_NAME(name) \
AZ_TRACE_METHOD_NAME_CATEGORY(name, "") \
AZ_PROFILE_SCOPE(AZ::Debug::ProfileCategory::AzTrace, name)
@ -53,6 +52,7 @@ namespace AZ
AZ_TRACE_METHOD_NAME_CATEGORY(AZ_FUNCTION_SIGNATURE, "") \
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzTrace)
#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() AZ_TRACE_METHOD_NAME(AZ_FUNCTION_SIGNATURE)
#endif

@ -38,6 +38,13 @@ namespace AZ
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.
//! By design, cannot represent skew transformations.
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/TestTypes.h
UnitTest/Mocks/MockFileIOBase.h
UnitTest/Mocks/MockSettingsRegistry.h
)

@ -12,6 +12,7 @@
#include <AzFramework/Components/NonUniformScaleComponent.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Math/Transform.h>
#include <AzCore/Math/ToString.h>
#include <AzCore/Component/Entity.h>
@ -81,13 +82,13 @@ namespace AzFramework
void NonUniformScaleComponent::SetScale(const AZ::Vector3& scale)
{
if (scale.GetMinElement() >= AZ::MinNonUniformScale)
if (scale.GetMinElement() >= AZ::MinTransformScale && scale.GetMaxElement() <= AZ::MaxTransformScale)
{
m_scale = scale;
}
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::ToString(scale).c_str(), AZ::ToString(clampedScale).c_str(), GetEntity()->GetName().c_str());
m_scale = clampedScale;

@ -114,6 +114,9 @@ namespace AzNetworking
void ClearUnusedBits();
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>
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;
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()[m_bitset.GetContainer().size() - 1] &= ClearBitMask;
m_bitset.ClearUnusedBits();
}
}

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

@ -256,6 +256,8 @@ namespace AzToolsFramework
m_userSettings = AZ::UserSettings::CreateFind<AssetEditorWidgetUserSettings>(k_assetEditorWidgetSettings, AZ::UserSettings::CT_LOCAL);
UpdateRecentFileListState();
QObject::connect(m_recentFileMenu, &QMenu::aboutToShow, this, &AssetEditorWidget::PopulateRecentMenu);
}
@ -952,7 +954,8 @@ namespace AzToolsFramework
void AssetEditorWidget::AddRecentPath(const AZStd::string& recentPath)
{
m_userSettings->AddRecentPath(recentPath);
m_userSettings->AddRecentPath(recentPath);
UpdateRecentFileListState();
}
void AssetEditorWidget::PopulateRecentMenu()
@ -989,6 +992,21 @@ namespace AzToolsFramework
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 AzToolsFramework

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

@ -58,7 +58,7 @@ namespace AzToolsFramework
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
EntityList inputEntityList;

@ -42,7 +42,7 @@ namespace AzToolsFramework
void UnregisterPrefabPublicHandlerInterface();
// 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 SavePrefab(AZ::IO::Path filePath) 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.
* @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.

@ -13,6 +13,7 @@
#include <AzToolsFramework/ToolsComponents/EditorNonUniformScaleComponent.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzFramework/Components/NonUniformScaleComponent.h>
#include <AzCore/Math/Transform.h>
#include <AzCore/Math/ToString.h>
namespace AzToolsFramework
@ -44,7 +45,9 @@ namespace AzToolsFramework
->DataElement(
AZ::Edit::UIHandlers::Default, &EditorNonUniformScaleComponent::m_scale, "Non-uniform Scale",
"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)
;
}
@ -106,13 +109,13 @@ namespace AzToolsFramework
void EditorNonUniformScaleComponent::SetScale(const AZ::Vector3& scale)
{
if (scale.GetMinElement() >= AZ::MinNonUniformScale)
if (scale.GetMinElement() >= AZ::MinTransformScale && scale.GetMaxElement() <= AZ::MaxTransformScale)
{
m_scale = scale;
}
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::ToString(scale).c_str(), AZ::ToString(clampedScale).c_str(), GetEntity()->GetName().c_str());
m_scale = clampedScale;

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

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

@ -24,6 +24,7 @@
#include <AzToolsFramework/AssetBrowser/AssetBrowserBus.h>
#include <AzToolsFramework/AssetBrowser/AssetSelectionModel.h>
#include <AzToolsFramework/AssetBrowser/Entries/SourceAssetBrowserEntry.h>
#include <AzToolsFramework/Prefab/PrefabLoaderInterface.h>
#include <AzToolsFramework/ToolsComponents/EditorLayerComponentBus.h>
#include <AzToolsFramework/UI/EditorEntityUi/EditorEntityUiInterface.h>
#include <AzToolsFramework/UI/Prefab/PrefabIntegrationInterface.h>
@ -39,9 +40,12 @@ namespace AzToolsFramework
{
namespace Prefab
{
EditorEntityUiInterface* PrefabIntegrationManager::s_editorEntityUiInterface = nullptr;
PrefabPublicInterface* PrefabIntegrationManager::s_prefabPublicInterface = nullptr;
PrefabEditInterface* PrefabIntegrationManager::s_prefabEditInterface = nullptr;
PrefabLoaderInterface* PrefabIntegrationManager::s_prefabLoaderInterface = nullptr;
const AZStd::string PrefabIntegrationManager::s_prefabFileExtension = ".prefab";
void PrefabUserSettings::Reflect(AZ::ReflectContext* context)
@ -79,6 +83,13 @@ namespace AzToolsFramework
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();
PrefabInstanceContainerNotificationBus::Handler::BusConnect();
AZ::Interface<PrefabIntegrationInterface>::Register(this);
@ -320,14 +331,15 @@ namespace AzToolsFramework
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.
return;
}
}
auto createPrefabOutcome = s_prefabPublicInterface->CreatePrefab(selectedEntities, prefabFilePath);
auto createPrefabOutcome = s_prefabPublicInterface->CreatePrefab(selectedEntities, s_prefabLoaderInterface->GetRelativePathToProject(prefabFilePath.data()));
if (!createPrefabOutcome.IsSuccess())
{

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

@ -65,7 +65,7 @@ namespace AzToolsFramework
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 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);
if (action.m_modifiers.Alt())

@ -313,7 +313,7 @@ namespace O3DELauncher
return "Failed to initialize the CrySystem Interface";
case ReturnCode::ErrCryEnvironment:
return "Failed to initialize the CryEngine global environment";
return "Failed to initialize the global environment";
case ReturnCode::ErrAssetProccessor:
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
@ -55,7 +55,7 @@ namespace
return static_cast<int>(ReturnCode::ErrUnitTestNotSupported);
#else
using namespace LumberyardLauncher;
using namespace O3DELauncher;
PlatformMainInfo mainInfo;
mainInfo.m_updateResourceLimits = IncreaseResourceLimits;
@ -79,19 +79,19 @@ namespace
#endif // AZ_TESTS_ENABLED
}
- (void)launchLumberyardApplication
- (void)launchO3DEApplication
{
const int exitCode = [self runLumberyardApplication];
const int exitCode = [self runO3DEApplication];
exit(exitCode);
}
- (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
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;
}
@ -132,4 +132,4 @@ namespace
&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>
@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
{
@ -65,4 +65,4 @@
[self touchesEnded: touches withEvent: event];
}
@end // LumberyardApplication_iOS Implementation
@end // O3DEApplication_iOS Implementation

@ -13,8 +13,8 @@ set(FILES
Launcher_iOS.mm
Launcher_Traits_iOS.h
Launcher_Traits_Platform.h
LumberyardApplication_iOS.mm
LumberyardApplicationDelegate_iOS.mm
O3DEApplication_iOS.mm
O3DEApplicationDelegate_iOS.mm
../Common/Apple/Launcher_Apple.mm
../Common/Apple/Launcher_Apple.h
../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}
# Otherwise the the absolute project_path is returned with symlinks resolved
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
################################################################################

@ -5702,7 +5702,7 @@ extern "C" int AZ_DLL_EXPORT CryEditMain(int argc, char* argv[])
int exitCode = 0;
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."
"\nSee other errors for more details.");

@ -1931,7 +1931,7 @@ void CCryEditDoc::Fetch(const QString& holdName, const QString& relativeHoldPath
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);
AZ_Error("CryEditDoc", false, "Fetch failed to load the Xml Archive");
AZ_Error("EditDoc", false, "Fetch failed to load the Xml Archive");
return;
}

@ -96,6 +96,11 @@ bool LegacyViewportCameraControllerInstance::HandleMouseMove(
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)
{
Matrix34 m = AZTransformToLYTransform(viewportContext->GetCameraTransform());
@ -342,13 +347,16 @@ bool LegacyViewportCameraControllerInstance::HandleInputChannelEvent(const AzFra
m_inRotateMode = true;
}
shouldConsumeEvent = 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)
{
m_inZoomMode = 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;
}
}

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

@ -37,7 +37,7 @@
#include <AzFramework/API/AtomActiveInterface.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()
{

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

@ -42,6 +42,7 @@
#include <SceneAPI/SceneCore/DataTypes/Rules/IMeshAdvancedRule.h>
#include <SceneAPI/SceneCore/DataTypes/Rules/ILodRule.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/IBlendShapeData.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::ILodRule, 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
context->Class<AZ::SceneAPI::DataTypes::IAnimationData, AZ::SceneAPI::DataTypes::IGraphObject>()->Version(1);
context->Class<AZ::SceneAPI::DataTypes::IBlendShapeData, AZ::SceneAPI::DataTypes::IGraphObject>()->Version(1);

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

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

@ -13,19 +13,23 @@
#include <AzTest/AzTest.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/Rules/CoordinateSystemRule.h>
#include <SceneAPI/SceneData/Behaviors/ScriptProcessorRuleBehavior.h>
#include <AzCore/Math/Quaternion.h>
#include <AzCore/Name/NameDictionary.h>
#include <AzCore/RTTI/BehaviorContext.h>
#include <AzCore/RTTI/ReflectionManager.h>
#include <AzCore/Serialization/Json/RegistrationContext.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/shared_ptr.h>
#include <AzCore/UnitTest/Mocks/MockSettingsRegistry.h>
#include <AzCore/UnitTest/TestTypes.h>
#include <AzFramework/FileFunc/FileFunc.h>
#include <AzCore/Math/Quaternion.h>
namespace AZ
{
@ -94,6 +98,19 @@ namespace AZ
m_jsonSystemComponent = AZStd::make_unique<JsonSystemComponent>();
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
@ -106,9 +123,19 @@ namespace AZ
m_jsonRegistrationContext.reset();
m_jsonSystemComponent.reset();
AZ::SettingsRegistry::Unregister(&m_data->m_settings);
m_data.reset();
AZ::NameDictionary::Destroy();
UnitTest::AllocatorsFixture::TearDown();
}
struct DataMembers
{
AZ::NiceSettingsRegistrySimpleMock m_settings;
};
AZStd::unique_ptr<DataMembers> m_data;
};
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"("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
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
typedef AZStd::recursive_mutex MutexType;
//////////////////////////////////////////////////////////////////////////
// Loads an image from a source file path

@ -102,7 +102,8 @@ namespace AZ
// Register Shader Resource Group Layout Builder
AssetBuilderSDK::AssetBuilderDesc srgLayoutBuilderDescriptor;
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("*.azsli", 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
AssetBuilderSDK::AssetBuilderDesc shaderAssetBuilderDescriptor;
shaderAssetBuilderDescriptor.m_name = "Shader Asset Builder";
shaderAssetBuilderDescriptor.m_version = 96; // SPEC-6065
shaderAssetBuilderDescriptor.m_version = 97; // ATOM-15196
// .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_busId = azrtti_typeid<ShaderAssetBuilder>();
@ -132,7 +133,7 @@ namespace AZ
shaderVariantAssetBuilderDescriptor.m_name = "Shader Variant Asset Builder";
// 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".
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_busId = azrtti_typeid<ShaderVariantAssetBuilder>();
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.
}
auto vertexBuffer = RPI::DynamicDrawInterface::Get()->GetDynamicBuffer(totalVtxBufferSize);
auto indexBuffer = RPI::DynamicDrawInterface::Get()->GetDynamicBuffer(totalIdxBufferSize);
auto vertexBuffer = RPI::DynamicDrawInterface::Get()->GetDynamicBuffer(totalVtxBufferSize, RHI::Alignment::InputAssembly);
auto indexBuffer = RPI::DynamicDrawInterface::Get()->GetDynamicBuffer(totalIdxBufferSize, RHI::Alignment::InputAssembly);
if (!vertexBuffer || !indexBuffer)
{

@ -186,7 +186,7 @@ namespace AZ
{
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)
{
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
Data::Asset<RPI::ShaderResourceGroupAsset> rayTracingSceneSrgAsset =
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);
}

@ -30,38 +30,42 @@ namespace AZ
{
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),
/// 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.
Constant = AZ_BIT(1),
Constant = AZ_BIT(2),
/// Supports read access through a ShaderResourceGroup.
ShaderRead = AZ_BIT(2),
ShaderRead = AZ_BIT(3),
/// Supports write access through ShaderResourceGroup.
ShaderWrite = AZ_BIT(3),
ShaderWrite = AZ_BIT(4),
/// Supports read-write access through a ShaderResourceGroup.
ShaderReadWrite = ShaderRead | ShaderWrite,
/// Supports read access for GPU copy operations.
CopyRead = AZ_BIT(4),
CopyRead = AZ_BIT(5),
/// Supports write access for GPU copy operations.
CopyWrite = AZ_BIT(5),
CopyWrite = AZ_BIT(6),
/// Supports predication access for conditional rendering.
Predication = AZ_BIT(6),
Predication = AZ_BIT(7),
/// Supports indirect buffer access for indirect draw/dispatch.
Indirect = AZ_BIT(7),
Indirect = AZ_BIT(8),
/// Supports ray tracing acceleration structure usage.
RayTracingAccelerationStructure = AZ_BIT(8),
RayTracingAccelerationStructure = AZ_BIT(9),
/// Supports ray tracing shader table usage.
RayTracingShaderTable = AZ_BIT(9)
RayTracingShaderTable = AZ_BIT(10)
};
AZ_DEFINE_ENUM_BITWISE_OPERATORS(AZ::RHI::BufferBindFlags);

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

@ -39,7 +39,7 @@ namespace AZ
{
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;
}

@ -670,7 +670,13 @@ namespace AZ
}
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;
@ -827,10 +833,13 @@ namespace AZ
AZStd::string& argBufferStr) const
{
size_t prevEndOfLine = argBufferStr.rfind("\n", resourceStartPos);
size_t nextEndOfLine = argBufferStr.find("\n", resourceStartPos);
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;
}
else

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

@ -210,6 +210,12 @@ namespace AZ
{
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();
}

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

@ -685,7 +685,7 @@ namespace AZ
using BindFlags = RHI::BufferBindFlags;
VkBufferUsageFlags usageFlags{ 0 };
if (RHI::CheckBitsAny(bindFlags, BindFlags::InputAssembly))
if (RHI::CheckBitsAny(bindFlags, BindFlags::InputAssembly | BindFlags::DynamicInputAssembly))
{
usageFlags |=
VK_BUFFER_USAGE_INDEX_BUFFER_BIT |
@ -932,7 +932,7 @@ namespace AZ
VkPipelineStageFlags GetResourcePipelineStateFlags(const RHI::BufferBindFlags& bindFlags)
{
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;
}
@ -1042,7 +1042,7 @@ namespace AZ
VkAccessFlags GetResourceAccessFlags(const RHI::BufferBindFlags& bindFlags)
{
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;
}

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

@ -36,7 +36,7 @@ namespace AZ
//! buffer->Write(data, size);
//! // 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
: public AZStd::intrusive_base
{

@ -11,6 +11,7 @@
*/
#pragma once
#include <Atom/RHI.Reflect/AttachmentEnums.h>
#include <Atom/RHI.Reflect/QueryPoolDescriptor.h>
#include <AtomCore/std/containers/array_view.h>
@ -43,15 +44,19 @@ namespace AZ
{
public:
TimestampResult() = default;
TimestampResult(uint64_t timestampInTicks);
TimestampResult(uint64_t timestampQueryResultLow, uint64_t timestampQueryResultHigh);
TimestampResult(AZStd::array_view<TimestampResult>&& timestampResultArray);
TimestampResult(uint64_t beginTick, uint64_t endTick, RHI::HardwareQueueClass hardwareQueueClass);
uint64_t GetTimestampInNanoseconds() const;
uint64_t GetTimestampInTicks() const;
uint64_t GetDurationInNanoseconds() const;
uint64_t GetDurationInTicks() const;
uint64_t GetTimestampBeginInTicks() const;
void Add(const TimestampResult& extent);
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

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

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

@ -74,8 +74,9 @@ namespace AZ
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.");
return nullptr;
}
@ -203,11 +204,11 @@ namespace AZ
void Buffer::InitBufferView()
{
// 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;
}
m_bufferView = m_rhiBuffer->GetBufferView(m_bufferViewDescriptor);
if(!m_bufferView.get())

@ -100,12 +100,12 @@ namespace AZ
bufferPoolDesc.m_hostMemoryAccess = RHI::HostMemoryAccess::Write;
break;
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_hostMemoryAccess = RHI::HostMemoryAccess::Write;
break;
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_hostMemoryAccess = RHI::HostMemoryAccess::Write;
break;

@ -63,6 +63,7 @@ namespace AZ
// [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)
{
size = RHI::AlignUp(size, alignment);
uint32_t allocatePosition = 0;
//m_ringBufferStartAddress can be null for Null back end

@ -21,41 +21,39 @@ namespace AZ
namespace RPI
{
// --- TimestampResult ---
TimestampResult::TimestampResult(uint64_t timestampInTicks)
TimestampResult::TimestampResult(uint64_t beginTick, uint64_t endTick, RHI::HardwareQueueClass hardwareQueueClass)
{
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 uint64_t high = AZStd::max(timestampQueryResultLow, timestampQueryResultHigh);
const RHI::Ptr<RHI::Device> device = RHI::GetRHIDevice();
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
for (const TimestampResult& timestampResult : timestampResultArray)
{
m_timestampInTicks += timestampResult.m_timestampInTicks;
}
return m_duration;
}
uint64_t TimestampResult::GetTimestampInNanoseconds() const
uint64_t TimestampResult::GetTimestampBeginInTicks() const
{
const RHI::Ptr<RHI::Device> device = RHI::GetRHIDevice();
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());
return m_begin;
}
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 ---

@ -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
{
AZStd::vector<PipelineStatisticsResult> pipelineStatisticsResultArray;
@ -414,7 +401,7 @@ namespace AZ
// Calculate the PipelineStatistics result by summing all of its child's PipelineStatistics
for (const Ptr<Pass>& childPass : m_children)
{
pipelineStatisticsResultArray.emplace_back(childPass->GetPipelineStatisticsResult());
pipelineStatisticsResultArray.emplace_back(childPass->GetLatestPipelineStatisticsResult());
}
return PipelineStatisticsResult(pipelineStatisticsResultArray);
}

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

@ -174,7 +174,7 @@ namespace AZ
}
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);
// 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;
uint64_t timestampResult[TimestampResultQueryCount] = {0};
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)

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

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

@ -93,7 +93,9 @@ namespace AZ
ImGuiPipelineStatisticsView();
//! 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).
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.
ImGuiTextFilter m_passFilter;
// Pause and showing the pipeline statistics result when it's paused.
bool m_paused = false;
};
class ImGuiTimestampView
@ -180,9 +185,19 @@ namespace AZ
Count
};
// Timestamp refresh type .
enum class RefreshType : int32_t
{
Realtime = 0,
OncePerSecond,
Count
};
public:
//! 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:
// Draw option for the hierarchical view of the passes.
@ -223,6 +238,20 @@ namespace AZ
// ImGui filter used to filter passes.
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

@ -105,9 +105,9 @@ namespace AZ
// [GFX TODO][ATOM-4001] Cache the timestamp and PipelineStatistics results.
// 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,
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
if (!draw)
@ -188,12 +190,6 @@ namespace AZ
continue;
}
// Filter out disabled passes for the PipelineStatistics window if necessary.
if (!m_showDisabledPasses && !passEntry.IsPipelineStatisticsEnabled())
{
continue;
}
// Filter out parent passes if necessary.
if (!m_showParentPasses && passEntry.m_isParent)
{
@ -230,6 +226,13 @@ namespace AZ
// Start drawing the PipelineStatistics window.
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");
// Draw the statistics of the RootPass.
@ -426,23 +429,16 @@ namespace AZ
}
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]),
static_cast<uint32_t>(normalized * 100.0f));
}
else
{
label = AZStd::string::format("%llu",
static_cast<AZ::u64>(passEntry->m_pipelineStatistics[attributeIdx]));
}
label = AZStd::string::format("%llu (%u%%)",
static_cast<AZ::u64>(passEntry->m_pipelineStatistics[attributeIdx]),
static_cast<uint32_t>(normalized * 100.0f));
}
else
{
label = "-";
label = AZStd::string::format("%llu",
static_cast<AZ::u64>(passEntry->m_pipelineStatistics[attributeIdx]));
}
if (rootEntry)
@ -523,7 +519,9 @@ namespace AZ
// --- 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
if (!draw)
@ -534,10 +532,28 @@ namespace AZ
// Clear the references from the previous frame.
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.
for (auto& passEntryIt : timestampEntryDatabase)
{
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 (passEntry->m_parent && !passEntry->m_linked)
@ -545,19 +561,94 @@ namespace AZ
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);
}
}
// 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);
ImGui::SetNextWindowSize(windowSize, ImGuiCond_Always);
if (ImGui::Begin("Timestamp View", &draw, ImGuiWindowFlags_NoResize))
{
// Draw the header.
{
// Pause/unpause the profiling
if (ImGui::Button(m_paused? "Resume":"Pause"))
{
m_paused = !m_paused;
}
// 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());
ImGui::Text(headerFrameTime.c_str());
@ -566,6 +657,17 @@ namespace AZ
ImGui::SameLine();
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.
const ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_None;
GpuProfilerImGuiHelper::TreeNode("Advanced options", flags, [this](bool unrolled)
@ -587,6 +689,56 @@ namespace AZ
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.
{
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)
{
ImGui::NextColumn();
ImGui::Text(entryTime.c_str());
ImGui::NextColumn();
// Only draw the workload bar when the entry is enabled.
if (entry->IsTimestampEnabled())
if (entry->m_isParent)
{
DrawFrameWorkloadBar(NormalizeFrameWorkload(entry->m_interpolatedTimestampInNanoseconds));
ImGui::NextColumn();
ImGui::NextColumn();
}
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)
@ -800,23 +950,17 @@ namespace AZ
// Draw the flat view.
for (const PassEntry* entry : m_passEntryReferences)
{
if (entry->m_isParent)
{
continue;
}
const AZStd::string entryTime = FormatTimestampLabel(entry->m_interpolatedTimestampInNanoseconds);
ImGui::Text(entry->m_name.GetCStr());
ImGui::NextColumn();
ImGui::Text(entryTime.c_str());
ImGui::NextColumn();
// 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");
}
DrawFrameWorkloadBar(NormalizeFrameWorkload(entry->m_interpolatedTimestampInNanoseconds));
ImGui::NextColumn();
}
}
@ -890,23 +1034,33 @@ namespace AZ
// Update the PassEntry database.
const PassEntry* rootPassEntryRef = CreatePassEntries(rootPass);
bool wasDraw = draw;
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::Checkbox("Enable PipelineStatisticsView", &m_drawPipelineStatisticsView);
if(ImGui::Checkbox("Enable PipelineStatisticsView", &m_drawPipelineStatisticsView))
{
rootPass->SetPipelineStatisticsQueryEnabled(m_drawPipelineStatisticsView);
}
});
// 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.
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.
// Enable/Disable the Timestamp and PipelineStatistics on the RootPass
rootPass->SetTimestampQueryEnabled(draw && m_drawTimestampView);
rootPass->SetPipelineStatisticsQueryEnabled(draw && m_drawPipelineStatisticsView);
//closing window
if (wasDraw && !draw)
{
rootPass->SetTimestampQueryEnabled(false);
rootPass->SetPipelineStatisticsQueryEnabled(false);
}
}
inline void ImGuiGpuProfiler::InterpolatePassEntries(AZStd::unordered_map<Name, PassEntry>& passEntryDatabase, float weight) const
@ -918,7 +1072,7 @@ namespace AZ
{
// Interpolate the timestamps.
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));
entry.second.m_interpolatedTimestampInNanoseconds = static_cast<uint64_t>(interpolated);
}

@ -54,8 +54,9 @@ namespace AZ
RPI::SceneDescriptor sceneDesc;
sceneDesc.m_featureProcessorNames.push_back("AZ::Render::TransformServiceFeatureProcessor");
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::SpotLightFeatureProcessor");
// 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.
// Possibly re-enable with [GFX TODO][ATOM-13639]

@ -179,7 +179,7 @@ namespace Blast
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::MaterialComponentRequestBus::EventResult(

@ -98,15 +98,16 @@ namespace Camera
AZ_Assert(m_atomCamera, "Attempted to activate Atom camera before component activation");
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
AZ::Transform localTransform;
AZ::TransformBus::EventResult(localTransform, m_entityId, &AZ::TransformBus::Events::GetLocalTM);
AZ::Transform worldTransform;
AZ::TransformBus::EventResult(worldTransform, m_entityId, &AZ::TransformBus::Events::GetWorldTM);
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();
}
}

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

@ -95,4 +95,20 @@ namespace Multiplayer
private:
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
#include <AzCore/std/containers/list.h>
#include <Source/MultiplayerTypes.h>
namespace AZ
{
@ -17,7 +18,13 @@ namespace {{ Namespace }}
{% set ComponentName = Component.attrib['Name'] %}
{{ ComponentName }},
{% 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);
//! For creating multiplayer component network inputs.
void CreateComponentNetworkInput();
}

@ -33,22 +33,20 @@ const {{ Property.attrib['Type'] }}& Get{{ PropertyName }}() const;
#}
{% macro DeclareNetworkPropertySetter(Property) %}
{% set PropertyName = UpperFirst(Property.attrib['Name']) %}
{% if Property.attrib['IsPredictable'] | booleanTrue %}
{% if Property.attrib['Container'] == 'Array' %}
void Set{{ PropertyName }}(const Multiplayer::NetworkInput&, int32_t index, const {{ Property.attrib['Type'] }}& value);
{{ Property.attrib['Type'] }}& Modify{{ PropertyName }}(const Multiplayer::NetworkInput&, int32_t index);
{% elif Property.attrib['Container'] == 'Vector' %}
void Set{{ PropertyName }}(const Multiplayer::NetworkInput&, int32_t index, const {{ Property.attrib['Type'] }}& value);
{{ Property.attrib['Type'] }}& Modify{{ PropertyName }}(const Multiplayer::NetworkInput&, int32_t index);
bool {{ PropertyName }}PushBack(const Multiplayer::NetworkInput&, const {{ Property.attrib['Type'] }}& value);
bool {{ PropertyName }}PopBack(const Multiplayer::NetworkInput&);
void {{ PropertyName }}Clear(const Multiplayer::NetworkInput&);
{% elif Property.attrib['Container'] == 'Object' %}
void Set{{ PropertyName }}(const Multiplayer::NetworkInput&, const {{ Property.attrib['Type'] }}& value);
{{ Property.attrib['Type'] }}& Modify{{ PropertyName }}(const Multiplayer::NetworkInput&);
{% else %}
void Set{{ PropertyName }}(const Multiplayer::NetworkInput&, const {{ Property.attrib['Type'] }}& value);
{% endif %}
{% if Property.attrib['Container'] == 'Array' %}
void Set{{ PropertyName }}(int32_t index, const {{ Property.attrib['Type'] }}& value);
{{ Property.attrib['Type'] }}& Modify{{ PropertyName }}(int32_t index);
{% elif Property.attrib['Container'] == 'Vector' %}
void Set{{ PropertyName }}(int32_t index, const {{ Property.attrib['Type'] }}& value);
{{ Property.attrib['Type'] }}& Modify{{ PropertyName }}(int32_t index);
bool {{ PropertyName }}PushBack(const {{ Property.attrib['Type'] }}& value);
bool {{ PropertyName }}PopBack();
void {{ PropertyName }}Clear();
{% elif Property.attrib['Container'] == 'Object' %}
void Set{{ PropertyName }}(const {{ Property.attrib['Type'] }}& value);
{{ Property.attrib['Type'] }}& Modify{{ PropertyName }}();
{% else %}
void Set{{ PropertyName }}(const {{ Property.attrib['Type'] }}& value);
{% endif %}
{% 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 void Reflect(AZ::ReflectContext* context);
static void ReflectToEditContext(AZ::ReflectContext* context);
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided);
static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required);
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) %}
{% if Property.attrib['IsPredictable'] | booleanTrue %}
{% if Property.attrib['Container'] == 'Array' %}
void {{ ClassName }}::Set{{ UpperFirst(Property.attrib['Name']) }}(const Multiplayer::NetworkInput& inputCommand, int32_t index, const {{ Property.attrib['Type'] }}& value)
{% macro DefineNetworkPropertySet(Component, ReplicateFrom, ReplicateTo, ClassName, Property) %}
{% if Property.attrib['Container'] == 'Array' %}
void {{ ClassName }}::Set{{ UpperFirst(Property.attrib['Name']) }}(int32_t index, const {{ Property.attrib['Type'] }}& 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') }});
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];
}
{% elif Property.attrib['Container'] == 'Vector' %}
void {{ ClassName }}::Set{{ UpperFirst(Property.attrib['Name']) }}(const Multiplayer::NetworkInput& inputCommand, int32_t index, const {{ Property.attrib['Type'] }}& value)
{% elif Property.attrib['Container'] == 'Vector' %}
void {{ ClassName }}::Set{{ UpperFirst(Property.attrib['Name']) }}(int32_t index, const {{ Property.attrib['Type'] }}& 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') }});
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];
}
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();
GetParent().m_{{ LowerFirst(Property.attrib['Name']) }}.PushBack(value);
@ -134,24 +133,24 @@ void {{ ClassName }}::{{ UpperFirst(Property.attrib['Name']) }}Clear(const Multi
GetParent().MarkDirty();
}
{% elif Property.attrib['Container'] == 'Object' %}
void {{ ClassName }}::Set{{ UpperFirst(Property.attrib['Name']) }}(const Multiplayer::NetworkInput& inputCommand, const {{ Property.attrib['Type'] }}& value)
{% elif Property.attrib['Container'] == 'Object' %}
void {{ ClassName }}::Set{{ UpperFirst(Property.attrib['Name']) }}(const {{ Property.attrib['Type'] }}& 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().MarkDirty();
return GetParent().m_{{ LowerFirst(Property.attrib['Name']) }}{% if Property.attrib['IsRewindable']|booleanTrue %}.Modify(){% endif %};
}
{% else %}
void {{ ClassName }}::Set{{ UpperFirst(Property.attrib['Name']) }}(const Multiplayer::NetworkInput&, const {{ Property.attrib['Type'] }}& value)
{% else %}
void {{ ClassName }}::Set{{ UpperFirst(Property.attrib['Name']) }}(const {{ Property.attrib['Type'] }}& value)
{
if (GetParent().m_{{ LowerFirst(Property.attrib['Name']) }} != value)
{
@ -161,7 +160,6 @@ void {{ ClassName }}::Set{{ UpperFirst(Property.attrib['Name']) }}(const Multipl
}
}
{% endif %}
{% endif %}
{% endmacro %}
{#
@ -273,7 +271,7 @@ void {{ ClassName }}::Set{{ UpperFirst(Property.attrib['Name']) }}(const {{ Prop
{% call(Property) AutoComponentMacros.ParseNetworkProperties(Component, ReplicateFrom, ReplicateTo) %}
{% if Property.attrib['IsPublic'] | booleanTrue != IsProtected %}
{{ DefineNetworkPropertyGet(ClassName, Property, "GetParent().") }}
{{ DefineNetworkPropertyPredictableSet(Component, ReplicateFrom, ReplicateTo, ClassName, Property) }}
{{ DefineNetworkPropertySet(Component, ReplicateFrom, ReplicateTo, ClassName, Property) }}
{% endif %}
{% endcall %}
{% endmacro %}
@ -478,6 +476,7 @@ bool {{ ClassName }}::Serialize{{ AutoComponentMacros.GetNetPropertiesSetName(Re
{%- if networkPropertyCount.update({'value': networkPropertyCount.value + 1}) %}{% endif -%}
{% endcall %}
{% 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)
[[maybe_unused]] bool modifyRecord = serializer.GetSerializerMode() == AzNetworking::SerializerMode::WriteToObject;
{% 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) }}),
m_{{ LowerFirst(Property.attrib['Name']) }},
"{{ Property.attrib['Name'] }}",
GetNetComponentId()
GetNetComponentId(),
stats
);
{% endif %}
{% endcall %}
@ -1111,23 +1111,29 @@ namespace {{ Component.attrib['Namespace'] }}
{{ DefineNetworkPropertyReflection(Component, 'Authority', 'Client', ComponentBaseName)|indent(16) -}}
{{ DefineNetworkPropertyReflection(Component, 'Authority', 'Autonomous', 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();
if (editContext)
{
editContext->Class<{{ ComponentBaseName }}>("{{ ComponentName }}", "{{ Component.attrib['Description'] }}")
editContext->Class<{{ ComponentName }}>("{{ ComponentName }}", "{{ Component.attrib['Description'] }}")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::Category, "Multiplayer")
->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC_CE("Game"))
{{ DefineNetworkPropertyEditReflection(Component, 'Authority', 'Authority', ComponentBaseName)|indent(20) -}}
{{ DefineNetworkPropertyEditReflection(Component, 'Authority', 'Server', ComponentBaseName)|indent(20) -}}
{{ DefineNetworkPropertyEditReflection(Component, 'Authority', 'Client', ComponentBaseName)|indent(20) -}}
{{ DefineNetworkPropertyEditReflection(Component, 'Authority', 'Autonomous', ComponentBaseName)|indent(20) -}}
{{ DefineNetworkPropertyEditReflection(Component, 'Autonomous', 'Authority', ComponentBaseName)|indent(20) }}
{{ DefineArchetypePropertyEditReflection(Component, ComponentBaseName)|indent(20) }}
;
{{ DefineNetworkPropertyEditReflection(Component, 'Authority', 'Authority', ComponentName)|indent(20) -}}
{{ DefineNetworkPropertyEditReflection(Component, 'Authority', 'Server', ComponentName)|indent(20) -}}
{{ DefineNetworkPropertyEditReflection(Component, 'Authority', 'Client', ComponentName)|indent(20) -}}
{{ DefineNetworkPropertyEditReflection(Component, 'Authority', 'Autonomous', ComponentName)|indent(20) -}}
{{ DefineNetworkPropertyEditReflection(Component, 'Autonomous', 'Authority', ComponentName)|indent(20) }}
{{ DefineArchetypePropertyEditReflection(Component, ComponentName)|indent(20) }};
}
}
}

@ -16,7 +16,7 @@
<Include File="Source/NetworkInput/NetworkInputVector.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">
<Param Type="Multiplayer::NetworkInputVector" Name="inputArray" />
@ -25,7 +25,7 @@
</RemoteProcedure>
<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" />
</RemoteProcedure>

@ -24,7 +24,6 @@ namespace Multiplayer
serializeContext->Class<LocalPredictionPlayerInputComponent, LocalPredictionPlayerInputComponentBase>()
->Version(1);
}
LocalPredictionPlayerInputComponentBase::Reflect(context);
}
@ -48,7 +47,7 @@ namespace Multiplayer
void LocalPredictionPlayerInputComponentController::HandleSendClientInputCorrection
(
[[maybe_unused]] const Multiplayer::NetworkInputId& inputId,
[[maybe_unused]] const Multiplayer::ClientInputId& inputId,
[[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 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
{
const NetBindComponent* netBindComponent = GetNetBindComponent();
return netBindComponent ? netBindComponent->GetNetEntityId() : InvalidNetEntityId;
return m_netBindComponent ? m_netBindComponent->GetNetEntityId() : InvalidNetEntityId;
}
NetEntityRole MultiplayerComponent::GetNetEntityRole() const
{
return m_netBindComponent ? m_netBindComponent->GetNetEntityRole() : NetEntityRole::InvalidRole;
}
ConstNetworkEntityHandle MultiplayerComponent::GetEntityHandle() const

@ -62,6 +62,7 @@ namespace Multiplayer
//! @}
NetEntityId GetNetEntityId() const;
NetEntityRole GetNetEntityRole() const;
ConstNetworkEntityHandle GetEntityHandle() const;
NetworkEntityHandle GetEntityHandle();
void MarkDirty();
@ -109,7 +110,8 @@ namespace Multiplayer
int32_t bitIndex,
TYPE& value,
const char* name,
[[maybe_unused]] NetComponentId componentId
[[maybe_unused]] NetComponentId componentId,
MultiplayerStats& stats
)
{
if (bitset.GetBit(bitIndex))
@ -119,6 +121,7 @@ namespace Multiplayer
serializer.Serialize(value, name);
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);
}
const uint32_t postUpdateSize = serializer.GetSize();
@ -126,8 +129,7 @@ namespace Multiplayer
const uint32_t updateSize = (postUpdateSize - prevUpdateSize);
if (updateSize > 0)
{
MultiplayerStats& stats = AZ::Interface<IMultiplayer>::Get()->GetStats();
if (serializer.GetSerializerMode() == AzNetworking::SerializerMode::WriteToObject)
if (modifyRecord)
{
stats.m_propertyUpdatesRecv++;
stats.m_propertyUpdatesRecvBytes += updateSize;

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

@ -47,6 +47,10 @@ namespace Multiplayer
//! @return the networkId for the entity that owns this controller
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.
//! @return the raw AZ::Entity pointer for the entity that owns this controller
AZ::Entity* GetEntity() const;

@ -13,6 +13,8 @@
#include <Source/Components/NetworkTransformComponent.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/EBus/IEventScheduler.h>
#include <AzFramework/Components/TransformComponent.h>
namespace Multiplayer
{
@ -24,7 +26,81 @@ namespace Multiplayer
serializeContext->Class<NetworkTransformComponent, NetworkTransformComponentBase>()
->Version(1);
}
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
#include <Source/AutoGen/NetworkTransformComponent.AutoComponent.h>
#include <AzCore/Component/TransformBus.h>
namespace Multiplayer
{
@ -22,20 +23,36 @@ namespace Multiplayer
public:
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 {}
void OnActivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) override {}
void OnDeactivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) override {}
NetworkTransformComponent();
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
: public NetworkTransformComponentControllerBase
{
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 {}
void OnDeactivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) override {}
AZ::TransformChangedEvent::Handler m_transformChangedHandler;
};
}

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

Loading…
Cancel
Save