diff --git a/.gitignore b/.gitignore index 3d1d994..aace89d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,7 @@ .gradle +gradle/ build/ -!gradle/wrapper/gradle-wrapper.jar -!**/src/main/**/build/ -!**/src/test/**/build/ + ### IntelliJ IDEA ### /.idea/workspace.xml diff --git a/.idea/compiler.xml b/.idea/compiler.xml index 659bf43..6030a3d 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -1,6 +1,6 @@ - + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml index f9163b4..7bcf8e5 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -6,7 +6,7 @@ diff --git a/.idea/misc.xml b/.idea/misc.xml index 370437b..a4d2007 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,8 +1,8 @@ - - - + + - + + \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts deleted file mode 100644 index d3e83b2..0000000 --- a/build.gradle.kts +++ /dev/null @@ -1,66 +0,0 @@ -val lwjglVersion = "3.3.3" -val jomlVersion = "1.10.5" - -group = "dev.euph" -version = "1.0-SNAPSHOT" - -sourceSets { - main { - java.srcDirs("src") - resources.srcDir("res") - } -} - -plugins { - java - application -} - -val lwjglNatives = Pair( - System.getProperty("os.name")!!, - System.getProperty("os.arch")!! -).let { (name, arch) -> - when { - arrayOf("Linux", "FreeBSD", "SunOS", "Unit").any { name.startsWith(it) } -> - if (arrayOf("arm", "aarch64").any { arch.startsWith(it) }) - "natives-linux${if (arch.contains("64") || arch.startsWith("armv8")) "-arm64" else "-arm32"}" - else if (arch.startsWith("ppc")) - "natives-linux-ppc64le" - else if (arch.startsWith("riscv")) - "natives-linux-riscv64" - else - "natives-linux" - arrayOf("Mac OS X", "Darwin").any { name.startsWith(it) } -> - "natives-macos${if (arch.startsWith("aarch64")) "-arm64" else ""}" - arrayOf("Windows").any { name.startsWith(it) } -> - if (arch.contains("64")) - "natives-windows${if (arch.startsWith("aarch64")) "-arm64" else ""}" - else - "natives-windows-x86" - else -> throw Error("Unrecognized or unsupported platform. Please set \"lwjglNatives\" manually") - } -} - -repositories { - mavenCentral() -} - -dependencies { - implementation(platform("org.lwjgl:lwjgl-bom:$lwjglVersion")) - - implementation("org.lwjgl", "lwjgl") - implementation("org.lwjgl", "lwjgl-assimp") - implementation("org.lwjgl", "lwjgl-glfw") - implementation("org.lwjgl", "lwjgl-openal") - implementation("org.lwjgl", "lwjgl-opengl") - implementation("org.lwjgl", "lwjgl-remotery") - implementation("org.lwjgl", "lwjgl-stb") - runtimeOnly("org.lwjgl", "lwjgl", classifier = lwjglNatives) - runtimeOnly("org.lwjgl", "lwjgl-assimp", classifier = lwjglNatives) - runtimeOnly("org.lwjgl", "lwjgl-glfw", classifier = lwjglNatives) - runtimeOnly("org.lwjgl", "lwjgl-openal", classifier = lwjglNatives) - runtimeOnly("org.lwjgl", "lwjgl-opengl", classifier = lwjglNatives) - runtimeOnly("org.lwjgl", "lwjgl-remotery", classifier = lwjglNatives) - runtimeOnly("org.lwjgl", "lwjgl-stb", classifier = lwjglNatives) - implementation("org.joml", "joml", jomlVersion) -} \ No newline at end of file diff --git a/game/build.gradle.kts b/game/build.gradle.kts new file mode 100644 index 0000000..e7e753d --- /dev/null +++ b/game/build.gradle.kts @@ -0,0 +1,23 @@ +plugins { + application + java +} + +repositories { + mavenCentral() +} + +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(22)) + } +} + +application { + mainClass.set("dev.euph.game.Main") +} + +dependencies { + implementation(project(path = ":engine", configuration = "shadow")) +} + diff --git a/game/settings.gradle.kts b/game/settings.gradle.kts new file mode 100644 index 0000000..ca4e2cd --- /dev/null +++ b/game/settings.gradle.kts @@ -0,0 +1,3 @@ +rootProject.name = "game" +include(":engine") +project(":engine").projectDir = file("../") \ No newline at end of file diff --git a/src/dev/euph/game/CameraController.java b/game/src/main/java/dev/euph/game/CameraController.java similarity index 100% rename from src/dev/euph/game/CameraController.java rename to game/src/main/java/dev/euph/game/CameraController.java diff --git a/src/dev/euph/game/Main.java b/game/src/main/java/dev/euph/game/Main.java similarity index 92% rename from src/dev/euph/game/Main.java rename to game/src/main/java/dev/euph/game/Main.java index 107f452..be48cd9 100644 --- a/src/dev/euph/game/Main.java +++ b/game/src/main/java/dev/euph/game/Main.java @@ -19,13 +19,16 @@ import dev.euph.engine.util.Path; import org.joml.Vector3f; import java.awt.*; +import java.io.InputStream; public class Main { public static void main(String[] args) { Window window = new Window(1200, 720, "Test Window"); Scene scene = new Scene(); - ShaderManager shaderManager = new ShaderManager(); + InputStream fragmentShaderStream = Main.class.getResourceAsStream("/dev/euph/game/shader/default.fs.glsl"); + InputStream vertexShaderStream = Main.class.getResourceAsStream("/dev/euph/game/shader/default.vs.glsl"); + ShaderManager shaderManager = new ShaderManager(vertexShaderStream, fragmentShaderStream); //Creating a Camera Entity cameraEntity = new Entity("Camera"); diff --git a/src/dev/euph/game/OctreeTest.java b/game/src/main/java/dev/euph/game/OctreeTest.java similarity index 100% rename from src/dev/euph/game/OctreeTest.java rename to game/src/main/java/dev/euph/game/OctreeTest.java diff --git a/src/dev/euph/game/PipelineTest.java b/game/src/main/java/dev/euph/game/PipelineTest.java similarity index 100% rename from src/dev/euph/game/PipelineTest.java rename to game/src/main/java/dev/euph/game/PipelineTest.java diff --git a/res/shader/fs/default.glsl b/game/src/main/resources/dev/euph/game/shader/default.fs.glsl similarity index 100% rename from res/shader/fs/default.glsl rename to game/src/main/resources/dev/euph/game/shader/default.fs.glsl diff --git a/res/shader/vs/default.glsl b/game/src/main/resources/dev/euph/game/shader/default.vs.glsl similarity index 100% rename from res/shader/vs/default.glsl rename to game/src/main/resources/dev/euph/game/shader/default.vs.glsl diff --git a/res/human.blend b/game/src/main/resources/human.blend similarity index 100% rename from res/human.blend rename to game/src/main/resources/human.blend diff --git a/res/human_rigged.mtl b/game/src/main/resources/human_rigged.mtl similarity index 100% rename from res/human_rigged.mtl rename to game/src/main/resources/human_rigged.mtl diff --git a/res/human_rigged.obj b/game/src/main/resources/human_rigged.obj similarity index 100% rename from res/human_rigged.obj rename to game/src/main/resources/human_rigged.obj diff --git a/res/shader/fs/testFS.glsl b/game/src/main/resources/shader/testFS.glsl similarity index 100% rename from res/shader/fs/testFS.glsl rename to game/src/main/resources/shader/testFS.glsl diff --git a/res/shader/vs/testVS.glsl b/game/src/main/resources/shader/testVS.glsl similarity index 100% rename from res/shader/vs/testVS.glsl rename to game/src/main/resources/shader/testVS.glsl diff --git a/res/uv.png b/game/src/main/resources/uv.png similarity index 100% rename from res/uv.png rename to game/src/main/resources/uv.png diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index 62f495d..0000000 --- a/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,7 +0,0 @@ -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip -networkTimeout=10000 -validateDistributionUrl=true -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew deleted file mode 100755 index fcb6fca..0000000 --- a/gradlew +++ /dev/null @@ -1,248 +0,0 @@ -#!/bin/sh - -# -# Copyright © 2015-2021 the original authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -############################################################################## -# -# Gradle start up script for POSIX generated by Gradle. -# -# Important for running: -# -# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is -# noncompliant, but you have some other compliant shell such as ksh or -# bash, then to run this script, type that shell name before the whole -# command line, like: -# -# ksh Gradle -# -# Busybox and similar reduced shells will NOT work, because this script -# requires all of these POSIX shell features: -# * functions; -# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», -# «${var#prefix}», «${var%suffix}», and «$( cmd )»; -# * compound commands having a testable exit status, especially «case»; -# * various built-in commands including «command», «set», and «ulimit». -# -# Important for patching: -# -# (2) This script targets any POSIX shell, so it avoids extensions provided -# by Bash, Ksh, etc; in particular arrays are avoided. -# -# The "traditional" practice of packing multiple parameters into a -# space-separated string is a well documented source of bugs and security -# problems, so this is (mostly) avoided, by progressively accumulating -# options in "$@", and eventually passing that to Java. -# -# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, -# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; -# see the in-line comments for details. -# -# There are tweaks for specific operating systems such as AIX, CygWin, -# Darwin, MinGW, and NonStop. -# -# (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt -# within the Gradle project. -# -# You can find Gradle at https://github.com/gradle/gradle/. -# -############################################################################## - -# Attempt to set APP_HOME - -# Resolve links: $0 may be a link -app_path=$0 - -# Need this for daisy-chained symlinks. -while - APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path - [ -h "$app_path" ] -do - ls=$( ls -ld "$app_path" ) - link=${ls#*' -> '} - case $link in #( - /*) app_path=$link ;; #( - *) app_path=$APP_HOME$link ;; - esac -done - -# This is normally unused -# shellcheck disable=SC2034 -APP_BASE_NAME=${0##*/} -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD=maximum - -warn () { - echo "$*" -} >&2 - -die () { - echo - echo "$*" - echo - exit 1 -} >&2 - -# OS specific support (must be 'true' or 'false'). -cygwin=false -msys=false -darwin=false -nonstop=false -case "$( uname )" in #( - CYGWIN* ) cygwin=true ;; #( - Darwin* ) darwin=true ;; #( - MSYS* | MINGW* ) msys=true ;; #( - NONSTOP* ) nonstop=true ;; -esac - -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar - - -# Determine the Java command to use to start the JVM. -if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD=$JAVA_HOME/jre/sh/java - else - JAVACMD=$JAVA_HOME/bin/java - fi - if [ ! -x "$JAVACMD" ] ; then - die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -else - JAVACMD=java - if ! command -v java >/dev/null 2>&1 - then - die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -fi - -# Increase the maximum file descriptors if we can. -if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then - case $MAX_FD in #( - max*) - # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 - MAX_FD=$( ulimit -H -n ) || - warn "Could not query maximum file descriptor limit" - esac - case $MAX_FD in #( - '' | soft) :;; #( - *) - # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 - ulimit -n "$MAX_FD" || - warn "Could not set maximum file descriptor limit to $MAX_FD" - esac -fi - -# Collect all arguments for the java command, stacking in reverse order: -# * args from the command line -# * the main class name -# * -classpath -# * -D...appname settings -# * --module-path (only if needed) -# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. - -# For Cygwin or MSYS, switch paths to Windows format before running java -if "$cygwin" || "$msys" ; then - APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) - CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) - - JAVACMD=$( cygpath --unix "$JAVACMD" ) - - # Now convert the arguments - kludge to limit ourselves to /bin/sh - for arg do - if - case $arg in #( - -*) false ;; # don't mess with options #( - /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath - [ -e "$t" ] ;; #( - *) false ;; - esac - then - arg=$( cygpath --path --ignore --mixed "$arg" ) - fi - # Roll the args list around exactly as many times as the number of - # args, so each arg winds up back in the position where it started, but - # possibly modified. - # - # NB: a `for` loop captures its iteration list before it begins, so - # changing the positional parameters here affects neither the number of - # iterations, nor the values presented in `arg`. - shift # remove old arg - set -- "$@" "$arg" # push replacement arg - done -fi - - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' - -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. - -set -- \ - "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ - "$@" - -# Stop when "xargs" is not available. -if ! command -v xargs >/dev/null 2>&1 -then - die "xargs is not available" -fi - -# Use "xargs" to parse quoted args. -# -# With -n1 it outputs one arg per line, with the quotes and backslashes removed. -# -# In Bash we could simply go: -# -# readarray ARGS < <( xargs -n1 <<<"$var" ) && -# set -- "${ARGS[@]}" "$@" -# -# but POSIX shell has neither arrays nor command substitution, so instead we -# post-process each arg (as a line of input to sed) to backslash-escape any -# character that might be a shell metacharacter, then use eval to reverse -# that process (while maintaining the separation between arguments), and wrap -# the whole thing up as a single "set" statement. -# -# This will of course break if any of these variables contains a newline or -# an unmatched quote. -# - -eval "set -- $( - printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | - xargs -n1 | - sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | - tr '\n' ' ' - )" '"$@"' - -exec "$JAVACMD" "$@" diff --git a/profiler/LICENSE b/profiler/LICENSE deleted file mode 100644 index b0fcd6d..0000000 --- a/profiler/LICENSE +++ /dev/null @@ -1,175 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. diff --git a/profiler/lib/Remotery.c b/profiler/lib/Remotery.c deleted file mode 100644 index ba26535..0000000 --- a/profiler/lib/Remotery.c +++ /dev/null @@ -1,10142 +0,0 @@ -// -// Copyright 2014-2022 Celtoys Ltd -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -/* -@Contents: - - @DEPS: External Dependencies - @TIMERS: Platform-specific timers - @TLS: Thread-Local Storage - @ERROR: Error handling - @ATOMIC: Atomic Operations - @RNG: Random Number Generator - @LFSR: Galois Linear-feedback Shift Register - @VMBUFFER: Mirror Buffer using Virtual Memory for auto-wrap - @NEW: New/Delete operators with error values for simplifying object create/destroy - @SAFEC: Safe C Library excerpts - @OSTHREADS: Wrappers around OS-specific thread functions - @THREADS: Cross-platform thread object - @OBJALLOC: Reusable Object Allocator - @DYNBUF: Dynamic Buffer - @HASHTABLE: Integer pair hash map for inserts/finds. No removes for added simplicity. - @STRINGTABLE: Map from string hash to string offset in local buffer - @SOCKETS: Sockets TCP/IP Wrapper - @SHA1: SHA-1 Cryptographic Hash Function - @BASE64: Base-64 encoder - @MURMURHASH: Murmur-Hash 3 - @WEBSOCKETS: WebSockets - @MESSAGEQ: Multiple producer, single consumer message queue - @NETWORK: Network Server - @SAMPLE: Base Sample Description (CPU by default) - @SAMPLETREE: A tree of samples with their allocator - @TPROFILER: Thread Profiler data, storing both sampling and instrumentation results - @TGATHER: Thread Gatherer, periodically polling for newly created threads - @TSAMPLER: Sampling thread contexts - @REMOTERY: Remotery - @CUDA: CUDA event sampling - @D3D11: Direct3D 11 event sampling - @D3D12: Direct3D 12 event sampling - @OPENGL: OpenGL event sampling - @METAL: Metal event sampling - @SAMPLEAPI: Sample API for user callbacks - @PROPERTYAPI: Property API for user callbacks - @PROPERTIES: Property API -*/ - -#define RMT_IMPL -#include "Remotery.h" - -#ifdef RMT_PLATFORM_WINDOWS -#pragma comment(lib, "ws2_32.lib") -#pragma comment(lib, "winmm.lib") -#endif - -#if RMT_ENABLED - -// Global settings -static rmtSettings g_Settings; -static rmtBool g_SettingsInitialized = RMT_FALSE; - -/* ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- - @DEPS: External Dependencies ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- -*/ - -// clang-format off - -// -// Required CRT dependencies -// -#if RMT_USE_TINYCRT - - #include - #include - #include - - #define CreateFileMapping CreateFileMappingA - #define RMT_ENABLE_THREAD_SAMPLER - -#else - - #ifdef RMT_PLATFORM_MACOS - #include - #include - #include - #include - #else - #if !defined(__FreeBSD__) && !defined(__OpenBSD__) - #include - #endif - #endif - - #include - #include - #include - #include - #include - #include - #include - - #ifdef RMT_PLATFORM_WINDOWS - #include - #ifndef __MINGW32__ - #include - #endif - #undef min - #undef max - #include - #include - #include - typedef long NTSTATUS; // winternl.h - - #ifdef _XBOX_ONE - #ifdef _DURANGO - #include "xmem.h" - #endif - #else - #define RMT_ENABLE_THREAD_SAMPLER - #endif - - #endif - - #ifdef RMT_PLATFORM_LINUX - #if defined(__FreeBSD__) || defined(__OpenBSD__) - #include - #else - #include - #endif - #endif - - #if defined(RMT_PLATFORM_POSIX) - #include - #include - #include - #include - #include - #include - #include - #include - #include - #endif - - #ifdef __MINGW32__ - #include - #endif - -#endif - -#if RMT_USE_CUDA - #include -#endif - -// clang-format on - -#if defined(_MSC_VER) && !defined(__clang__) - #define RMT_UNREFERENCED_PARAMETER(i) (i) -#else - #define RMT_UNREFERENCED_PARAMETER(i) (void)(1 ? (void)0 : ((void)i)) -#endif - -// Executes the given statement and returns from the calling function if it fails, returning the error with it -#define rmtTry(stmt) \ - { \ - rmtError error = stmt; \ - if (error != RMT_ERROR_NONE) \ - return error; \ - } - -static rmtU8 minU8(rmtU8 a, rmtU8 b) -{ - return a < b ? a : b; -} -static rmtU16 maxU16(rmtU16 a, rmtU16 b) -{ - return a > b ? a : b; -} -static rmtS32 minS32(rmtS32 a, rmtS32 b) -{ - return a < b ? a : b; -} -static rmtS32 maxS32(rmtS32 a, rmtS32 b) -{ - return a > b ? a : b; -} -static rmtU32 minU32(rmtU32 a, rmtU32 b) -{ - return a < b ? a : b; -} -static rmtU32 maxU32(rmtU32 a, rmtU32 b) -{ - return a > b ? a : b; -} -static rmtS64 maxS64(rmtS64 a, rmtS64 b) -{ - return a > b ? a : b; -} - -// Memory management functions -static void* rmtMalloc(rmtU32 size) -{ - return g_Settings.malloc(g_Settings.mm_context, size); -} - -static void* rmtRealloc(void* ptr, rmtU32 size) -{ - return g_Settings.realloc(g_Settings.mm_context, ptr, size); -} - -static void rmtFree(void* ptr) -{ - g_Settings.free(g_Settings.mm_context, ptr); -} - -// File system functions -static FILE* rmtOpenFile(const char* filename, const char* mode) -{ -#if defined(RMT_PLATFORM_WINDOWS) && !RMT_USE_TINYCRT - FILE* fp; - return fopen_s(&fp, filename, mode) == 0 ? fp : NULL; -#else - return fopen(filename, mode); -#endif -} - -void rmtCloseFile(FILE* fp) -{ - if (fp != NULL) - { - fclose(fp); - } -} - -rmtBool rmtWriteFile(FILE* fp, const void* data, rmtU32 size) -{ - assert(fp != NULL); - return fwrite(data, size, 1, fp) == size ? RMT_TRUE : RMT_FALSE; -} - -#if RMT_USE_OPENGL -// DLL/Shared Library functions - -static void* rmtLoadLibrary(const char* path) -{ -#if defined(RMT_PLATFORM_WINDOWS) - return (void*)LoadLibraryA(path); -#elif defined(RMT_PLATFORM_POSIX) - return dlopen(path, RTLD_LOCAL | RTLD_LAZY); -#else - return NULL; -#endif -} - -static void rmtFreeLibrary(void* handle) -{ -#if defined(RMT_PLATFORM_WINDOWS) - FreeLibrary((HMODULE)handle); -#elif defined(RMT_PLATFORM_POSIX) - dlclose(handle); -#endif -} - -#if defined(RMT_PLATFORM_WINDOWS) -typedef FARPROC ProcReturnType; -#else -typedef void* ProcReturnType; -#endif - -static ProcReturnType rmtGetProcAddress(void* handle, const char* symbol) -{ -#if defined(RMT_PLATFORM_WINDOWS) - return GetProcAddress((HMODULE)handle, (LPCSTR)symbol); -#elif defined(RMT_PLATFORM_POSIX) - return dlsym(handle, symbol); -#endif -} - -#endif - -/* ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- - @TIMERS: Platform-specific timers ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- -*/ - -// -// Get millisecond timer value that has only one guarantee: multiple calls are consistently comparable. -// On some platforms, even though this returns milliseconds, the timer may be far less accurate. -// -static rmtU32 msTimer_Get() -{ -#ifdef RMT_PLATFORM_WINDOWS - - return (rmtU32)GetTickCount(); - -#else - - clock_t time = clock(); - -// CLOCKS_PER_SEC is 128 on FreeBSD, causing div/0 -#if defined(__FreeBSD__) || defined(__OpenBSD__) - rmtU32 msTime = (rmtU32)(time * 1000 / CLOCKS_PER_SEC); -#else - rmtU32 msTime = (rmtU32)(time / (CLOCKS_PER_SEC / 1000)); -#endif - - return msTime; - -#endif -} - -// -// Micro-second accuracy high performance counter -// -#ifndef RMT_PLATFORM_WINDOWS -typedef rmtU64 LARGE_INTEGER; -#endif -typedef struct -{ - LARGE_INTEGER counter_start; - double counter_scale; -} usTimer; - -static void usTimer_Init(usTimer* timer) -{ -#if defined(RMT_PLATFORM_WINDOWS) - LARGE_INTEGER performance_frequency; - - assert(timer != NULL); - - // Calculate the scale from performance counter to microseconds - QueryPerformanceFrequency(&performance_frequency); - timer->counter_scale = 1000000.0 / performance_frequency.QuadPart; - - // Record the offset for each read of the counter - QueryPerformanceCounter(&timer->counter_start); - -#elif defined(RMT_PLATFORM_MACOS) - - mach_timebase_info_data_t nsScale; - mach_timebase_info(&nsScale); - const double ns_per_us = 1.0e3; - timer->counter_scale = (double)(nsScale.numer) / ((double)nsScale.denom * ns_per_us); - - timer->counter_start = mach_absolute_time(); - -#elif defined(RMT_PLATFORM_LINUX) - - struct timespec tv; - clock_gettime(CLOCK_REALTIME, &tv); - timer->counter_start = (rmtU64)(tv.tv_sec * (rmtU64)1000000) + (rmtU64)(tv.tv_nsec * 0.001); - -#endif -} - -static rmtU64 usTimer_Get(usTimer* timer) -{ -#if defined(RMT_PLATFORM_WINDOWS) - LARGE_INTEGER performance_count; - - assert(timer != NULL); - - // Read counter and convert to microseconds - QueryPerformanceCounter(&performance_count); - return (rmtU64)((performance_count.QuadPart - timer->counter_start.QuadPart) * timer->counter_scale); - -#elif defined(RMT_PLATFORM_MACOS) - - rmtU64 curr_time = mach_absolute_time(); - return (rmtU64)((curr_time - timer->counter_start) * timer->counter_scale); - -#elif defined(RMT_PLATFORM_LINUX) - - struct timespec tv; - clock_gettime(CLOCK_REALTIME, &tv); - return ((rmtU64)(tv.tv_sec * (rmtU64)1000000) + (rmtU64)(tv.tv_nsec * 0.001)) - timer->counter_start; - -#endif -} - -static void msSleep(rmtU32 time_ms) -{ -#ifdef RMT_PLATFORM_WINDOWS - Sleep(time_ms); -#elif defined(RMT_PLATFORM_POSIX) - usleep(time_ms * 1000); -#endif -} - -static struct tm* TimeDateNow() -{ - time_t time_now = time(NULL); - -#if defined(RMT_PLATFORM_WINDOWS) && !RMT_USE_TINYCRT - // Discard the thread-safety benefit of gmtime_s - static struct tm tm_now; - gmtime_s(&tm_now, &time_now); - return &tm_now; -#else - return gmtime(&time_now); -#endif -} - -/* ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- - @TLS: Thread-Local Storage ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- -*/ - -#define TLS_INVALID_HANDLE 0xFFFFFFFF - -#if defined(RMT_PLATFORM_WINDOWS) -typedef rmtU32 rmtTLS; -#else -typedef pthread_key_t rmtTLS; -#endif - -static rmtError tlsAlloc(rmtTLS* handle) -{ - assert(handle != NULL); - -#if defined(RMT_PLATFORM_WINDOWS) - *handle = (rmtTLS)TlsAlloc(); - if (*handle == TLS_OUT_OF_INDEXES) - { - *handle = TLS_INVALID_HANDLE; - return RMT_ERROR_TLS_ALLOC_FAIL; - } -#elif defined(RMT_PLATFORM_POSIX) - if (pthread_key_create(handle, NULL) != 0) - { - *handle = TLS_INVALID_HANDLE; - return RMT_ERROR_TLS_ALLOC_FAIL; - } -#endif - - return RMT_ERROR_NONE; -} - -static void tlsFree(rmtTLS handle) -{ - assert(handle != TLS_INVALID_HANDLE); -#if defined(RMT_PLATFORM_WINDOWS) - TlsFree(handle); -#elif defined(RMT_PLATFORM_POSIX) - pthread_key_delete((pthread_key_t)handle); -#endif -} - -static void tlsSet(rmtTLS handle, void* value) -{ - assert(handle != TLS_INVALID_HANDLE); -#if defined(RMT_PLATFORM_WINDOWS) - TlsSetValue(handle, value); -#elif defined(RMT_PLATFORM_POSIX) - pthread_setspecific((pthread_key_t)handle, value); -#endif -} - -static void* tlsGet(rmtTLS handle) -{ - assert(handle != TLS_INVALID_HANDLE); -#if defined(RMT_PLATFORM_WINDOWS) - return TlsGetValue(handle); -#elif defined(RMT_PLATFORM_POSIX) - return pthread_getspecific((pthread_key_t)handle); -#endif -} - -/* ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- - @ERROR: Error handling ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- -*/ - -// Used to store per-thread error messages -// Static so that we can set error messages from code the Remotery object depends on -static rmtTLS g_lastErrorMessageTlsHandle = TLS_INVALID_HANDLE; -static const rmtU32 g_errorMessageSize = 1024; - -static rmtError rmtMakeError(rmtError error, rmtPStr error_message) -{ - char* thread_message_ptr; - rmtU32 error_len; - - // Allocate the TLS on-demand - // TODO(don): Make this thread-safe - if (g_lastErrorMessageTlsHandle == TLS_INVALID_HANDLE) - { - rmtTry(tlsAlloc(&g_lastErrorMessageTlsHandle)); - } - - // Allocate the string storage for the error message on-demand - thread_message_ptr = (char*)tlsGet(g_lastErrorMessageTlsHandle); - if (thread_message_ptr == NULL) - { - thread_message_ptr = (char*)rmtMalloc(g_errorMessageSize); - if (thread_message_ptr == NULL) - { - return RMT_ERROR_MALLOC_FAIL; - } - - tlsSet(g_lastErrorMessageTlsHandle, (void*)thread_message_ptr); - } - - // Safe copy of the error text without going via strcpy_s down below - error_len = strlen(error_message); - error_len = error_len >= g_errorMessageSize ? g_errorMessageSize - 1 : error_len; - memcpy(thread_message_ptr, error_message, error_len); - thread_message_ptr[error_len] = 0; - - return error; -} - -RMT_API rmtPStr rmt_GetLastErrorMessage() -{ - rmtPStr thread_message_ptr; - - // No message to specify if `rmtMakeError` failed or one hasn't been set yet - if (g_lastErrorMessageTlsHandle == TLS_INVALID_HANDLE) - { - return "No error message"; - } - thread_message_ptr = (rmtPStr)tlsGet(g_lastErrorMessageTlsHandle); - if (thread_message_ptr == NULL) - { - return "No error message"; - } - - return thread_message_ptr; -} - - -/* ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- - @MUTEX: Mutexes ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- -*/ - -#ifdef RMT_PLATFORM_WINDOWS -typedef CRITICAL_SECTION rmtMutex; -#else -typedef pthread_mutex_t rmtMutex; -#endif - -static void mtxInit(rmtMutex* mutex) -{ - assert(mutex != NULL); -#if defined(RMT_PLATFORM_WINDOWS) - InitializeCriticalSection(mutex); -#elif defined(RMT_PLATFORM_POSIX) - pthread_mutex_init(mutex, NULL); -#endif -} - -static void mtxLock(rmtMutex* mutex) -{ - assert(mutex != NULL); -#if defined(RMT_PLATFORM_WINDOWS) - EnterCriticalSection(mutex); -#elif defined(RMT_PLATFORM_POSIX) - pthread_mutex_lock(mutex); -#endif -} - -static void mtxUnlock(rmtMutex* mutex) -{ - assert(mutex != NULL); -#if defined(RMT_PLATFORM_WINDOWS) - LeaveCriticalSection(mutex); -#elif defined(RMT_PLATFORM_POSIX) - pthread_mutex_unlock(mutex); -#endif -} - -static void mtxDelete(rmtMutex* mutex) -{ - assert(mutex != NULL); -#if defined(RMT_PLATFORM_WINDOWS) - DeleteCriticalSection(mutex); -#elif defined(RMT_PLATFORM_POSIX) - pthread_mutex_destroy(mutex); -#endif -} - -/* ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- - @ATOMIC: Atomic Operations ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- -*/ - -// TODO(don): The CAS loops possible with this API are suboptimal. For example, AtomicCompareAndSwapU32 discards the -// return value which tells you the current (potentially mismatching) value of the location you want to modify. This -// means the CAS loop has to explicitly re-load this location on each modify attempt. Instead, the return value should -// be used to update the old value and an initial load only made once before the loop starts. - -// TODO(don): Vary these types across versions of C and C++ -typedef volatile rmtS32 rmtAtomicS32; -typedef volatile rmtU32 rmtAtomicU32; -typedef volatile rmtU64 rmtAtomicU64; - -static rmtBool AtomicCompareAndSwapU32(rmtU32 volatile* val, long old_val, long new_val) -{ -#if defined(RMT_PLATFORM_WINDOWS) && !defined(__MINGW32__) - return _InterlockedCompareExchange((long volatile*)val, new_val, old_val) == old_val ? RMT_TRUE : RMT_FALSE; -#elif defined(RMT_PLATFORM_POSIX) || defined(__MINGW32__) - return __sync_bool_compare_and_swap(val, old_val, new_val) ? RMT_TRUE : RMT_FALSE; -#endif -} - -static rmtBool AtomicCompareAndSwapU64(rmtAtomicU64* val, rmtU64 old_value, rmtU64 new_val) -{ - #if defined(RMT_PLATFORM_WINDOWS) && !defined(__MINGW32__) - return _InterlockedCompareExchange64((volatile LONG64*)val, (LONG64)new_val, (LONG64)old_value) == (LONG64)old_value - ? RMT_TRUE - : RMT_FALSE; - #elif defined(RMT_PLATFORM_POSIX) || defined(__MINGW32__) - return __sync_bool_compare_and_swap(val, old_value, new_val) ? RMT_TRUE : RMT_FALSE; - #endif -} - -static rmtBool AtomicCompareAndSwapPointer(long* volatile* ptr, long* old_ptr, long* new_ptr) -{ -#if defined(RMT_PLATFORM_WINDOWS) && !defined(__MINGW32__) -#ifdef _WIN64 - return _InterlockedCompareExchange64((__int64 volatile*)ptr, (__int64)new_ptr, (__int64)old_ptr) == (__int64)old_ptr - ? RMT_TRUE - : RMT_FALSE; -#else - return _InterlockedCompareExchange((long volatile*)ptr, (long)new_ptr, (long)old_ptr) == (long)old_ptr ? RMT_TRUE - : RMT_FALSE; -#endif -#elif defined(RMT_PLATFORM_POSIX) || defined(__MINGW32__) - return __sync_bool_compare_and_swap(ptr, old_ptr, new_ptr) ? RMT_TRUE : RMT_FALSE; -#endif -} - -// -// NOTE: Does not guarantee a memory barrier -// TODO: Make sure all platforms don't insert a memory barrier as this is only for stats -// Alternatively, add strong/weak memory order equivalents -// -static rmtS32 AtomicAddS32(rmtAtomicS32* value, rmtS32 add) -{ -#if defined(RMT_PLATFORM_WINDOWS) && !defined(__MINGW32__) - return _InterlockedExchangeAdd((long volatile*)value, (long)add); -#elif defined(RMT_PLATFORM_POSIX) || defined(__MINGW32__) - return __sync_fetch_and_add(value, add); -#endif -} - -static rmtU32 AtomicAddU32(rmtAtomicU32* value, rmtU32 add) -{ -#if defined(RMT_PLATFORM_WINDOWS) && !defined(__MINGW32__) - return (rmtU32)_InterlockedExchangeAdd((long volatile*)value, (long)add); -#elif defined(RMT_PLATFORM_POSIX) || defined(__MINGW32__) - return (rmtU32)__sync_fetch_and_add(value, add); -#endif -} - -static void AtomicSubS32(rmtAtomicS32* value, rmtS32 sub) -{ - // Not all platforms have an implementation so just negate and add - AtomicAddS32(value, -sub); -} - -static void CompilerWriteFence() -{ -#if defined(__clang__) - __asm__ volatile("" : : : "memory"); -#elif defined(RMT_PLATFORM_WINDOWS) && !defined(__MINGW32__) - _WriteBarrier(); -#else - asm volatile("" : : : "memory"); -#endif -} - -static void CompilerReadFence() -{ -#if defined(__clang__) - __asm__ volatile("" : : : "memory"); -#elif defined(RMT_PLATFORM_WINDOWS) && !defined(__MINGW32__) - _ReadBarrier(); -#else - asm volatile("" : : : "memory"); -#endif -} - -static rmtU32 LoadAcquire(rmtAtomicU32* address) -{ - rmtU32 value = *address; - CompilerReadFence(); - return value; -} - -static long* LoadAcquirePointer(long* volatile* ptr) -{ - long* value = *ptr; - CompilerReadFence(); - return value; -} - -static void StoreRelease(rmtAtomicU32* address, rmtU32 value) -{ - CompilerWriteFence(); - *address = value; -} - -static void StoreReleasePointer(long* volatile* ptr, long* value) -{ - CompilerWriteFence(); - *ptr = value; -} - -/* ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- - @RNG: Random Number Generator ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- -*/ - -// -// WELL: Well Equidistributed Long-period Linear -// These algorithms produce numbers with better equidistribution than MT19937 and improve upon "bit-mixing" properties. They are -// fast, come in many sizes, and produce higher quality random numbers. -// -// This implementation has a period of 2^512, or 10^154. -// -// Implementation from: Game Programming Gems 7, Random Number Generation Chris Lomont -// Documentation: http://www.lomont.org/Math/Papers/2008/Lomont_PRNG_2008.pdf -// - -// Global RNG state for now -// Far better than interfering with the user's rand() -#define Well512_StateSize 16 -static rmtU32 Well512_State[Well512_StateSize]; -static rmtU32 Well512_Index; - -static void Well512_Init(rmtU32 seed) -{ - rmtU32 i; - - // Generate initial state from seed - Well512_State[0] = seed; - for (i = 1; i < Well512_StateSize; i++) - { - rmtU32 prev = Well512_State[i - 1]; - Well512_State[i] = (1812433253 * (prev ^ (prev >> 30)) + i); - } - Well512_Index = 0; -} - -static rmtU32 Well512_RandomU32() -{ - rmtU32 a, b, c, d; - - a = Well512_State[Well512_Index]; - c = Well512_State[(Well512_Index + 13) & 15]; - b = a ^ c ^ (a << 16) ^ (c << 15); - c = Well512_State[(Well512_Index + 9) & 15]; - c ^= (c >> 11); - a = Well512_State[Well512_Index] = b ^ c; - d = a ^ ((a << 5) & 0xDA442D24UL); - Well512_Index = (Well512_Index + 15) & 15; - a = Well512_State[Well512_Index]; - Well512_State[Well512_Index] = a ^ b ^ d ^ (a << 2) ^ (b << 18) ^ (c << 28); - return Well512_State[Well512_Index]; -} - -static rmtU32 Well512_RandomOpenLimit(rmtU32 limit) -{ - // Using % to modulo with range is just masking out the higher bits, leaving a result that's objectively biased. - // Dividing by RAND_MAX is better but leads to increased repetition at low ranges due to very large bucket sizes. - // Instead use multiple passes with smaller bucket sizes, rejecting results that don't fit into this smaller range. - rmtU32 bucket_size = UINT_MAX / limit; - rmtU32 bucket_limit = bucket_size * limit; - rmtU32 r; - do - { - r = Well512_RandomU32(); - } while(r >= bucket_limit); - - return r / bucket_size; -} - -/* ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- - @LFSR: Galois Linear-feedback Shift Register ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- -*/ - -static rmtU32 Log2i(rmtU32 x) -{ - static const rmtU32 MultiplyDeBruijnBitPosition[32] = - { - 0, 9, 1, 10, 13, 21, 2, 29, 11, 14, 16, 18, 22, 25, 3, 30, - 8, 12, 20, 28, 15, 17, 24, 7, 19, 27, 23, 6, 26, 5, 4, 31 - }; - - // First round down to one less than a power of two - x |= x >> 1; - x |= x >> 2; - x |= x >> 4; - x |= x >> 8; - x |= x >> 16; - - return MultiplyDeBruijnBitPosition[(rmtU32)(x * 0x07C4ACDDU) >> 27]; -} - -static rmtU32 GaloisLFSRMask(rmtU32 table_size_log2) -{ - // Taps for 4 to 8 bit ranges - static const rmtU32 XORMasks[] = - { - ((1 << 0) | (1 << 1)), // 2 - ((1 << 1) | (1 << 2)), // 3 - ((1 << 2) | (1 << 3)), // 4 - ((1 << 2) | (1 << 4)), // 5 - ((1 << 4) | (1 << 5)), // 6 - ((1 << 5) | (1 << 6)), // 7 - ((1 << 3) | (1 << 4) | (1 << 5) | (1 << 7)), // 8 - }; - - // Map table size to required XOR mask - assert(table_size_log2 >= 2); - assert(table_size_log2 <= 8); - return XORMasks[table_size_log2 - 2]; -} - -static rmtU32 GaloisLFSRNext(rmtU32 value, rmtU32 xor_mask) -{ - // Output bit - rmtU32 lsb = value & 1; - - // Apply the register shift - value >>= 1; - - // Apply toggle mask if the output bit is set - if (lsb != 0) - { - value ^= xor_mask; - } - - return value; -} - -/* ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- - @NEW: New/Delete operators with error values for simplifying object create/destroy ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- -*/ - -#define rmtTryMalloc(type, obj) \ - obj = (type*)rmtMalloc(sizeof(type)); \ - if (obj == NULL) \ - { \ - return RMT_ERROR_MALLOC_FAIL; \ - } - -#define rmtTryMallocArray(type, obj, count) \ - obj = (type*)rmtMalloc((count) * sizeof(type)); \ - if (obj == NULL) \ - { \ - return RMT_ERROR_MALLOC_FAIL; \ - } - -// Ensures the pointer is non-NULL, calls the destructor, frees memory and sets the pointer to NULL -#define rmtDelete(type, obj) \ - if (obj != NULL) \ - { \ - type##_Destructor(obj); \ - rmtFree(obj); \ - obj = NULL; \ - } - -// New will allocate enough space for the object and call the constructor -// If allocation fails the constructor won't be called -// If the constructor fails, the destructor is called and memory is released -// NOTE: Use of sizeof() requires that the type be defined at the point of call -// This is a disadvantage over requiring only a custom Create function -#define rmtTryNew(type, obj, ...) \ - { \ - obj = (type*)rmtMalloc(sizeof(type)); \ - if (obj == NULL) \ - { \ - return RMT_ERROR_MALLOC_FAIL; \ - } \ - rmtError error = type##_Constructor(obj, ##__VA_ARGS__); \ - if (error != RMT_ERROR_NONE) \ - { \ - rmtDelete(type, obj); \ - return error; \ - } \ - } - -/* ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- - @VMBUFFER: Mirror Buffer using Virtual Memory for auto-wrap ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- -*/ - -typedef struct VirtualMirrorBuffer -{ - // Page-rounded size of the buffer without mirroring - rmtU32 size; - - // Pointer to the first part of the mirror - // The second part comes directly after at ptr+size bytes - rmtU8* ptr; - -#ifdef RMT_PLATFORM_WINDOWS -#ifdef _DURANGO - size_t page_count; - size_t* page_mapping; -#else - HANDLE file_map_handle; -#endif -#endif - -} VirtualMirrorBuffer; - -#ifdef __ANDROID__ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include -#include -#include -#include -#include -#define ASHMEM_DEVICE "/dev/ashmem" - -/* - * ashmem_create_region - creates a new ashmem region and returns the file - * descriptor, or <0 on error - * - * `name' is an optional label to give the region (visible in /proc/pid/maps) - * `size' is the size of the region, in page-aligned bytes - */ -int ashmem_create_region(const char* name, size_t size) -{ - int fd, ret; - - fd = open(ASHMEM_DEVICE, O_RDWR); - if (fd < 0) - return fd; - - if (name) - { - char buf[ASHMEM_NAME_LEN] = {0}; - - strncpy(buf, name, sizeof(buf)); - buf[sizeof(buf) - 1] = 0; - ret = ioctl(fd, ASHMEM_SET_NAME, buf); - if (ret < 0) - goto error; - } - - ret = ioctl(fd, ASHMEM_SET_SIZE, size); - if (ret < 0) - goto error; - - return fd; - -error: - close(fd); - return ret; -} -#endif // __ANDROID__ - -static rmtError VirtualMirrorBuffer_Constructor(VirtualMirrorBuffer* buffer, rmtU32 size, int nb_attempts) -{ - static const rmtU32 k_64 = 64 * 1024; - RMT_UNREFERENCED_PARAMETER(nb_attempts); - -#ifdef RMT_PLATFORM_LINUX -#if defined(__FreeBSD__) || defined(__OpenBSD__) - char path[] = "/tmp/ring-buffer-XXXXXX"; -#else - char path[] = "/dev/shm/ring-buffer-XXXXXX"; -#endif - int file_descriptor; -#endif - - // Round up to page-granulation; the nearest 64k boundary for now - size = (size + k_64 - 1) / k_64 * k_64; - - // Set defaults - buffer->size = size; - buffer->ptr = NULL; -#ifdef RMT_PLATFORM_WINDOWS -#ifdef _DURANGO - buffer->page_count = 0; - buffer->page_mapping = NULL; -#else - buffer->file_map_handle = INVALID_HANDLE_VALUE; -#endif -#endif - -#ifdef RMT_PLATFORM_WINDOWS -#ifdef _DURANGO - - // Xbox version based on Windows version and XDK reference - - buffer->page_count = size / k_64; - if (buffer->page_mapping) - { - free(buffer->page_mapping); - } - buffer->page_mapping = (size_t*)malloc(sizeof(ULONG) * buffer->page_count); - - while (nb_attempts-- > 0) - { - rmtU8* desired_addr; - - // Create a page mapping for pointing to its physical address with multiple virtual pages - if (!AllocateTitlePhysicalPages(GetCurrentProcess(), MEM_LARGE_PAGES, &buffer->page_count, - buffer->page_mapping)) - { - free(buffer->page_mapping); - buffer->page_mapping = NULL; - break; - } - - // Reserve two contiguous pages of virtual memory - desired_addr = (rmtU8*)VirtualAlloc(0, size * 2, MEM_RESERVE, PAGE_NOACCESS); - if (desired_addr == NULL) - break; - - // Release the range immediately but retain the address for the next sequence of code to - // try and map to it. In the mean-time some other OS thread may come along and allocate this - // address range from underneath us so multiple attempts need to be made. - VirtualFree(desired_addr, 0, MEM_RELEASE); - - // Immediately try to point both pages at the file mapping - if (MapTitlePhysicalPages(desired_addr, buffer->page_count, MEM_LARGE_PAGES, PAGE_READWRITE, - buffer->page_mapping) == desired_addr && - MapTitlePhysicalPages(desired_addr + size, buffer->page_count, MEM_LARGE_PAGES, PAGE_READWRITE, - buffer->page_mapping) == desired_addr + size) - { - buffer->ptr = desired_addr; - break; - } - - // Failed to map the virtual pages; cleanup and try again - FreeTitlePhysicalPages(GetCurrentProcess(), buffer->page_count, buffer->page_mapping); - buffer->page_mapping = NULL; - } - -#else - - // Windows version based on https://gist.github.com/rygorous/3158316 - - while (nb_attempts-- > 0) - { - rmtU8* desired_addr; - - // Create a file mapping for pointing to its physical address with multiple virtual pages - buffer->file_map_handle = CreateFileMapping(INVALID_HANDLE_VALUE, 0, PAGE_READWRITE, 0, size, 0); - if (buffer->file_map_handle == NULL) - break; - -#ifndef _UWP // NON-UWP Windows Desktop Version - - // Reserve two contiguous pages of virtual memory - desired_addr = (rmtU8*)VirtualAlloc(0, size * 2, MEM_RESERVE, PAGE_NOACCESS); - if (desired_addr == NULL) - break; - - // Release the range immediately but retain the address for the next sequence of code to - // try and map to it. In the mean-time some other OS thread may come along and allocate this - // address range from underneath us so multiple attempts need to be made. - VirtualFree(desired_addr, 0, MEM_RELEASE); - - // Immediately try to point both pages at the file mapping - if (MapViewOfFileEx(buffer->file_map_handle, FILE_MAP_ALL_ACCESS, 0, 0, size, desired_addr) == desired_addr && - MapViewOfFileEx(buffer->file_map_handle, FILE_MAP_ALL_ACCESS, 0, 0, size, desired_addr + size) == - desired_addr + size) - { - buffer->ptr = desired_addr; - break; - } - -#else // UWP - - // Implementation based on example from: - // https://docs.microsoft.com/en-us/windows/desktop/api/memoryapi/nf-memoryapi-virtualalloc2 - // - // Notes - // - just replaced the non-uwp functions by the uwp variants. - // - Both versions could be rewritten to not need the try-loop, see the example mentioned above. I just keep it - // as is for now. - // - Successfully tested on Hololens - desired_addr = (rmtU8*)VirtualAlloc2FromApp(NULL, NULL, 2 * size, MEM_RESERVE | MEM_RESERVE_PLACEHOLDER, - PAGE_NOACCESS, NULL, 0); - - // Split the placeholder region into two regions of equal size. - VirtualFree(desired_addr, size, MEM_RELEASE | MEM_PRESERVE_PLACEHOLDER); - - // Immediately try to point both pages at the file mapping. - if (MapViewOfFile3FromApp(buffer->file_map_handle, NULL, desired_addr, 0, size, MEM_REPLACE_PLACEHOLDER, - PAGE_READWRITE, NULL, 0) == desired_addr && - MapViewOfFile3FromApp(buffer->file_map_handle, NULL, desired_addr + size, 0, size, MEM_REPLACE_PLACEHOLDER, - PAGE_READWRITE, NULL, 0) == desired_addr + size) - { - buffer->ptr = desired_addr; - break; - } -#endif - // Failed to map the virtual pages; cleanup and try again - CloseHandle(buffer->file_map_handle); - buffer->file_map_handle = NULL; - } - -#endif // _XBOX_ONE - -#endif - -#ifdef RMT_PLATFORM_MACOS - - // - // Mac version based on https://github.com/mikeash/MAMirroredQueue - // - // Copyright (c) 2010, Michael Ash - // All rights reserved. - // - // Redistribution and use in source and binary forms, with or without modification, are permitted provided that - // the following conditions are met: - // - // Redistributions of source code must retain the above copyright notice, this list of conditions and the following - // disclaimer. - // - // Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the - // following disclaimer in the documentation and/or other materials provided with the distribution. - // Neither the name of Michael Ash nor the names of its contributors may be used to endorse or promote products - // derived from this software without specific prior written permission. - // - // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED - // WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - // PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, - // INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING - // IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - // - - while (nb_attempts-- > 0) - { - vm_prot_t cur_prot, max_prot; - kern_return_t mach_error; - rmtU8* ptr = NULL; - rmtU8* target = NULL; - - // Allocate 2 contiguous pages of virtual memory - if (vm_allocate(mach_task_self(), (vm_address_t*)&ptr, size * 2, VM_FLAGS_ANYWHERE) != KERN_SUCCESS) - break; - - // Try to deallocate the last page, leaving its virtual memory address free - target = ptr + size; - if (vm_deallocate(mach_task_self(), (vm_address_t)target, size) != KERN_SUCCESS) - { - vm_deallocate(mach_task_self(), (vm_address_t)ptr, size * 2); - break; - } - - // Attempt to remap the page just deallocated to the buffer again - mach_error = vm_remap(mach_task_self(), (vm_address_t*)&target, size, - 0, // mask - 0, // anywhere - mach_task_self(), (vm_address_t)ptr, - 0, // copy - &cur_prot, &max_prot, VM_INHERIT_COPY); - - if (mach_error == KERN_NO_SPACE) - { - // Failed on this pass, cleanup and make another attempt - if (vm_deallocate(mach_task_self(), (vm_address_t)ptr, size) != KERN_SUCCESS) - break; - } - - else if (mach_error == KERN_SUCCESS) - { - // Leave the loop on success - buffer->ptr = ptr; - break; - } - - else - { - // Unknown error, can't recover - vm_deallocate(mach_task_self(), (vm_address_t)ptr, size); - break; - } - } - -#endif - -#ifdef RMT_PLATFORM_LINUX - - // Linux version based on now-defunct Wikipedia section - // http://en.wikipedia.org/w/index.php?title=Circular_buffer&oldid=600431497 - -#ifdef __ANDROID__ - file_descriptor = ashmem_create_region("remotery_shm", size * 2); - if (file_descriptor < 0) - { - return RMT_ERROR_VIRTUAL_MEMORY_BUFFER_FAIL; - } -#else - // Create a unique temporary filename in the shared memory folder - file_descriptor = mkstemp(path); - if (file_descriptor < 0) - return RMT_ERROR_VIRTUAL_MEMORY_BUFFER_FAIL; - - // Delete the name - if (unlink(path)) - return RMT_ERROR_VIRTUAL_MEMORY_BUFFER_FAIL; - - // Set the file size to twice the buffer size - // TODO: this 2x behaviour can be avoided with similar solution to Win/Mac - if (ftruncate(file_descriptor, size * 2)) - return RMT_ERROR_VIRTUAL_MEMORY_BUFFER_FAIL; - -#endif - // Map 2 contiguous pages - buffer->ptr = (rmtU8*)mmap(NULL, size * 2, PROT_NONE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); - if (buffer->ptr == MAP_FAILED) - { - buffer->ptr = NULL; - return RMT_ERROR_VIRTUAL_MEMORY_BUFFER_FAIL; - } - - // Point both pages to the same memory file - if (mmap(buffer->ptr, size, PROT_READ | PROT_WRITE, MAP_FIXED | MAP_SHARED, file_descriptor, 0) != buffer->ptr || - mmap(buffer->ptr + size, size, PROT_READ | PROT_WRITE, MAP_FIXED | MAP_SHARED, file_descriptor, 0) != - buffer->ptr + size) - return RMT_ERROR_VIRTUAL_MEMORY_BUFFER_FAIL; - -#endif - - // Cleanup if exceeded number of attempts or failed - if (buffer->ptr == NULL) - return RMT_ERROR_VIRTUAL_MEMORY_BUFFER_FAIL; - - return RMT_ERROR_NONE; -} - -static void VirtualMirrorBuffer_Destructor(VirtualMirrorBuffer* buffer) -{ - assert(buffer != 0); - -#ifdef RMT_PLATFORM_WINDOWS -#ifdef _DURANGO - if (buffer->page_mapping != NULL) - { - VirtualFree(buffer->ptr, 0, MEM_DECOMMIT); // needed in conjunction with FreeTitlePhysicalPages - FreeTitlePhysicalPages(GetCurrentProcess(), buffer->page_count, buffer->page_mapping); - free(buffer->page_mapping); - buffer->page_mapping = NULL; - } -#else - if (buffer->file_map_handle != NULL) - { - // FIXME, don't we need to unmap the file views obtained in VirtualMirrorBuffer_Constructor, both for - // uwp/non-uwp See example - // https://docs.microsoft.com/en-us/windows/desktop/api/memoryapi/nf-memoryapi-virtualalloc2 - - CloseHandle(buffer->file_map_handle); - buffer->file_map_handle = NULL; - } -#endif -#endif - -#ifdef RMT_PLATFORM_MACOS - if (buffer->ptr != NULL) - vm_deallocate(mach_task_self(), (vm_address_t)buffer->ptr, buffer->size * 2); -#endif - -#ifdef RMT_PLATFORM_LINUX - if (buffer->ptr != NULL) - munmap(buffer->ptr, buffer->size * 2); -#endif - - buffer->ptr = NULL; -} - -/* ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- - @SAFEC: Safe C Library excerpts - http://sourceforge.net/projects/safeclib/ ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- -*/ - -/*------------------------------------------------------------------ - * - * November 2008, Bo Berry - * - * Copyright (c) 2008-2011 by Cisco Systems, Inc - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - *------------------------------------------------------------------ - */ - -// NOTE: Microsoft also has its own version of these functions so I'm do some hacky PP to remove them -#define strnlen_s strnlen_s_safe_c -#define strncat_s strncat_s_safe_c -#define strcpy_s strcpy_s_safe_c - -#define RSIZE_MAX_STR (4UL << 10) /* 4KB */ -#define RCNEGATE(x) x - -#define EOK (0) -#define ESNULLP (400) /* null ptr */ -#define ESZEROL (401) /* length is zero */ -#define ESLEMAX (403) /* length exceeds max */ -#define ESOVRLP (404) /* overlap undefined */ -#define ESNOSPC (406) /* not enough space for s2 */ -#define ESUNTERM (407) /* unterminated string */ -#define ESNOTFND (409) /* not found */ - -#ifndef _ERRNO_T_DEFINED -#define _ERRNO_T_DEFINED -typedef int errno_t; -#endif - -// rsize_t equivalent without going to the hassle of detecting if a platform has implemented C11/K3.2 -typedef unsigned int r_size_t; - -static r_size_t strnlen_s(const char* dest, r_size_t dmax) -{ - r_size_t count; - - if (dest == NULL) - { - return RCNEGATE(0); - } - - if (dmax == 0) - { - return RCNEGATE(0); - } - - if (dmax > RSIZE_MAX_STR) - { - return RCNEGATE(0); - } - - count = 0; - while (*dest && dmax) - { - count++; - dmax--; - dest++; - } - - return RCNEGATE(count); -} - -static errno_t strstr_s(char* dest, r_size_t dmax, const char* src, r_size_t slen, char** substring) -{ - r_size_t len; - r_size_t dlen; - int i; - - if (substring == NULL) - { - return RCNEGATE(ESNULLP); - } - *substring = NULL; - - if (dest == NULL) - { - return RCNEGATE(ESNULLP); - } - - if (dmax == 0) - { - return RCNEGATE(ESZEROL); - } - - if (dmax > RSIZE_MAX_STR) - { - return RCNEGATE(ESLEMAX); - } - - if (src == NULL) - { - return RCNEGATE(ESNULLP); - } - - if (slen == 0) - { - return RCNEGATE(ESZEROL); - } - - if (slen > RSIZE_MAX_STR) - { - return RCNEGATE(ESLEMAX); - } - - /* - * src points to a string with zero length, or - * src equals dest, return dest - */ - if (*src == '\0' || dest == src) - { - *substring = dest; - return RCNEGATE(EOK); - } - - while (*dest && dmax) - { - i = 0; - len = slen; - dlen = dmax; - - while (src[i] && dlen) - { - - /* not a match, not a substring */ - if (dest[i] != src[i]) - { - break; - } - - /* move to the next char */ - i++; - len--; - dlen--; - - if (src[i] == '\0' || !len) - { - *substring = dest; - return RCNEGATE(EOK); - } - } - dest++; - dmax--; - } - - /* - * substring was not found, return NULL - */ - *substring = NULL; - return RCNEGATE(ESNOTFND); -} - -static errno_t strncat_s(char* dest, r_size_t dmax, const char* src, r_size_t slen) -{ - const char* overlap_bumper; - - if (dest == NULL) - { - return RCNEGATE(ESNULLP); - } - - if (src == NULL) - { - return RCNEGATE(ESNULLP); - } - - if (slen > RSIZE_MAX_STR) - { - return RCNEGATE(ESLEMAX); - } - - if (dmax == 0) - { - return RCNEGATE(ESZEROL); - } - - if (dmax > RSIZE_MAX_STR) - { - return RCNEGATE(ESLEMAX); - } - - /* hold base of dest in case src was not copied */ - - if (dest < src) - { - overlap_bumper = src; - - /* Find the end of dest */ - while (*dest != '\0') - { - - if (dest == overlap_bumper) - { - return RCNEGATE(ESOVRLP); - } - - dest++; - dmax--; - if (dmax == 0) - { - return RCNEGATE(ESUNTERM); - } - } - - while (dmax > 0) - { - if (dest == overlap_bumper) - { - return RCNEGATE(ESOVRLP); - } - - /* - * Copying truncated before the source null is encountered - */ - if (slen == 0) - { - *dest = '\0'; - return RCNEGATE(EOK); - } - - *dest = *src; - if (*dest == '\0') - { - return RCNEGATE(EOK); - } - - dmax--; - slen--; - dest++; - src++; - } - } - else - { - overlap_bumper = dest; - - /* Find the end of dest */ - while (*dest != '\0') - { - - /* - * NOTE: no need to check for overlap here since src comes first - * in memory and we're not incrementing src here. - */ - dest++; - dmax--; - if (dmax == 0) - { - return RCNEGATE(ESUNTERM); - } - } - - while (dmax > 0) - { - if (src == overlap_bumper) - { - return RCNEGATE(ESOVRLP); - } - - /* - * Copying truncated - */ - if (slen == 0) - { - *dest = '\0'; - return RCNEGATE(EOK); - } - - *dest = *src; - if (*dest == '\0') - { - return RCNEGATE(EOK); - } - - dmax--; - slen--; - dest++; - src++; - } - } - - /* - * the entire src was not copied, so the string will be nulled. - */ - return RCNEGATE(ESNOSPC); -} - -errno_t strcpy_s(char* dest, r_size_t dmax, const char* src) -{ - const char* overlap_bumper; - - if (dest == NULL) - { - return RCNEGATE(ESNULLP); - } - - if (dmax == 0) - { - return RCNEGATE(ESZEROL); - } - - if (dmax > RSIZE_MAX_STR) - { - return RCNEGATE(ESLEMAX); - } - - if (src == NULL) - { - *dest = '\0'; - return RCNEGATE(ESNULLP); - } - - if (dest == src) - { - return RCNEGATE(EOK); - } - - if (dest < src) - { - overlap_bumper = src; - - while (dmax > 0) - { - if (dest == overlap_bumper) - { - return RCNEGATE(ESOVRLP); - } - - *dest = *src; - if (*dest == '\0') - { - return RCNEGATE(EOK); - } - - dmax--; - dest++; - src++; - } - } - else - { - overlap_bumper = dest; - - while (dmax > 0) - { - if (src == overlap_bumper) - { - return RCNEGATE(ESOVRLP); - } - - *dest = *src; - if (*dest == '\0') - { - return RCNEGATE(EOK); - } - - dmax--; - dest++; - src++; - } - } - - /* - * the entire src must have been copied, if not reset dest - * to null the string. - */ - return RCNEGATE(ESNOSPC); -} - -/* very simple integer to hex */ -static const char* hex_encoding_table = "0123456789ABCDEF"; - -static void itoahex_s(char* dest, r_size_t dmax, rmtS32 value) -{ - r_size_t len; - rmtS32 halfbytepos; - - halfbytepos = 8; - - /* strip leading 0's */ - while (halfbytepos > 1) - { - --halfbytepos; - if (value >> (4 * halfbytepos) & 0xF) - { - ++halfbytepos; - break; - } - } - - len = 0; - while (len + 1 < dmax && halfbytepos > 0) - { - --halfbytepos; - dest[len] = hex_encoding_table[value >> (4 * halfbytepos) & 0xF]; - ++len; - } - - if (len < dmax) - { - dest[len] = 0; - } -} - -static const char* itoa_s(rmtS32 value) -{ - static char temp_dest[12] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; - int pos = 10; - - // Work back with the absolute value - rmtS32 abs_value = abs(value); - while (abs_value > 0) - { - temp_dest[pos--] = '0' + (abs_value % 10); - abs_value /= 10; - } - - // Place the negative - if (value < 0) - { - temp_dest[pos--] = '-'; - } - - return temp_dest + pos + 1; -} - -/* ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- - @OSTHREADS: Wrappers around OS-specific thread functions ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- -*/ - -#ifdef RMT_PLATFORM_WINDOWS -typedef DWORD rmtThreadId; -typedef HANDLE rmtThreadHandle; -#else -typedef uintptr_t rmtThreadId; -typedef pthread_t rmtThreadHandle; -#endif - -#ifdef RMT_PLATFORM_WINDOWS -typedef CONTEXT rmtCpuContext; -#else -typedef int rmtCpuContext; -#endif - -static rmtU32 rmtGetNbProcessors() -{ -#ifdef RMT_PLATFORM_WINDOWS - SYSTEM_INFO system_info; - GetSystemInfo(&system_info); - return system_info.dwNumberOfProcessors; -#else - // TODO: get_nprocs_conf / get_nprocs - return 0; -#endif -} - -static rmtThreadId rmtGetCurrentThreadId() -{ -#ifdef RMT_PLATFORM_WINDOWS - return GetCurrentThreadId(); -#else - return (rmtThreadId)pthread_self(); -#endif -} - -static rmtBool rmtSuspendThread(rmtThreadHandle thread_handle) -{ -#ifdef RMT_PLATFORM_WINDOWS - // SuspendThread is an async call to the scheduler and upon return the thread is not guaranteed to be suspended. - // Calling GetThreadContext will serialise that. - // See: https://github.com/mono/mono/blob/master/mono/utils/mono-threads-windows.c#L203 - return SuspendThread(thread_handle) == 0 ? RMT_TRUE : RMT_FALSE; -#else - return RMT_FALSE; -#endif -} - -static void rmtResumeThread(rmtThreadHandle thread_handle) -{ -#ifdef RMT_PLATFORM_WINDOWS - ResumeThread(thread_handle); -#endif -} - -#ifdef RMT_PLATFORM_WINDOWS -#ifndef CONTEXT_EXCEPTION_REQUEST -// These seem to be guarded by a _AMD64_ macro in winnt.h, which doesn't seem to be defined in older MSVC compilers. -// Which makes sense given this was a post-Vista/Windows 7 patch around errors in the WoW64 context switch. -// This bug was never fixed in the OS so defining these will only get this code to compile on Old Windows systems, with no -// guarantee of being stable at runtime. -#define CONTEXT_EXCEPTION_ACTIVE 0x8000000L -#define CONTEXT_SERVICE_ACTIVE 0x10000000L -#define CONTEXT_EXCEPTION_REQUEST 0x40000000L -#define CONTEXT_EXCEPTION_REPORTING 0x80000000L -#endif -#endif - -static rmtBool rmtGetUserModeThreadContext(rmtThreadHandle thread, rmtCpuContext* context) -{ -#ifdef RMT_PLATFORM_WINDOWS - DWORD kernel_mode_mask; - - // Request thread context with exception reporting - context->ContextFlags = CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_EXCEPTION_REQUEST; - if (GetThreadContext(thread, context) == 0) - { - return RMT_FALSE; - } - - // Context on WoW64 is only valid and can only be set if the thread isn't in kernel mode - // Typical reference to this appears to be: http://zachsaw.blogspot.com/2010/11/wow64-bug-getthreadcontext-may-return.html - // Confirmed by MS here: https://social.msdn.microsoft.com/Forums/vstudio/en-US/aa176c36-6624-4776-9380-1c9cf37a314e/getthreadcontext-returns-stale-register-values-on-wow64?forum=windowscompatibility - kernel_mode_mask = CONTEXT_EXCEPTION_REPORTING | CONTEXT_EXCEPTION_ACTIVE | CONTEXT_SERVICE_ACTIVE; - return (context->ContextFlags & kernel_mode_mask) == CONTEXT_EXCEPTION_REPORTING ? RMT_TRUE : RMT_FALSE; -#else - return RMT_FALSE; -#endif -} - -static void rmtSetThreadContext(rmtThreadHandle thread_handle, rmtCpuContext* context) -{ -#ifdef RMT_PLATFORM_WINDOWS - SetThreadContext(thread_handle, context); -#endif -} - -static rmtError rmtOpenThreadHandle(rmtThreadId thread_id, rmtThreadHandle* out_thread_handle) -{ -#ifdef RMT_PLATFORM_WINDOWS - // Open the thread with required access rights to get the thread handle - *out_thread_handle = OpenThread(THREAD_QUERY_INFORMATION | THREAD_SUSPEND_RESUME | THREAD_SET_CONTEXT | THREAD_GET_CONTEXT, FALSE, thread_id); - if (*out_thread_handle == NULL) - { - return RMT_ERROR_OPEN_THREAD_HANDLE_FAIL; - } -#endif - - return RMT_ERROR_NONE; -} - -static void rmtCloseThreadHandle(rmtThreadHandle thread_handle) -{ -#ifdef RMT_PLATFORM_WINDOWS - if (thread_handle != NULL) - { - CloseHandle(thread_handle); - } -#endif -} - -#ifdef RMT_ENABLE_THREAD_SAMPLER -DWORD_PTR GetThreadStartAddress(rmtThreadHandle thread_handle) -{ - // Get NtQueryInformationThread from ntdll - HMODULE ntdll = GetModuleHandleA("ntdll.dll"); - if (ntdll != NULL) - { - typedef NTSTATUS (WINAPI *NTQUERYINFOMATIONTHREAD)(HANDLE, LONG, PVOID, ULONG, PULONG); - NTQUERYINFOMATIONTHREAD NtQueryInformationThread = (NTQUERYINFOMATIONTHREAD)GetProcAddress(ntdll, "NtQueryInformationThread"); - - // Use it to query the start address - DWORD_PTR start_address; - NTSTATUS status = NtQueryInformationThread(thread_handle, 9, &start_address, sizeof(DWORD), NULL); - if (status == 0) - { - return start_address; - } - } - - return 0; -} - -const char* GetStartAddressModuleName(DWORD_PTR start_address) -{ - BOOL success; - MODULEENTRY32 module_entry; - - // Snapshot the modules - HANDLE handle = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, 0); - if (handle == INVALID_HANDLE_VALUE) - { - return NULL; - } - - module_entry.dwSize = sizeof(MODULEENTRY32); - module_entry.th32ModuleID = 1; - - // Enumerate modules checking start address against their loaded address range - success = Module32First(handle, &module_entry); - while (success == TRUE) - { - if (start_address >= (DWORD_PTR)module_entry.modBaseAddr && start_address <= ((DWORD_PTR)module_entry.modBaseAddr + module_entry.modBaseSize)) - { - static char name[MAX_MODULE_NAME32 + 1]; -#ifdef UNICODE - int size = WideCharToMultiByte(CP_ACP, 0, module_entry.szModule, -1, name, MAX_MODULE_NAME32, NULL, NULL); - if (size < 1) - { - name[0] = '\0'; - } -#else - strcpy_s(name, sizeof(name), module_entry.szModule); -#endif - CloseHandle(handle); - return name; - } - - success = Module32Next(handle, &module_entry); - } - - CloseHandle(handle); - - return NULL; -} -#endif - -static void rmtGetThreadNameFallback(char* out_thread_name, rmtU32 thread_name_size) -{ - // In cases where we can't get a thread name from the OS - static rmtS32 countThreads = 0; - out_thread_name[0] = 0; - strncat_s(out_thread_name, thread_name_size, "Thread", 6); - itoahex_s(out_thread_name + 6, thread_name_size - 6, AtomicAddS32(&countThreads, 1)); -} - -static void rmtGetThreadName(rmtThreadId thread_id, rmtThreadHandle thread_handle, char* out_thread_name, rmtU32 thread_name_size) -{ -#ifdef RMT_PLATFORM_WINDOWS - DWORD_PTR address; - const char* module_name; - rmtU32 len; - - // Use the new Windows 10 GetThreadDescription function - HMODULE kernel32 = GetModuleHandleA("Kernel32.dll"); - if (kernel32 != NULL) - { - typedef HRESULT(WINAPI* GETTHREADDESCRIPTION)(HANDLE hThread, PWSTR *ppszThreadDescription); - GETTHREADDESCRIPTION GetThreadDescription = (GETTHREADDESCRIPTION)GetProcAddress(kernel32, "GetThreadDescription"); - if (GetThreadDescription != NULL) - { - int size; - - WCHAR* thread_name_w; - GetThreadDescription(thread_handle, &thread_name_w); - - // Returned size is the byte size, so will be 1 for a null-terminated strings - size = WideCharToMultiByte(CP_ACP, 0, thread_name_w, -1, out_thread_name, thread_name_size, NULL, NULL); - if (size > 1) - { - return; - } - } - } - - #ifndef _XBOX_ONE - // At this point GetThreadDescription hasn't returned anything so let's get the thread module name and use that - address = GetThreadStartAddress(thread_handle); - if (address == 0) - { - rmtGetThreadNameFallback(out_thread_name, thread_name_size); - return; - } - module_name = GetStartAddressModuleName(address); - if (module_name == NULL) - { - rmtGetThreadNameFallback(out_thread_name, thread_name_size); - return; - } - #else - rmtGetThreadNameFallback(out_thread_name, thread_name_size); - return; - #endif - - // Concatenate thread name with then thread ID as that will be unique, whereas the start address won't be - memset(out_thread_name, 0, thread_name_size); - strcpy_s(out_thread_name, thread_name_size, module_name); - strncat_s(out_thread_name, thread_name_size, "!", 1); - len = strnlen_s(out_thread_name, thread_name_size); - itoahex_s(out_thread_name + len, thread_name_size - len, thread_id); - -#elif defined(RMT_PLATFORM_LINUX) && RMT_USE_POSIX_THREADNAMES && !defined(__FreeBSD__) && !defined(__OpenBSD__) - - prctl(PR_GET_NAME, out_thread_name, 0, 0, 0); - -#else - - rmtGetThreadNameFallback(out_thread_name, thread_name_size); - -#endif -} - -/* ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- - @THREADS: Cross-platform thread object ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- -*/ - -typedef struct Thread_t rmtThread; -typedef rmtError (*ThreadProc)(rmtThread* thread); - -struct Thread_t -{ - rmtThreadHandle handle; - - // Callback executed when the thread is created - ThreadProc callback; - - // Caller-specified parameter passed to Thread_Create - void* param; - - // Error state returned from callback - rmtError error; - - // External threads can set this to request an exit - volatile rmtBool request_exit; -}; - -#if defined(RMT_PLATFORM_WINDOWS) - -static DWORD WINAPI ThreadProcWindows(LPVOID lpParameter) -{ - rmtThread* thread = (rmtThread*)lpParameter; - assert(thread != NULL); - thread->error = thread->callback(thread); - return thread->error == RMT_ERROR_NONE ? 0 : 1; -} - -#else -static void* StartFunc(void* pArgs) -{ - rmtThread* thread = (rmtThread*)pArgs; - assert(thread != NULL); - thread->error = thread->callback(thread); - return NULL; // returned error not use, check thread->error. -} -#endif - -static int rmtThread_Valid(rmtThread* thread) -{ - assert(thread != NULL); - -#if defined(RMT_PLATFORM_WINDOWS) - return thread->handle != NULL; -#else - return !pthread_equal(thread->handle, pthread_self()); -#endif -} - -static rmtError rmtThread_Constructor(rmtThread* thread, ThreadProc callback, void* param) -{ - assert(thread != NULL); - - thread->callback = callback; - thread->param = param; - thread->error = RMT_ERROR_NONE; - thread->request_exit = RMT_FALSE; - - // OS-specific thread creation - -#if defined(RMT_PLATFORM_WINDOWS) - - thread->handle = CreateThread(NULL, // lpThreadAttributes - 0, // dwStackSize - ThreadProcWindows, // lpStartAddress - thread, // lpParameter - 0, // dwCreationFlags - NULL); // lpThreadId - - if (thread->handle == NULL) - return RMT_ERROR_CREATE_THREAD_FAIL; - -#else - - int32_t error = pthread_create(&thread->handle, NULL, StartFunc, thread); - if (error) - { - // Contents of 'thread' parameter to pthread_create() are undefined after - // failure call so can't pre-set to invalid value before hand. - thread->handle = pthread_self(); - return RMT_ERROR_CREATE_THREAD_FAIL; - } - -#endif - - return RMT_ERROR_NONE; -} - -static void rmtThread_RequestExit(rmtThread* thread) -{ - // Not really worried about memory barriers or delayed visibility to the target thread - assert(thread != NULL); - thread->request_exit = RMT_TRUE; -} - -static void rmtThread_Join(rmtThread* thread) -{ - assert(rmtThread_Valid(thread)); - -#if defined(RMT_PLATFORM_WINDOWS) - WaitForSingleObject(thread->handle, INFINITE); -#else - pthread_join(thread->handle, NULL); -#endif -} - -static void rmtThread_Destructor(rmtThread* thread) -{ - assert(thread != NULL); - - if (rmtThread_Valid(thread)) - { - // Shutdown the thread - rmtThread_RequestExit(thread); - rmtThread_Join(thread); - - // OS-specific release of thread resources - -#if defined(RMT_PLATFORM_WINDOWS) - CloseHandle(thread->handle); - thread->handle = NULL; -#endif - } -} - -/* ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- - @OBJALLOC: Reusable Object Allocator ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- -*/ - -// -// All objects that require free-list-backed allocation need to inherit from this type. -// -typedef struct ObjectLink_s -{ - struct ObjectLink_s* volatile next; -} ObjectLink; - -static void ObjectLink_Constructor(ObjectLink* link) -{ - assert(link != NULL); - link->next = NULL; -} - -typedef rmtError (*ObjConstructor)(void*); -typedef void (*ObjDestructor)(void*); - -typedef struct -{ - // Object create/destroy parameters - rmtU32 object_size; - ObjConstructor constructor; - ObjDestructor destructor; - - // Number of objects in the free list - rmtAtomicS32 nb_free; - - // Number of objects used by callers - rmtAtomicS32 nb_inuse; - - // Total allocation count - rmtAtomicS32 nb_allocated; - - ObjectLink* first_free; -} ObjectAllocator; - -static rmtError ObjectAllocator_Constructor(ObjectAllocator* allocator, rmtU32 object_size, ObjConstructor constructor, - ObjDestructor destructor) -{ - allocator->object_size = object_size; - allocator->constructor = constructor; - allocator->destructor = destructor; - allocator->nb_free = 0; - allocator->nb_inuse = 0; - allocator->nb_allocated = 0; - allocator->first_free = NULL; - return RMT_ERROR_NONE; -} - -static void ObjectAllocator_Destructor(ObjectAllocator* allocator) -{ - // Ensure everything has been released to the allocator - assert(allocator != NULL); - assert(allocator->nb_inuse == 0); - - // Destroy all objects released to the allocator - while (allocator->first_free != NULL) - { - ObjectLink* next = allocator->first_free->next; - assert(allocator->destructor != NULL); - allocator->destructor(allocator->first_free); - rmtFree(allocator->first_free); - allocator->first_free = next; - } -} - -static void ObjectAllocator_Push(ObjectAllocator* allocator, ObjectLink* start, ObjectLink* end) -{ - assert(allocator != NULL); - assert(start != NULL); - assert(end != NULL); - - // CAS pop add range to the front of the list - for (;;) - { - ObjectLink* old_link = (ObjectLink*)allocator->first_free; - end->next = old_link; - if (AtomicCompareAndSwapPointer((long* volatile*)&allocator->first_free, (long*)old_link, (long*)start) == - RMT_TRUE) - break; - } -} - -static ObjectLink* ObjectAllocator_Pop(ObjectAllocator* allocator) -{ - ObjectLink* link; - - assert(allocator != NULL); - - // CAS pop from the front of the list - for (;;) - { - ObjectLink* old_link = (ObjectLink*)allocator->first_free; - if (old_link == NULL) - { - return NULL; - } - ObjectLink* next_link = old_link->next; - if (AtomicCompareAndSwapPointer((long* volatile*)&allocator->first_free, (long*)old_link, (long*)next_link) == - RMT_TRUE) - { - link = old_link; - break; - } - } - - link->next = NULL; - - return link; -} - -static rmtError ObjectAllocator_Alloc(ObjectAllocator* allocator, void** object) -{ - // This function only calls the object constructor on initial malloc of an object - - assert(allocator != NULL); - assert(object != NULL); - - // Pull available objects from the free list - *object = ObjectAllocator_Pop(allocator); - - // Has the free list run out? - if (*object == NULL) - { - rmtError error; - - // Allocate/construct a new object - *object = rmtMalloc(allocator->object_size); - if (*object == NULL) - return RMT_ERROR_MALLOC_FAIL; - assert(allocator->constructor != NULL); - error = allocator->constructor(*object); - if (error != RMT_ERROR_NONE) - { - // Auto-teardown on failure - assert(allocator->destructor != NULL); - allocator->destructor(*object); - rmtFree(*object); - return error; - } - - AtomicAddS32(&allocator->nb_allocated, 1); - } - else - { - AtomicSubS32(&allocator->nb_free, 1); - } - - AtomicAddS32(&allocator->nb_inuse, 1); - - return RMT_ERROR_NONE; -} - -static void ObjectAllocator_Free(ObjectAllocator* allocator, void* object) -{ - // Add back to the free-list - assert(allocator != NULL); - ObjectAllocator_Push(allocator, (ObjectLink*)object, (ObjectLink*)object); - AtomicSubS32(&allocator->nb_inuse, 1); - AtomicAddS32(&allocator->nb_free, 1); -} - -static void ObjectAllocator_FreeRange(ObjectAllocator* allocator, void* start, void* end, rmtU32 count) -{ - assert(allocator != NULL); - ObjectAllocator_Push(allocator, (ObjectLink*)start, (ObjectLink*)end); - AtomicSubS32(&allocator->nb_inuse, count); - AtomicAddS32(&allocator->nb_free, count); -} - -/* ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- - @DYNBUF: Dynamic Buffer ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- -*/ - -typedef struct -{ - rmtU32 alloc_granularity; - - rmtU32 bytes_allocated; - rmtU32 bytes_used; - - rmtU8* data; -} Buffer; - -static rmtError Buffer_Constructor(Buffer* buffer, rmtU32 alloc_granularity) -{ - assert(buffer != NULL); - buffer->alloc_granularity = alloc_granularity; - buffer->bytes_allocated = 0; - buffer->bytes_used = 0; - buffer->data = NULL; - return RMT_ERROR_NONE; -} - -static void Buffer_Destructor(Buffer* buffer) -{ - assert(buffer != NULL); - - if (buffer->data != NULL) - { - rmtFree(buffer->data); - buffer->data = NULL; - } -} - -static rmtError Buffer_Grow(Buffer* buffer, rmtU32 length) -{ - // Calculate size increase rounded up to the requested allocation granularity - rmtU32 granularity = buffer->alloc_granularity; - rmtU32 allocate = buffer->bytes_allocated + length; - allocate = allocate + ((granularity - 1) - ((allocate - 1) % granularity)); - - buffer->bytes_allocated = allocate; - buffer->data = (rmtU8*)rmtRealloc(buffer->data, buffer->bytes_allocated); - if (buffer->data == NULL) - return RMT_ERROR_MALLOC_FAIL; - - return RMT_ERROR_NONE; -} - -static rmtError Buffer_Pad(Buffer* buffer, rmtU32 length) -{ - assert(buffer != NULL); - - // Reallocate the buffer on overflow - if (buffer->bytes_used + length > buffer->bytes_allocated) - { - rmtTry(Buffer_Grow(buffer, length)); - } - - // Step by the pad amount - buffer->bytes_used += length; - - return RMT_ERROR_NONE; -} - -static rmtError Buffer_AlignedPad(Buffer* buffer, rmtU32 start_pos) -{ - return Buffer_Pad(buffer, (4 - ((buffer->bytes_used - start_pos) & 3)) & 3); -} - -static rmtError Buffer_Write(Buffer* buffer, const void* data, rmtU32 length) -{ - assert(buffer != NULL); - - // Reallocate the buffer on overflow - if (buffer->bytes_used + length > buffer->bytes_allocated) - { - rmtTry(Buffer_Grow(buffer, length)); - } - - // Copy all bytes - memcpy(buffer->data + buffer->bytes_used, data, length); - buffer->bytes_used += length; - - return RMT_ERROR_NONE; -} - -static rmtError Buffer_WriteStringZ(Buffer* buffer, rmtPStr string) -{ - assert(string != NULL); - return Buffer_Write(buffer, (void*)string, (rmtU32)strnlen_s(string, 2048) + 1); -} - -static void U32ToByteArray(rmtU8* dest, rmtU32 value) -{ - // Commit as little-endian - dest[0] = value & 255; - dest[1] = (value >> 8) & 255; - dest[2] = (value >> 16) & 255; - dest[3] = value >> 24; -} - -static rmtError Buffer_WriteBool(Buffer* buffer, rmtBool value) -{ - return Buffer_Write(buffer, &value, 1); -} - -static rmtError Buffer_WriteU32(Buffer* buffer, rmtU32 value) -{ - assert(buffer != NULL); - - // Reallocate the buffer on overflow - if (buffer->bytes_used + sizeof(value) > buffer->bytes_allocated) - { - rmtTry(Buffer_Grow(buffer, sizeof(value))); - } - -// Copy all bytes -#if RMT_ASSUME_LITTLE_ENDIAN - *(rmtU32*)(buffer->data + buffer->bytes_used) = value; -#else - U32ToByteArray(buffer->data + buffer->bytes_used, value); -#endif - - buffer->bytes_used += sizeof(value); - - return RMT_ERROR_NONE; -} - -static rmtBool IsLittleEndian() -{ - // Not storing this in a global variable allows the compiler to more easily optimise - // this away altogether. - union { - unsigned int i; - unsigned char c[sizeof(unsigned int)]; - } u; - u.i = 1; - return u.c[0] == 1 ? RMT_TRUE : RMT_FALSE; -} - -static rmtError Buffer_WriteF64(Buffer* buffer, rmtF64 value) -{ - assert(buffer != NULL); - - // Reallocate the buffer on overflow - if (buffer->bytes_used + sizeof(value) > buffer->bytes_allocated) - { - rmtTry(Buffer_Grow(buffer, sizeof(value))); - } - -// Copy all bytes -#if RMT_ASSUME_LITTLE_ENDIAN - *(rmtF64*)(buffer->data + buffer->bytes_used) = value; -#else - { - union { - double d; - unsigned char c[sizeof(double)]; - } u; - rmtU8* dest = buffer->data + buffer->bytes_used; - u.d = value; - if (IsLittleEndian()) - { - dest[0] = u.c[0]; - dest[1] = u.c[1]; - dest[2] = u.c[2]; - dest[3] = u.c[3]; - dest[4] = u.c[4]; - dest[5] = u.c[5]; - dest[6] = u.c[6]; - dest[7] = u.c[7]; - } - else - { - dest[0] = u.c[7]; - dest[1] = u.c[6]; - dest[2] = u.c[5]; - dest[3] = u.c[4]; - dest[4] = u.c[3]; - dest[5] = u.c[2]; - dest[6] = u.c[1]; - dest[7] = u.c[0]; - } - } -#endif - - buffer->bytes_used += sizeof(value); - - return RMT_ERROR_NONE; -} - -static rmtError Buffer_WriteU64(Buffer* buffer, rmtU64 value) -{ - // Write as a double as Javascript DataView doesn't have a 64-bit integer read - return Buffer_WriteF64(buffer, (double)value); -} - -static rmtError Buffer_WriteStringWithLength(Buffer* buffer, rmtPStr string) -{ - rmtU32 length = (rmtU32)strnlen_s(string, 2048); - rmtTry(Buffer_WriteU32(buffer, length)); - return Buffer_Write(buffer, (void*)string, length); -} - -/* ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- - @HASHTABLE: Integer pair hash map for inserts/finds. No removes for added simplicity. ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- -*/ - -#define RMT_NOT_FOUND 0xffffffffffffffff - -typedef struct -{ - // Non-zero, pre-hashed key - rmtU32 key; - - // Value that's not equal to RMT_NOT_FOUND - rmtU64 value; -} HashSlot; - -typedef struct -{ - // Stats - rmtU32 maxNbSlots; - rmtU32 nbSlots; - - // Data - HashSlot* slots; -} rmtHashTable; - -static rmtError rmtHashTable_Constructor(rmtHashTable* table, rmtU32 max_nb_slots) -{ - // Default initialise - assert(table != NULL); - table->maxNbSlots = max_nb_slots; - table->nbSlots = 0; - - // Allocate and clear the hash slots - rmtTryMallocArray(HashSlot, table->slots, table->maxNbSlots); - memset(table->slots, 0, table->maxNbSlots * sizeof(HashSlot)); - - return RMT_ERROR_NONE; -} - -static void rmtHashTable_Destructor(rmtHashTable* table) -{ - assert(table != NULL); - - if (table->slots != NULL) - { - rmtFree(table->slots); - table->slots = NULL; - } -} - -static rmtError rmtHashTable_Resize(rmtHashTable* table); - -static rmtError rmtHashTable_Insert(rmtHashTable* table, rmtU32 key, rmtU64 value) -{ - HashSlot* slot = NULL; - rmtError error = RMT_ERROR_NONE; - - // Calculate initial slot location for this key - rmtU32 index_mask = table->maxNbSlots - 1; - rmtU32 index = key & index_mask; - - assert(key != 0); - assert(value != RMT_NOT_FOUND); - - // Linear probe for free slot, reusing any existing key matches - // There will always be at least one free slot due to load factor management - while (table->slots[index].key) - { - if (table->slots[index].key == key) - { - // Counter occupied slot increments below - table->nbSlots--; - break; - } - - index = (index + 1) & index_mask; - } - - // Just verify that I've got no errors in the code above - assert(index < table->maxNbSlots); - - // Add to the table - slot = table->slots + index; - slot->key = key; - slot->value = value; - table->nbSlots++; - - // Resize when load factor is greater than 2/3 - if (table->nbSlots > (table->maxNbSlots * 2) / 3) - { - error = rmtHashTable_Resize(table); - } - - return error; -} - -static rmtError rmtHashTable_Resize(rmtHashTable* table) -{ - rmtU32 old_max_nb_slots = table->maxNbSlots; - HashSlot* new_slots = NULL; - HashSlot* old_slots = table->slots; - rmtU32 i; - - // Increase the table size - rmtU32 new_max_nb_slots = table->maxNbSlots; - if (new_max_nb_slots < 8192 * 4) - { - new_max_nb_slots *= 4; - } - else - { - new_max_nb_slots *= 2; - } - - // Allocate and clear a new table - rmtTryMallocArray(HashSlot, new_slots, new_max_nb_slots); - memset(new_slots, 0, new_max_nb_slots * sizeof(HashSlot)); - - // Update fields of the table after successful allocation only - table->slots = new_slots; - table->maxNbSlots = new_max_nb_slots; - table->nbSlots = 0; - - // Reinsert all objects into the new table - for (i = 0; i < old_max_nb_slots; i++) - { - HashSlot* slot = old_slots + i; - if (slot->key != 0) - { - rmtHashTable_Insert(table, slot->key, slot->value); - } - } - - rmtFree(old_slots); - - return RMT_ERROR_NONE; -} - -static rmtU64 rmtHashTable_Find(rmtHashTable* table, rmtU32 key) -{ - // Calculate initial slot location for this key - rmtU32 index_mask = table->maxNbSlots - 1; - rmtU32 index = key & index_mask; - - // Linear probe for matching hash - while (table->slots[index].key) - { - HashSlot* slot = table->slots + index; - - if (slot->key == key) - { - return slot->value; - } - - index = (index + 1) & index_mask; - } - - return RMT_NOT_FOUND; -} - -/* ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- - @STRINGTABLE: Map from string hash to string offset in local buffer ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- -*/ - -typedef struct -{ - // Growable dynamic array of strings added so far - Buffer* text; - - // Map from text hash to text location in the buffer - rmtHashTable* text_map; -} StringTable; - -static rmtError StringTable_Constructor(StringTable* table) -{ - // Default initialise - assert(table != NULL); - table->text = NULL; - table->text_map = NULL; - - // Allocate reasonably storage for initial sample names - rmtTryNew(Buffer, table->text, 8 * 1024); - rmtTryNew(rmtHashTable, table->text_map, 1 * 1024); - - return RMT_ERROR_NONE; -} - -static void StringTable_Destructor(StringTable* table) -{ - assert(table != NULL); - - rmtDelete(rmtHashTable, table->text_map); - rmtDelete(Buffer, table->text); -} - -static rmtPStr StringTable_Find(StringTable* table, rmtU32 name_hash) -{ - rmtU64 text_offset = rmtHashTable_Find(table->text_map, name_hash); - if (text_offset != RMT_NOT_FOUND) - { - return (rmtPStr)(table->text->data + text_offset); - } - return NULL; -} - -static rmtBool StringTable_Insert(StringTable* table, rmtU32 name_hash, rmtPStr name) -{ - // Only add to the buffer if the string isn't already there - rmtU64 text_offset = rmtHashTable_Find(table->text_map, name_hash); - if (text_offset == RMT_NOT_FOUND) - { - // TODO: Allocation errors aren't being passed on to the caller - text_offset = table->text->bytes_used; - Buffer_WriteStringZ(table->text, name); - rmtHashTable_Insert(table->text_map, name_hash, text_offset); - return RMT_TRUE; - } - - return RMT_FALSE; -} - -/* ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- - @SOCKETS: Sockets TCP/IP Wrapper ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- -*/ - -#ifndef RMT_PLATFORM_WINDOWS -typedef int SOCKET; -#define INVALID_SOCKET -1 -#define SOCKET_ERROR -1 -#define SD_SEND SHUT_WR -#define closesocket close -#endif - -typedef struct -{ - SOCKET socket; -} TCPSocket; - -typedef struct -{ - rmtBool can_read; - rmtBool can_write; - rmtError error_state; -} SocketStatus; - -// -// Function prototypes -// -static void TCPSocket_Close(TCPSocket* tcp_socket); - -static rmtError InitialiseNetwork() -{ -#ifdef RMT_PLATFORM_WINDOWS - - WSADATA wsa_data; - if (WSAStartup(MAKEWORD(2, 2), &wsa_data)) - { - return rmtMakeError(RMT_ERROR_RESOURCE_CREATE_FAIL, "WSAStartup failed"); - } - if (LOBYTE(wsa_data.wVersion) != 2 || HIBYTE(wsa_data.wVersion) != 2) - { - return rmtMakeError(RMT_ERROR_RESOURCE_CREATE_FAIL, "WSAStartup returned incorrect version number"); - } - - return RMT_ERROR_NONE; - -#else - - return RMT_ERROR_NONE; - -#endif -} - -static void ShutdownNetwork() -{ -#ifdef RMT_PLATFORM_WINDOWS - WSACleanup(); -#endif -} - -static rmtError TCPSocket_Constructor(TCPSocket* tcp_socket) -{ - assert(tcp_socket != NULL); - tcp_socket->socket = INVALID_SOCKET; - return InitialiseNetwork(); -} - -static void TCPSocket_Destructor(TCPSocket* tcp_socket) -{ - assert(tcp_socket != NULL); - TCPSocket_Close(tcp_socket); - ShutdownNetwork(); -} - -static rmtError TCPSocket_RunServer(TCPSocket* tcp_socket, rmtU16 port, rmtBool reuse_open_port, - rmtBool limit_connections_to_localhost) -{ - SOCKET s = INVALID_SOCKET; - struct sockaddr_in sin; -#ifdef RMT_PLATFORM_WINDOWS - u_long nonblock = 1; -#endif - - memset(&sin, 0, sizeof(sin)); - assert(tcp_socket != NULL); - - // Try to create the socket - s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); - if (s == SOCKET_ERROR) - { - return rmtMakeError(RMT_ERROR_RESOURCE_CREATE_FAIL, "Can't create a socket for connection to the remote viewer"); - } - - if (reuse_open_port) - { - int enable = 1; - -// set SO_REUSEADDR so binding doesn't fail when restarting the application -// (otherwise the same port can't be reused within TIME_WAIT) -// I'm not checking for errors because if this fails (unlikely) we might still -// be able to bind to the socket anyway -#ifdef RMT_PLATFORM_POSIX - setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable)); -#elif defined(RMT_PLATFORM_WINDOWS) - // windows also needs SO_EXCLUSEIVEADDRUSE, - // see http://www.andy-pearce.com/blog/posts/2013/Feb/so_reuseaddr-on-windows/ - setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char*)&enable, sizeof(enable)); - enable = 1; - setsockopt(s, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, (char*)&enable, sizeof(enable)); -#endif - } - - // Bind the socket to the incoming port - sin.sin_family = AF_INET; - sin.sin_addr.s_addr = htonl(limit_connections_to_localhost ? INADDR_LOOPBACK : INADDR_ANY); - sin.sin_port = htons(port); - if (bind(s, (struct sockaddr*)&sin, sizeof(sin)) == SOCKET_ERROR) - { - return rmtMakeError(RMT_ERROR_RESOURCE_ACCESS_FAIL, "Can't bind a socket for the server"); - } - - // Connection is valid, remaining code is socket state modification - tcp_socket->socket = s; - - // Enter a listening state with a backlog of 1 connection - if (listen(s, 1) == SOCKET_ERROR) - { - return rmtMakeError(RMT_ERROR_RESOURCE_ACCESS_FAIL, "Created server socket failed to enter a listen state"); - } - -// Set as non-blocking -#ifdef RMT_PLATFORM_WINDOWS - if (ioctlsocket(tcp_socket->socket, FIONBIO, &nonblock) == SOCKET_ERROR) - { - return rmtMakeError(RMT_ERROR_RESOURCE_ACCESS_FAIL, "Created server socket failed to switch to a non-blocking state"); - } -#else - if (fcntl(tcp_socket->socket, F_SETFL, O_NONBLOCK) == SOCKET_ERROR) - { - return rmtMakeError(RMT_ERROR_RESOURCE_ACCESS_FAIL, "Created server socket failed to switch to a non-blocking state"); - } -#endif - - return RMT_ERROR_NONE; -} - -static void TCPSocket_Close(TCPSocket* tcp_socket) -{ - assert(tcp_socket != NULL); - - if (tcp_socket->socket != INVALID_SOCKET) - { - // Shutdown the connection, stopping all sends - int result = shutdown(tcp_socket->socket, SD_SEND); - if (result != SOCKET_ERROR) - { - // Keep receiving until the peer closes the connection - int total = 0; - char temp_buf[128]; - while (result > 0) - { - result = (int)recv(tcp_socket->socket, temp_buf, sizeof(temp_buf), 0); - total += result; - } - } - - // Close the socket and issue a network shutdown request - closesocket(tcp_socket->socket); - tcp_socket->socket = INVALID_SOCKET; - } -} - -static SocketStatus TCPSocket_PollStatus(TCPSocket* tcp_socket) -{ - SocketStatus status; - fd_set fd_read, fd_write, fd_errors; - struct timeval tv; - - status.can_read = RMT_FALSE; - status.can_write = RMT_FALSE; - status.error_state = RMT_ERROR_NONE; - - assert(tcp_socket != NULL); - if (tcp_socket->socket == INVALID_SOCKET) - { - status.error_state = RMT_ERROR_SOCKET_INVALID_POLL; - return status; - } - - // Set read/write/error markers for the socket - FD_ZERO(&fd_read); - FD_ZERO(&fd_write); - FD_ZERO(&fd_errors); -#ifdef _MSC_VER -#pragma warning(push) -#pragma warning(disable : 4127) // warning C4127: conditional expression is constant -#endif // _MSC_VER - FD_SET(tcp_socket->socket, &fd_read); - FD_SET(tcp_socket->socket, &fd_write); - FD_SET(tcp_socket->socket, &fd_errors); -#ifdef _MSC_VER -#pragma warning(pop) -#endif // _MSC_VER - - // Poll socket status without blocking - tv.tv_sec = 0; - tv.tv_usec = 0; - if (select(((int)tcp_socket->socket) + 1, &fd_read, &fd_write, &fd_errors, &tv) == SOCKET_ERROR) - { - status.error_state = RMT_ERROR_SOCKET_SELECT_FAIL; - return status; - } - - status.can_read = FD_ISSET(tcp_socket->socket, &fd_read) != 0 ? RMT_TRUE : RMT_FALSE; - status.can_write = FD_ISSET(tcp_socket->socket, &fd_write) != 0 ? RMT_TRUE : RMT_FALSE; - status.error_state = FD_ISSET(tcp_socket->socket, &fd_errors) != 0 ? RMT_ERROR_SOCKET_POLL_ERRORS : RMT_ERROR_NONE; - return status; -} - -static rmtError TCPSocket_AcceptConnection(TCPSocket* tcp_socket, TCPSocket** client_socket) -{ - SocketStatus status; - SOCKET s; - - // Ensure there is an incoming connection - assert(tcp_socket != NULL); - status = TCPSocket_PollStatus(tcp_socket); - if (status.error_state != RMT_ERROR_NONE || !status.can_read) - return status.error_state; - - // Accept the connection - s = accept(tcp_socket->socket, 0, 0); - if (s == SOCKET_ERROR) - { - return rmtMakeError(RMT_ERROR_RESOURCE_CREATE_FAIL, "Server failed to accept connection from client"); - } - -#ifdef SO_NOSIGPIPE - // On POSIX systems, send() may send a SIGPIPE signal when writing to an - // already closed connection. By setting this option, we prevent the - // signal from being emitted and send will instead return an error and set - // errno to EPIPE. - // - // This is supported on BSD platforms and not on Linux. - { - int flag = 1; - setsockopt(s, SOL_SOCKET, SO_NOSIGPIPE, &flag, sizeof(flag)); - } -#endif - // Create a client socket for the new connection - assert(client_socket != NULL); - rmtTryNew(TCPSocket, *client_socket); - (*client_socket)->socket = s; - - return RMT_ERROR_NONE; -} - -static int TCPTryAgain() -{ -#ifdef RMT_PLATFORM_WINDOWS - DWORD error = WSAGetLastError(); - return error == WSAEWOULDBLOCK; -#else -#if EAGAIN == EWOULDBLOCK - return errno == EAGAIN; -#else - return errno == EAGAIN || errno == EWOULDBLOCK; -#endif -#endif -} - -static rmtError TCPSocket_Send(TCPSocket* tcp_socket, const void* data, rmtU32 length, rmtU32 timeout_ms) -{ - SocketStatus status; - char* cur_data = NULL; - char* end_data = NULL; - rmtU32 start_ms = 0; - rmtU32 cur_ms = 0; - - assert(tcp_socket != NULL); - - start_ms = msTimer_Get(); - - // Loop until timeout checking whether data can be written - status.can_write = RMT_FALSE; - while (!status.can_write) - { - status = TCPSocket_PollStatus(tcp_socket); - if (status.error_state != RMT_ERROR_NONE) - return status.error_state; - - cur_ms = msTimer_Get(); - if (cur_ms - start_ms > timeout_ms) - { - return rmtMakeError(RMT_ERROR_TIMEOUT, "Timed out trying to send data"); - } - } - - cur_data = (char*)data; - end_data = cur_data + length; - - while (cur_data < end_data) - { - // Attempt to send the remaining chunk of data - int bytes_sent; - int send_flags = 0; -#ifdef MSG_NOSIGNAL - // On Linux this prevents send from emitting a SIGPIPE signal - // Equivalent on BSD to the SO_NOSIGPIPE option. - send_flags = MSG_NOSIGNAL; -#endif - bytes_sent = (int)send(tcp_socket->socket, cur_data, (int)(end_data - cur_data), send_flags); - - if (bytes_sent == SOCKET_ERROR || bytes_sent == 0) - { - // Close the connection if sending fails for any other reason other than blocking - if (bytes_sent != 0 && !TCPTryAgain()) - return RMT_ERROR_SOCKET_SEND_FAIL; - - // First check for tick-count overflow and reset, giving a slight hitch every 49.7 days - cur_ms = msTimer_Get(); - if (cur_ms < start_ms) - { - start_ms = cur_ms; - continue; - } - - // - // Timeout can happen when: - // - // 1) endpoint is no longer there - // 2) endpoint can't consume quick enough - // 3) local buffers overflow - // - // As none of these are actually errors, we have to pass this timeout back to the caller. - // - // TODO: This strategy breaks down if a send partially completes and then times out! - // - if (cur_ms - start_ms > timeout_ms) - { - return rmtMakeError(RMT_ERROR_TIMEOUT, "Timed out trying to send data"); - } - } - else - { - // Jump over the data sent - cur_data += bytes_sent; - } - } - - return RMT_ERROR_NONE; -} - -static rmtError TCPSocket_Receive(TCPSocket* tcp_socket, void* data, rmtU32 length, rmtU32 timeout_ms) -{ - SocketStatus status; - char* cur_data = NULL; - char* end_data = NULL; - rmtU32 start_ms = 0; - rmtU32 cur_ms = 0; - - assert(tcp_socket != NULL); - - // Ensure there is data to receive - status = TCPSocket_PollStatus(tcp_socket); - if (status.error_state != RMT_ERROR_NONE) - return status.error_state; - if (!status.can_read) - return RMT_ERROR_SOCKET_RECV_NO_DATA; - - cur_data = (char*)data; - end_data = cur_data + length; - - // Loop until all data has been received - start_ms = msTimer_Get(); - while (cur_data < end_data) - { - int bytes_received = (int)recv(tcp_socket->socket, cur_data, (int)(end_data - cur_data), 0); - - if (bytes_received == SOCKET_ERROR || bytes_received == 0) - { - // Close the connection if receiving fails for any other reason other than blocking - if (bytes_received != 0 && !TCPTryAgain()) - return RMT_ERROR_SOCKET_RECV_FAILED; - - // First check for tick-count overflow and reset, giving a slight hitch every 49.7 days - cur_ms = msTimer_Get(); - if (cur_ms < start_ms) - { - start_ms = cur_ms; - continue; - } - - // - // Timeout can happen when: - // - // 1) data is delayed by sender - // 2) sender fails to send a complete set of packets - // - // As not all of these scenarios are errors, we need to pass this information back to the caller. - // - // TODO: This strategy breaks down if a receive partially completes and then times out! - // - if (cur_ms - start_ms > timeout_ms) - { - return RMT_ERROR_SOCKET_RECV_TIMEOUT; - } - } - else - { - // Jump over the data received - cur_data += bytes_received; - } - } - - return RMT_ERROR_NONE; -} - -/* ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- - @SHA1: SHA-1 Cryptographic Hash Function ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- -*/ - -// -// Typed to allow enforced data size specification -// -typedef struct -{ - rmtU8 data[20]; -} SHA1; - -/* - Copyright (c) 2011, Micael Hildenborg - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of Micael Hildenborg nor the - names of its contributors may be used to endorse or promote products - derived from this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY Micael Hildenborg ''AS IS'' AND ANY - EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL Micael Hildenborg BE LIABLE FOR ANY - DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/* - Contributors: - Gustav - Several members in the gamedev.se forum. - Gregory Petrosyan - */ - -// Rotate an integer value to left. -static unsigned int rol(const unsigned int value, const unsigned int steps) -{ - return ((value << steps) | (value >> (32 - steps))); -} - -// Sets the first 16 integers in the buffert to zero. -// Used for clearing the W buffert. -static void clearWBuffert(unsigned int* buffert) -{ - int pos; - for (pos = 16; --pos >= 0;) - { - buffert[pos] = 0; - } -} - -static void innerHash(unsigned int* result, unsigned int* w) -{ - unsigned int a = result[0]; - unsigned int b = result[1]; - unsigned int c = result[2]; - unsigned int d = result[3]; - unsigned int e = result[4]; - - int round = 0; - -#define sha1macro(func, val) \ - { \ - const unsigned int t = rol(a, 5) + (func) + e + val + w[round]; \ - e = d; \ - d = c; \ - c = rol(b, 30); \ - b = a; \ - a = t; \ - } - - while (round < 16) - { - sha1macro((b & c) | (~b & d), 0x5a827999); - ++round; - } - while (round < 20) - { - w[round] = rol((w[round - 3] ^ w[round - 8] ^ w[round - 14] ^ w[round - 16]), 1); - sha1macro((b & c) | (~b & d), 0x5a827999); - ++round; - } - while (round < 40) - { - w[round] = rol((w[round - 3] ^ w[round - 8] ^ w[round - 14] ^ w[round - 16]), 1); - sha1macro(b ^ c ^ d, 0x6ed9eba1); - ++round; - } - while (round < 60) - { - w[round] = rol((w[round - 3] ^ w[round - 8] ^ w[round - 14] ^ w[round - 16]), 1); - sha1macro((b & c) | (b & d) | (c & d), 0x8f1bbcdc); - ++round; - } - while (round < 80) - { - w[round] = rol((w[round - 3] ^ w[round - 8] ^ w[round - 14] ^ w[round - 16]), 1); - sha1macro(b ^ c ^ d, 0xca62c1d6); - ++round; - } - -#undef sha1macro - - result[0] += a; - result[1] += b; - result[2] += c; - result[3] += d; - result[4] += e; -} - -static void calc(const void* src, const int bytelength, unsigned char* hash) -{ - int roundPos; - int lastBlockBytes; - int hashByte; - - // Init the result array. - unsigned int result[5] = {0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0}; - - // Cast the void src pointer to be the byte array we can work with. - const unsigned char* sarray = (const unsigned char*)src; - - // The reusable round buffer - unsigned int w[80]; - - // Loop through all complete 64byte blocks. - const int endOfFullBlocks = bytelength - 64; - int endCurrentBlock; - int currentBlock = 0; - - while (currentBlock <= endOfFullBlocks) - { - endCurrentBlock = currentBlock + 64; - - // Init the round buffer with the 64 byte block data. - for (roundPos = 0; currentBlock < endCurrentBlock; currentBlock += 4) - { - // This line will swap endian on big endian and keep endian on little endian. - w[roundPos++] = (unsigned int)sarray[currentBlock + 3] | (((unsigned int)sarray[currentBlock + 2]) << 8) | - (((unsigned int)sarray[currentBlock + 1]) << 16) | - (((unsigned int)sarray[currentBlock]) << 24); - } - innerHash(result, w); - } - - // Handle the last and not full 64 byte block if existing. - endCurrentBlock = bytelength - currentBlock; - clearWBuffert(w); - lastBlockBytes = 0; - for (; lastBlockBytes < endCurrentBlock; ++lastBlockBytes) - { - w[lastBlockBytes >> 2] |= (unsigned int)sarray[lastBlockBytes + currentBlock] - << ((3 - (lastBlockBytes & 3)) << 3); - } - w[lastBlockBytes >> 2] |= 0x80U << ((3 - (lastBlockBytes & 3)) << 3); - if (endCurrentBlock >= 56) - { - innerHash(result, w); - clearWBuffert(w); - } - w[15] = bytelength << 3; - innerHash(result, w); - - // Store hash in result pointer, and make sure we get in in the correct order on both endian models. - for (hashByte = 20; --hashByte >= 0;) - { - hash[hashByte] = (result[hashByte >> 2] >> (((3 - hashByte) & 0x3) << 3)) & 0xff; - } -} - -static SHA1 SHA1_Calculate(const void* src, unsigned int length) -{ - SHA1 hash; - assert((int)length >= 0); - calc(src, length, hash.data); - return hash; -} - -/* ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- - @BASE64: Base-64 encoder ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- -*/ - -static const char* b64_encoding_table = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz" - "0123456789+/"; - -static rmtU32 Base64_CalculateEncodedLength(rmtU32 length) -{ - // ceil(l * 4/3) - return 4 * ((length + 2) / 3); -} - -static void Base64_Encode(const rmtU8* in_bytes, rmtU32 length, rmtU8* out_bytes) -{ - rmtU32 i; - rmtU32 encoded_length; - rmtU32 remaining_bytes; - - rmtU8* optr = out_bytes; - - for (i = 0; i < length;) - { - // Read input 3 values at a time, null terminating - rmtU32 c0 = i < length ? in_bytes[i++] : 0; - rmtU32 c1 = i < length ? in_bytes[i++] : 0; - rmtU32 c2 = i < length ? in_bytes[i++] : 0; - - // Encode 4 bytes for ever 3 input bytes - rmtU32 triple = (c0 << 0x10) + (c1 << 0x08) + c2; - *optr++ = b64_encoding_table[(triple >> 3 * 6) & 0x3F]; - *optr++ = b64_encoding_table[(triple >> 2 * 6) & 0x3F]; - *optr++ = b64_encoding_table[(triple >> 1 * 6) & 0x3F]; - *optr++ = b64_encoding_table[(triple >> 0 * 6) & 0x3F]; - } - - // Pad output to multiple of 3 bytes with terminating '=' - encoded_length = Base64_CalculateEncodedLength(length); - remaining_bytes = (3 - ((length + 2) % 3)) - 1; - for (i = 0; i < remaining_bytes; i++) - out_bytes[encoded_length - 1 - i] = '='; - - // Null terminate - out_bytes[encoded_length] = 0; -} - -/* ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- - @MURMURHASH: MurmurHash3 - https://code.google.com/p/smhasher ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- -*/ - -//----------------------------------------------------------------------------- -// MurmurHash3 was written by Austin Appleby, and is placed in the public -// domain. The author hereby disclaims copyright to this source code. -//----------------------------------------------------------------------------- - -#if RMT_USE_INTERNAL_HASH_FUNCTION - -static rmtU32 rotl32(rmtU32 x, rmtS8 r) -{ - return (x << r) | (x >> (32 - r)); -} - -// Block read - if your platform needs to do endian-swapping, do the conversion here -static rmtU32 getblock32(const rmtU32* p, int i) -{ - rmtU32 result; - const rmtU8* src = ((const rmtU8*)p) + i * (int)sizeof(rmtU32); - memcpy(&result, src, sizeof(result)); - return result; -} - -// Finalization mix - force all bits of a hash block to avalanche -static rmtU32 fmix32(rmtU32 h) -{ - h ^= h >> 16; - h *= 0x85ebca6b; - h ^= h >> 13; - h *= 0xc2b2ae35; - h ^= h >> 16; - return h; -} - -static rmtU32 MurmurHash3_x86_32(const void* key, int len, rmtU32 seed) -{ - const rmtU8* data = (const rmtU8*)key; - const int nblocks = len / 4; - - rmtU32 h1 = seed; - - const rmtU32 c1 = 0xcc9e2d51; - const rmtU32 c2 = 0x1b873593; - - int i; - - const rmtU32* blocks = (const rmtU32*)(data + nblocks * 4); - const rmtU8* tail = (const rmtU8*)(data + nblocks * 4); - - rmtU32 k1 = 0; - - //---------- - // body - - for (i = -nblocks; i; i++) - { - rmtU32 k2 = getblock32(blocks, i); - - k2 *= c1; - k2 = rotl32(k2, 15); - k2 *= c2; - - h1 ^= k2; - h1 = rotl32(h1, 13); - h1 = h1 * 5 + 0xe6546b64; - } - - //---------- - // tail - - switch (len & 3) - { - case 3: - k1 ^= tail[2] << 16; // fallthrough - case 2: - k1 ^= tail[1] << 8; // fallthrough - case 1: - k1 ^= tail[0]; - k1 *= c1; - k1 = rotl32(k1, 15); - k1 *= c2; - h1 ^= k1; - }; - - //---------- - // finalization - - h1 ^= len; - - h1 = fmix32(h1); - - return h1; -} - -RMT_API rmtU32 _rmt_HashString32(const char* s, int len, rmtU32 seed) -{ - return MurmurHash3_x86_32(s, len, seed); -} - -#else - #if defined(__cplusplus) - extern "C" - #endif - RMT_API rmtU32 _rmt_HashString32(const char* s, int len, rmtU32 seed); - -#endif // RMT_USE_INTERNAL_HASH_FUNCTION - -/* ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- - @WEBSOCKETS: WebSockets ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- -*/ - -enum WebSocketMode -{ - WEBSOCKET_NONE = 0, - WEBSOCKET_TEXT = 1, - WEBSOCKET_BINARY = 2, -}; - -typedef struct -{ - TCPSocket* tcp_socket; - - enum WebSocketMode mode; - - rmtU32 frame_bytes_remaining; - rmtU32 mask_offset; - - union { - rmtU8 mask[4]; - rmtU32 mask_u32; - } data; - -} WebSocket; - -static void WebSocket_Close(WebSocket* web_socket); - -static char* GetField(char* buffer, r_size_t buffer_length, rmtPStr field_name) -{ - char* field = NULL; - char* buffer_end = buffer + buffer_length - 1; - - r_size_t field_length = strnlen_s(field_name, buffer_length); - if (field_length == 0) - return NULL; - - // Search for the start of the field - if (strstr_s(buffer, buffer_length, field_name, field_length, &field) != EOK) - return NULL; - - // Field name is now guaranteed to be in the buffer so its safe to jump over it without hitting the bounds - field += strlen(field_name); - - // Skip any trailing whitespace - while (*field == ' ') - { - if (field >= buffer_end) - return NULL; - field++; - } - - return field; -} - -static const char websocket_guid[] = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; -static const char websocket_response[] = "HTTP/1.1 101 Switching Protocols\r\n" - "Upgrade: websocket\r\n" - "Connection: Upgrade\r\n" - "Sec-WebSocket-Accept: "; - -static rmtError WebSocketHandshake(TCPSocket* tcp_socket, rmtPStr limit_host) -{ - rmtU32 start_ms, now_ms; - - // Parsing scratchpad - char buffer[1024]; - char* buffer_ptr = buffer; - int buffer_len = sizeof(buffer) - 1; - char* buffer_end = buffer + buffer_len; - - char response_buffer[256]; - int response_buffer_len = sizeof(response_buffer) - 1; - - char* version; - char* host; - char* key; - char* key_end; - SHA1 hash; - - assert(tcp_socket != NULL); - - start_ms = msTimer_Get(); - - // Really inefficient way of receiving the handshake data from the browser - // Not really sure how to do this any better, as the termination requirement is \r\n\r\n - while (buffer_ptr - buffer < buffer_len) - { - rmtError error = TCPSocket_Receive(tcp_socket, buffer_ptr, 1, 20); - if (error == RMT_ERROR_SOCKET_RECV_FAILED) - return error; - - // If there's a stall receiving the data, check for a handshake timeout - if (error == RMT_ERROR_SOCKET_RECV_NO_DATA || error == RMT_ERROR_SOCKET_RECV_TIMEOUT) - { - now_ms = msTimer_Get(); - if (now_ms - start_ms > 1000) - return RMT_ERROR_SOCKET_RECV_TIMEOUT; - - continue; - } - - // Just in case new enums are added... - assert(error == RMT_ERROR_NONE); - - if (buffer_ptr - buffer >= 4) - { - if (*(buffer_ptr - 3) == '\r' && *(buffer_ptr - 2) == '\n' && *(buffer_ptr - 1) == '\r' && - *(buffer_ptr - 0) == '\n') - break; - } - - buffer_ptr++; - } - *buffer_ptr = 0; - - // HTTP GET instruction - if (memcmp(buffer, "GET", 3) != 0) - return RMT_ERROR_WEBSOCKET_HANDSHAKE_NOT_GET; - - // Look for the version number and verify that it's supported - version = GetField(buffer, buffer_len, "Sec-WebSocket-Version:"); - if (version == NULL) - return RMT_ERROR_WEBSOCKET_HANDSHAKE_NO_VERSION; - if (buffer_end - version < 2 || (version[0] != '8' && (version[0] != '1' || version[1] != '3'))) - return RMT_ERROR_WEBSOCKET_HANDSHAKE_BAD_VERSION; - - // Make sure this connection comes from a known host - host = GetField(buffer, buffer_len, "Host:"); - if (host == NULL) - return RMT_ERROR_WEBSOCKET_HANDSHAKE_NO_HOST; - if (limit_host != NULL) - { - r_size_t limit_host_len = strnlen_s(limit_host, 128); - char* found = NULL; - if (strstr_s(host, (r_size_t)(buffer_end - host), limit_host, limit_host_len, &found) != EOK) - return RMT_ERROR_WEBSOCKET_HANDSHAKE_BAD_HOST; - } - - // Look for the key start and null-terminate it within the receive buffer - key = GetField(buffer, buffer_len, "Sec-WebSocket-Key:"); - if (key == NULL) - return RMT_ERROR_WEBSOCKET_HANDSHAKE_NO_KEY; - if (strstr_s(key, (r_size_t)(buffer_end - key), "\r\n", 2, &key_end) != EOK) - return RMT_ERROR_WEBSOCKET_HANDSHAKE_BAD_KEY; - *key_end = 0; - - // Concatenate the browser's key with the WebSocket Protocol GUID and base64 encode - // the hash, to prove to the browser that this is a bonafide WebSocket server - buffer[0] = 0; - if (strncat_s(buffer, buffer_len, key, (r_size_t)(key_end - key)) != EOK) - return RMT_ERROR_WEBSOCKET_HANDSHAKE_STRING_FAIL; - if (strncat_s(buffer, buffer_len, websocket_guid, sizeof(websocket_guid)) != EOK) - return RMT_ERROR_WEBSOCKET_HANDSHAKE_STRING_FAIL; - hash = SHA1_Calculate(buffer, (rmtU32)strnlen_s(buffer, buffer_len)); - Base64_Encode(hash.data, sizeof(hash.data), (rmtU8*)buffer); - - // Send the response back to the server with a longer timeout than usual - response_buffer[0] = 0; - if (strncat_s(response_buffer, response_buffer_len, websocket_response, sizeof(websocket_response)) != EOK) - return RMT_ERROR_WEBSOCKET_HANDSHAKE_STRING_FAIL; - if (strncat_s(response_buffer, response_buffer_len, buffer, buffer_len) != EOK) - return RMT_ERROR_WEBSOCKET_HANDSHAKE_STRING_FAIL; - if (strncat_s(response_buffer, response_buffer_len, "\r\n\r\n", 4) != EOK) - return RMT_ERROR_WEBSOCKET_HANDSHAKE_STRING_FAIL; - - return TCPSocket_Send(tcp_socket, response_buffer, (rmtU32)strnlen_s(response_buffer, response_buffer_len), 1000); -} - -static rmtError WebSocket_Constructor(WebSocket* web_socket, TCPSocket* tcp_socket) -{ - rmtError error = RMT_ERROR_NONE; - - assert(web_socket != NULL); - web_socket->tcp_socket = tcp_socket; - web_socket->mode = WEBSOCKET_NONE; - web_socket->frame_bytes_remaining = 0; - web_socket->mask_offset = 0; - web_socket->data.mask[0] = 0; - web_socket->data.mask[1] = 0; - web_socket->data.mask[2] = 0; - web_socket->data.mask[3] = 0; - - // Caller can optionally specify which TCP socket to use - if (web_socket->tcp_socket == NULL) - rmtTryNew(TCPSocket, web_socket->tcp_socket); - - return error; -} - -static void WebSocket_Destructor(WebSocket* web_socket) -{ - WebSocket_Close(web_socket); -} - -static rmtError WebSocket_RunServer(WebSocket* web_socket, rmtU16 port, rmtBool reuse_open_port, - rmtBool limit_connections_to_localhost, enum WebSocketMode mode) -{ - // Create the server's listening socket - assert(web_socket != NULL); - web_socket->mode = mode; - return TCPSocket_RunServer(web_socket->tcp_socket, port, reuse_open_port, limit_connections_to_localhost); -} - -static void WebSocket_Close(WebSocket* web_socket) -{ - assert(web_socket != NULL); - rmtDelete(TCPSocket, web_socket->tcp_socket); -} - -static SocketStatus WebSocket_PollStatus(WebSocket* web_socket) -{ - assert(web_socket != NULL); - return TCPSocket_PollStatus(web_socket->tcp_socket); -} - -static rmtError WebSocket_AcceptConnection(WebSocket* web_socket, WebSocket** client_socket) -{ - TCPSocket* tcp_socket = NULL; - - // Is there a waiting connection? - assert(web_socket != NULL); - rmtTry(TCPSocket_AcceptConnection(web_socket->tcp_socket, &tcp_socket)); - if (tcp_socket == NULL) - return RMT_ERROR_NONE; - - // Need a successful handshake between client/server before allowing the connection - // TODO: Specify limit_host - rmtTry(WebSocketHandshake(tcp_socket, NULL)); - - // Allocate and return a new client socket - assert(client_socket != NULL); - rmtTryNew(WebSocket, *client_socket, tcp_socket); - - (*client_socket)->mode = web_socket->mode; - - return RMT_ERROR_NONE; -} - -static void WriteSize(rmtU32 size, rmtU8* dest, rmtU32 dest_size, rmtU32 dest_offset) -{ - int size_size = dest_size - dest_offset; - rmtU32 i; - for (i = 0; i < dest_size; i++) - { - int j = i - dest_offset; - dest[i] = (j < 0) ? 0 : (size >> ((size_size - j - 1) * 8)) & 0xFF; - } -} - -// For send buffers to preallocate -#define WEBSOCKET_MAX_FRAME_HEADER_SIZE 10 - -static void WebSocket_PrepareBuffer(Buffer* buffer) -{ - char empty_frame_header[WEBSOCKET_MAX_FRAME_HEADER_SIZE]; - - assert(buffer != NULL); - - // Reset to start - buffer->bytes_used = 0; - - // Allocate enough space for a maximum-sized frame header - Buffer_Write(buffer, empty_frame_header, sizeof(empty_frame_header)); -} - -static rmtU32 WebSocket_FrameHeaderSize(rmtU32 length) -{ - if (length <= 125) - return 2; - if (length <= 65535) - return 4; - return 10; -} - -static void WebSocket_WriteFrameHeader(WebSocket* web_socket, rmtU8* dest, rmtU32 length) -{ - rmtU8 final_fragment = 0x1 << 7; - rmtU8 frame_type = (rmtU8)web_socket->mode; - - dest[0] = final_fragment | frame_type; - - // Construct the frame header, correctly applying the narrowest size - if (length <= 125) - { - dest[1] = (rmtU8)length; - } - else if (length <= 65535) - { - dest[1] = 126; - WriteSize(length, dest + 2, 2, 0); - } - else - { - dest[1] = 127; - WriteSize(length, dest + 2, 8, 4); - } -} - -static rmtError WebSocket_Send(WebSocket* web_socket, const void* data, rmtU32 length, rmtU32 timeout_ms) -{ - rmtError error; - SocketStatus status; - rmtU32 payload_length, frame_header_size, delta; - - assert(web_socket != NULL); - assert(data != NULL); - - // Can't send if there are socket errors - status = WebSocket_PollStatus(web_socket); - if (status.error_state != RMT_ERROR_NONE) - return status.error_state; - - // Assume space for max frame header has been allocated in the incoming data - payload_length = length - WEBSOCKET_MAX_FRAME_HEADER_SIZE; - frame_header_size = WebSocket_FrameHeaderSize(payload_length); - delta = WEBSOCKET_MAX_FRAME_HEADER_SIZE - frame_header_size; - data = (void*)((rmtU8*)data + delta); - length -= delta; - WebSocket_WriteFrameHeader(web_socket, (rmtU8*)data, payload_length); - - // Send frame header and data together - error = TCPSocket_Send(web_socket->tcp_socket, data, length, timeout_ms); - return error; -} - -static rmtError ReceiveFrameHeader(WebSocket* web_socket) -{ - // TODO: Specify infinite timeout? - - rmtU8 msg_header[2] = {0, 0}; - int msg_length, size_bytes_remaining, i; - rmtBool mask_present; - - assert(web_socket != NULL); - - // Get message header - rmtTry(TCPSocket_Receive(web_socket->tcp_socket, msg_header, 2, 20)); - - // Check for WebSocket Protocol disconnect - if (msg_header[0] == 0x88) - return RMT_ERROR_WEBSOCKET_DISCONNECTED; - - // Check that the client isn't sending messages we don't understand - if (msg_header[0] != 0x81 && msg_header[0] != 0x82) - return RMT_ERROR_WEBSOCKET_BAD_FRAME_HEADER; - - // Get message length and check to see if it's a marker for a wider length - msg_length = msg_header[1] & 0x7F; - size_bytes_remaining = 0; - switch (msg_length) - { - case 126: - size_bytes_remaining = 2; - break; - case 127: - size_bytes_remaining = 8; - break; - } - - if (size_bytes_remaining > 0) - { - // Receive the wider bytes of the length - rmtU8 size_bytes[8]; - rmtTry(TCPSocket_Receive(web_socket->tcp_socket, size_bytes, size_bytes_remaining, 20)); - - // Calculate new length, MSB first - msg_length = 0; - for (i = 0; i < size_bytes_remaining; i++) - msg_length |= size_bytes[i] << ((size_bytes_remaining - 1 - i) * 8); - } - - // Receive any message data masks - mask_present = (msg_header[1] & 0x80) != 0 ? RMT_TRUE : RMT_FALSE; - if (mask_present) - { - rmtTry(TCPSocket_Receive(web_socket->tcp_socket, web_socket->data.mask, 4, 20)); - } - - web_socket->frame_bytes_remaining = msg_length; - web_socket->mask_offset = 0; - - return RMT_ERROR_NONE; -} - -static rmtError WebSocket_Receive(WebSocket* web_socket, void* data, rmtU32* msg_len, rmtU32 length, rmtU32 timeout_ms) -{ - SocketStatus status; - char* cur_data; - char* end_data; - rmtU32 start_ms, now_ms; - rmtU32 bytes_to_read; - rmtError error; - - assert(web_socket != NULL); - - // Can't read with any socket errors - status = WebSocket_PollStatus(web_socket); - if (status.error_state != RMT_ERROR_NONE) - return status.error_state; - - cur_data = (char*)data; - end_data = cur_data + length; - - start_ms = msTimer_Get(); - while (cur_data < end_data) - { - // Get next WebSocket frame if we've run out of data to read from the socket - if (web_socket->frame_bytes_remaining == 0) - { - rmtTry(ReceiveFrameHeader(web_socket)); - - // Set output message length only on initial receive - if (msg_len != NULL) - *msg_len = web_socket->frame_bytes_remaining; - } - - // Read as much required data as possible - bytes_to_read = web_socket->frame_bytes_remaining < length ? web_socket->frame_bytes_remaining : length; - error = TCPSocket_Receive(web_socket->tcp_socket, cur_data, bytes_to_read, 20); - if (error == RMT_ERROR_SOCKET_RECV_FAILED) - return error; - - // If there's a stall receiving the data, check for timeout - if (error == RMT_ERROR_SOCKET_RECV_NO_DATA || error == RMT_ERROR_SOCKET_RECV_TIMEOUT) - { - now_ms = msTimer_Get(); - if (now_ms - start_ms > timeout_ms) - return RMT_ERROR_SOCKET_RECV_TIMEOUT; - continue; - } - - // Apply data mask - if (web_socket->data.mask_u32 != 0) - { - rmtU32 i; - for (i = 0; i < bytes_to_read; i++) - { - *((rmtU8*)cur_data + i) ^= web_socket->data.mask[web_socket->mask_offset & 3]; - web_socket->mask_offset++; - } - } - - cur_data += bytes_to_read; - web_socket->frame_bytes_remaining -= bytes_to_read; - } - - return RMT_ERROR_NONE; -} - -/* ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- - @MESSAGEQ: Multiple producer, single consumer message queue ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- -*/ - -typedef enum MessageID -{ - MsgID_NotReady, - MsgID_AddToStringTable, - MsgID_LogText, - MsgID_SampleTree, - MsgID_ProcessorThreads, - MsgID_None, - MsgID_PropertySnapshot, - MsgID_Force32Bits = 0xFFFFFFFF, -} MessageID; - -typedef struct Message -{ - MessageID id; - - rmtU32 payload_size; - - // For telling which thread the message came from in the debugger - struct ThreadProfiler* threadProfiler; - - rmtU8 payload[1]; -} Message; - -// Multiple producer, single consumer message queue that uses its own data buffer -// to store the message data. -typedef struct rmtMessageQueue -{ - rmtU32 size; - - // The physical address of this data buffer is pointed to by two sequential - // virtual memory pages, allowing automatic wrap-around of any reads or writes - // that exceed the limits of the buffer. - VirtualMirrorBuffer* data; - - // Read/write position never wrap allowing trivial overflow checks - // with easier debugging - rmtAtomicU32 read_pos; - rmtAtomicU32 write_pos; - -} rmtMessageQueue; - -static rmtError rmtMessageQueue_Constructor(rmtMessageQueue* queue, rmtU32 size) -{ - assert(queue != NULL); - - // Set defaults - queue->size = 0; - queue->data = NULL; - queue->read_pos = 0; - queue->write_pos = 0; - - rmtTryNew(VirtualMirrorBuffer, queue->data, size, 10); - - // The mirror buffer needs to be page-aligned and will change the requested - // size to match that. - queue->size = queue->data->size; - - // Set the entire buffer to not ready message - memset(queue->data->ptr, MsgID_NotReady, queue->size); - - return RMT_ERROR_NONE; -} - -static void rmtMessageQueue_Destructor(rmtMessageQueue* queue) -{ - assert(queue != NULL); - rmtDelete(VirtualMirrorBuffer, queue->data); -} - -static rmtU32 rmtMessageQueue_SizeForPayload(rmtU32 payload_size) -{ - // Add message header and align for ARM platforms - rmtU32 size = sizeof(Message) + payload_size; -#if defined(RMT_ARCH_64BIT) - size = (size + 7) & ~7U; -#else - size = (size + 3) & ~3U; -#endif - return size; -} - -static Message* rmtMessageQueue_AllocMessage(rmtMessageQueue* queue, rmtU32 payload_size, - struct ThreadProfiler* thread_profiler) -{ - Message* msg; - - rmtU32 write_size = rmtMessageQueue_SizeForPayload(payload_size); - - assert(queue != NULL); - - for (;;) - { - // Check for potential overflow - // Order of loads means allocation failure can happen when enough space has just been freed - // However, incorrect overflows are not possible - rmtU32 s = queue->size; - rmtU32 w = LoadAcquire(&queue->write_pos); - rmtU32 r = LoadAcquire(&queue->read_pos); - if ((int)(w - r) > ((int)(s - write_size))) - return NULL; - - // Point to the newly allocated space - msg = (Message*)(queue->data->ptr + (w & (s - 1))); - - // Increment the write position, leaving the loop if this is the thread that succeeded - if (AtomicCompareAndSwapU32(&queue->write_pos, w, w + write_size) == RMT_TRUE) - { - // Safe to set payload size after thread claims ownership of this allocated range - msg->payload_size = payload_size; - msg->threadProfiler = thread_profiler; - break; - } - } - - return msg; -} - -static void rmtMessageQueue_CommitMessage(Message* message, MessageID id) -{ - assert(message != NULL); - - // Setting the message ID signals to the consumer that the message is ready - assert(LoadAcquire((rmtU32*)&message->id) == MsgID_NotReady); - StoreRelease((rmtU32*)&message->id, id); -} - -Message* rmtMessageQueue_PeekNextMessage(rmtMessageQueue* queue) -{ - Message* ptr; - rmtU32 r, w; - MessageID id; - - assert(queue != NULL); - - // First check that there are bytes queued - w = LoadAcquire(&queue->write_pos); - r = queue->read_pos; - if (w - r == 0) - return NULL; - - // Messages are in the queue but may not have been commit yet - // Messages behind this one may have been commit but it's not reachable until - // the next one in the queue is ready. - r = r & (queue->size - 1); - ptr = (Message*)(queue->data->ptr + r); - id = (MessageID)LoadAcquire((rmtU32*)&ptr->id); - if (id != MsgID_NotReady) - return ptr; - - return NULL; -} - -static void rmtMessageQueue_ConsumeNextMessage(rmtMessageQueue* queue, Message* message) -{ - rmtU32 message_size, read_pos; - - assert(queue != NULL); - assert(message != NULL); - - // Setting the message ID to "not ready" serves as a marker to the consumer that even though - // space has been allocated for a message, the message isn't ready to be consumed - // yet. - // - // We can't do that when allocating the message because multiple threads will be fighting for - // the same location. Instead, clear out any messages just read by the consumer before advancing - // the read position so that a winning thread's allocation will inherit the "not ready" state. - // - // This costs some write bandwidth and has the potential to flush cache to other cores. - message_size = rmtMessageQueue_SizeForPayload(message->payload_size); - memset(message, MsgID_NotReady, message_size); - - // Advance read position - read_pos = queue->read_pos + message_size; - StoreRelease(&queue->read_pos, read_pos); -} - -/* ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- - @NETWORK: Network Server ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- -*/ - -typedef rmtError (*Server_ReceiveHandler)(void*, char*, rmtU32); - -typedef struct -{ - WebSocket* listen_socket; - - WebSocket* client_socket; - - rmtU32 last_ping_time; - - rmtU16 port; - - rmtBool reuse_open_port; - rmtBool limit_connections_to_localhost; - - // A dynamically-sized buffer used for binary-encoding messages and sending to the client - Buffer* bin_buf; - - // Handler for receiving messages from the client - Server_ReceiveHandler receive_handler; - void* receive_handler_context; -} Server; - -static rmtError Server_CreateListenSocket(Server* server, rmtU16 port, rmtBool reuse_open_port, - rmtBool limit_connections_to_localhost) -{ - rmtTryNew(WebSocket, server->listen_socket, NULL); - rmtTry(WebSocket_RunServer(server->listen_socket, port, reuse_open_port, limit_connections_to_localhost, WEBSOCKET_BINARY)); - return RMT_ERROR_NONE; -} - -static rmtError Server_Constructor(Server* server, rmtU16 port, rmtBool reuse_open_port, - rmtBool limit_connections_to_localhost) -{ - assert(server != NULL); - server->listen_socket = NULL; - server->client_socket = NULL; - server->last_ping_time = 0; - server->port = port; - server->reuse_open_port = reuse_open_port; - server->limit_connections_to_localhost = limit_connections_to_localhost; - server->bin_buf = NULL; - server->receive_handler = NULL; - server->receive_handler_context = NULL; - - // Create the binary serialisation buffer - rmtTryNew(Buffer, server->bin_buf, 4096); - - // Create the listening WebSocket - return Server_CreateListenSocket(server, port, reuse_open_port, limit_connections_to_localhost); -} - -static void Server_Destructor(Server* server) -{ - assert(server != NULL); - rmtDelete(WebSocket, server->client_socket); - rmtDelete(WebSocket, server->listen_socket); - rmtDelete(Buffer, server->bin_buf); -} - -static rmtBool Server_IsClientConnected(Server* server) -{ - assert(server != NULL); - return server->client_socket != NULL ? RMT_TRUE : RMT_FALSE; -} - -static void Server_DisconnectClient(Server* server) -{ - WebSocket* client_socket; - - assert(server != NULL); - - // NULL the variable before destroying the socket - client_socket = server->client_socket; - server->client_socket = NULL; - CompilerWriteFence(); - rmtDelete(WebSocket, client_socket); -} - -static rmtError Server_Send(Server* server, const void* data, rmtU32 length, rmtU32 timeout) -{ - assert(server != NULL); - if (Server_IsClientConnected(server)) - { - rmtError error = WebSocket_Send(server->client_socket, data, length, timeout); - if (error == RMT_ERROR_SOCKET_SEND_FAIL) - Server_DisconnectClient(server); - - return error; - } - - return RMT_ERROR_NONE; -} - -static rmtError Server_ReceiveMessage(Server* server, char message_first_byte, rmtU32 message_length) -{ - char message_data[1024]; - - // Check for potential message data overflow - if (message_length >= sizeof(message_data) - 1) - { - rmt_LogText("Ignoring console input bigger than internal receive buffer (1024 bytes)"); - return RMT_ERROR_NONE; - } - - // Receive the rest of the message - message_data[0] = message_first_byte; - rmtTry(WebSocket_Receive(server->client_socket, message_data + 1, NULL, message_length - 1, 100)); - message_data[message_length] = 0; - - // Each message must have a descriptive 4 byte header - if (message_length < 4) - return RMT_ERROR_NONE; - - // Dispatch to handler - if (server->receive_handler) - rmtTry(server->receive_handler(server->receive_handler_context, message_data, message_length)); - - return RMT_ERROR_NONE; -} - -static rmtError bin_MessageHeader(Buffer* buffer, const char* id, rmtU32* out_write_start_offset) -{ - // Record where the header starts before writing it - *out_write_start_offset = buffer->bytes_used; - rmtTry(Buffer_Write(buffer, (void*)id, 4)); - rmtTry(Buffer_Write(buffer, (void*)" ", 4)); - return RMT_ERROR_NONE; -} - -static rmtError bin_MessageFooter(Buffer* buffer, rmtU32 write_start_offset) -{ - // Align message size to 32-bits so that the viewer can alias float arrays within log files - rmtTry(Buffer_AlignedPad(buffer, write_start_offset)); - - // Patch message size, including padding at the end - U32ToByteArray(buffer->data + write_start_offset + 4, (buffer->bytes_used - write_start_offset)); - - return RMT_ERROR_NONE; -} - -static void Server_Update(Server* server) -{ - rmtU32 cur_time; - - assert(server != NULL); - - // Recreate the listening socket if it's been destroyed earlier - if (server->listen_socket == NULL) - Server_CreateListenSocket(server, server->port, server->reuse_open_port, - server->limit_connections_to_localhost); - - if (server->listen_socket != NULL && server->client_socket == NULL) - { - // Accept connections as long as there is no client connected - WebSocket* client_socket = NULL; - rmtError error = WebSocket_AcceptConnection(server->listen_socket, &client_socket); - if (error == RMT_ERROR_NONE) - { - server->client_socket = client_socket; - } - else - { - // Destroy the listen socket on failure to accept - // It will get recreated in another update - rmtDelete(WebSocket, server->listen_socket); - } - } - - else - { - // Loop checking for incoming messages - for (;;) - { - // Inspect first byte to see if a message is there - char message_first_byte; - rmtU32 message_length; - rmtError error = WebSocket_Receive(server->client_socket, &message_first_byte, &message_length, 1, 0); - if (error == RMT_ERROR_NONE) - { - // Parse remaining message - error = Server_ReceiveMessage(server, message_first_byte, message_length); - if (error != RMT_ERROR_NONE) - { - Server_DisconnectClient(server); - break; - } - - // Check for more... - continue; - } - - // Passable errors... - if (error == RMT_ERROR_SOCKET_RECV_NO_DATA) - { - // No data available - break; - } - - if (error == RMT_ERROR_SOCKET_RECV_TIMEOUT) - { - // Data not available yet, can afford to ignore as we're only reading the first byte - break; - } - - // Anything else is an error that may have closed the connection - Server_DisconnectClient(server); - break; - } - } - - // Send pings to the client every second - cur_time = msTimer_Get(); - if (cur_time - server->last_ping_time > 1000) - { - Buffer* bin_buf = server->bin_buf; - rmtU32 write_start_offset; - WebSocket_PrepareBuffer(bin_buf); - bin_MessageHeader(bin_buf, "PING", &write_start_offset); - bin_MessageFooter(bin_buf, write_start_offset); - Server_Send(server, bin_buf->data, bin_buf->bytes_used, 10); - server->last_ping_time = cur_time; - } -} - -/* ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- - @SAMPLE: Base Sample Description for CPU by default ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- -*/ - -#define SAMPLE_NAME_LEN 128 - -typedef struct Sample -{ - // Inherit so that samples can be quickly allocated - ObjectLink Link; - - enum rmtSampleType type; - - // Hash generated from sample name - rmtU32 name_hash; - - // Unique, persistent ID among all samples - rmtU32 unique_id; - - // RGB8 unique colour generated from the unique ID - rmtU8 uniqueColour[3]; - - // Links to related samples in the tree - struct Sample* parent; - struct Sample* first_child; - struct Sample* last_child; - struct Sample* next_sibling; - - // Keep track of child count to distinguish from repeated calls to the same function at the same stack level - // This is also mixed with the callstack hash to allow consistent addressing of any point in the tree - rmtU32 nb_children; - - // Sample end points and length in microseconds - rmtU64 us_start; - rmtU64 us_end; - rmtU64 us_length; - - // Total sampled length of all children - rmtU64 us_sampled_length; - - // If this is a GPU sample, when the sample was issued on the GPU - rmtU64 usGpuIssueOnCpu; - - // Number of times this sample was used in a call in aggregate mode, 1 otherwise - rmtU32 call_count; - - // Current and maximum sample recursion depths - rmtU16 recurse_depth; - rmtU16 max_recurse_depth; - -} Sample; - -static rmtError Sample_Constructor(Sample* sample) -{ - assert(sample != NULL); - - ObjectLink_Constructor((ObjectLink*)sample); - - sample->type = RMT_SampleType_CPU; - sample->name_hash = 0; - sample->unique_id = 0; - sample->uniqueColour[0] = 0; - sample->uniqueColour[1] = 0; - sample->uniqueColour[2] = 0; - sample->parent = NULL; - sample->first_child = NULL; - sample->last_child = NULL; - sample->next_sibling = NULL; - sample->nb_children = 0; - sample->us_start = 0; - sample->us_end = 0; - sample->us_length = 0; - sample->us_sampled_length = 0; - sample->usGpuIssueOnCpu = 0; - sample->call_count = 0; - sample->recurse_depth = 0; - sample->max_recurse_depth = 0; - - return RMT_ERROR_NONE; -} - -static void Sample_Destructor(Sample* sample) -{ - RMT_UNREFERENCED_PARAMETER(sample); -} - -static void Sample_Prepare(Sample* sample, rmtU32 name_hash, Sample* parent) -{ - sample->name_hash = name_hash; - sample->unique_id = 0; - sample->parent = parent; - sample->first_child = NULL; - sample->last_child = NULL; - sample->next_sibling = NULL; - sample->nb_children = 0; - sample->us_start = 0; - sample->us_end = 0; - sample->us_length = 0; - sample->us_sampled_length = 0; - sample->usGpuIssueOnCpu = 0; - sample->call_count = 1; - sample->recurse_depth = 0; - sample->max_recurse_depth = 0; -} - -static void Sample_Close(Sample* sample, rmtS64 us_end) -{ - // Aggregate samples use us_end to store start so that us_start is preserved - rmtS64 us_length = 0; - if (sample->call_count > 1 && sample->max_recurse_depth == 0) - { - us_length = maxS64(us_end - sample->us_end, 0); - } - else - { - us_length = maxS64(us_end - sample->us_start, 0); - } - - sample->us_length += us_length; - - // Sum length on the parent to track un-sampled time in the parent - if (sample->parent != NULL) - { - sample->parent->us_sampled_length += us_length; - } -} - -static void Sample_CopyState(Sample* dst_sample, const Sample* src_sample) -{ - // Copy fields that don't override destination allocator links or transfer source sample tree positioning - // Also ignoring uniqueColour as that's calculated in the Remotery thread - dst_sample->type = src_sample->type; - dst_sample->name_hash = src_sample->name_hash; - dst_sample->unique_id = src_sample->unique_id; - dst_sample->nb_children = src_sample->nb_children; - dst_sample->us_start = src_sample->us_start; - dst_sample->us_end = src_sample->us_end; - dst_sample->us_length = src_sample->us_length; - dst_sample->us_sampled_length = src_sample->us_sampled_length; - dst_sample->usGpuIssueOnCpu = src_sample->usGpuIssueOnCpu; - dst_sample->call_count = src_sample->call_count; - dst_sample->recurse_depth = src_sample->recurse_depth; - dst_sample->max_recurse_depth = src_sample->max_recurse_depth; - - // Prepare empty tree links - dst_sample->parent = NULL; - dst_sample->first_child = NULL; - dst_sample->last_child = NULL; - dst_sample->next_sibling = NULL; -} - -static rmtError bin_SampleArray(Buffer* buffer, Sample* parent_sample, rmtU8 depth); - -static rmtError bin_Sample(Buffer* buffer, Sample* sample, rmtU8 depth) -{ - assert(sample != NULL); - - rmtTry(Buffer_WriteU32(buffer, sample->name_hash)); - rmtTry(Buffer_WriteU32(buffer, sample->unique_id)); - rmtTry(Buffer_Write(buffer, sample->uniqueColour, 3)); - rmtTry(Buffer_Write(buffer, &depth, 1)); - rmtTry(Buffer_WriteU64(buffer, sample->us_start)); - rmtTry(Buffer_WriteU64(buffer, sample->us_length)); - rmtTry(Buffer_WriteU64(buffer, maxS64(sample->us_length - sample->us_sampled_length, 0))); - rmtTry(Buffer_WriteU64(buffer, sample->usGpuIssueOnCpu)); - rmtTry(Buffer_WriteU32(buffer, sample->call_count)); - rmtTry(Buffer_WriteU32(buffer, sample->max_recurse_depth)); - rmtTry(bin_SampleArray(buffer, sample, depth + 1)); - - return RMT_ERROR_NONE; -} - -static rmtError bin_SampleArray(Buffer* buffer, Sample* parent_sample, rmtU8 depth) -{ - Sample* sample; - - rmtTry(Buffer_WriteU32(buffer, parent_sample->nb_children)); - for (sample = parent_sample->first_child; sample != NULL; sample = sample->next_sibling) - rmtTry(bin_Sample(buffer, sample, depth)); - - return RMT_ERROR_NONE; -} - -/* ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- - @SAMPLETREE: A tree of samples with their allocator ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- -*/ - -typedef struct SampleTree -{ - // Allocator for all samples - ObjectAllocator* allocator; - - // Root sample for all samples created by this thread - Sample* root; - - // Most recently pushed sample - Sample* currentParent; - - // Last time this sample tree was completed and sent to listeners, for stall detection - rmtAtomicU32 msLastTreeSendTime; - - // Lightweight flag, changed with release/acquire semantics to inform the stall detector the state of the tree is unreliable - rmtAtomicU32 treeBeingModified; - - // Send this popped sample to the log/viewer on close? - Sample* sendSampleOnClose; - -} SampleTree; - -// Notify tree watchers that its structure is in the process of being changed -#define ModifySampleTree(tree, statements) \ - StoreRelease(&tree->treeBeingModified, 1); \ - statements; \ - StoreRelease(&tree->treeBeingModified, 0); - -static rmtError SampleTree_Constructor(SampleTree* tree, rmtU32 sample_size, ObjConstructor constructor, - ObjDestructor destructor) -{ - assert(tree != NULL); - - tree->allocator = NULL; - tree->root = NULL; - tree->currentParent = NULL; - StoreRelease(&tree->msLastTreeSendTime, 0); - StoreRelease(&tree->treeBeingModified, 0); - tree->sendSampleOnClose = NULL; - - // Create the sample allocator - rmtTryNew(ObjectAllocator, tree->allocator, sample_size, constructor, destructor); - - // Create a root sample that's around for the lifetime of the thread - rmtTry(ObjectAllocator_Alloc(tree->allocator, (void**)&tree->root)); - Sample_Prepare(tree->root, 0, NULL); - tree->currentParent = tree->root; - - return RMT_ERROR_NONE; -} - -static void SampleTree_Destructor(SampleTree* tree) -{ - assert(tree != NULL); - - if (tree->root != NULL) - { - ObjectAllocator_Free(tree->allocator, tree->root); - tree->root = NULL; - } - - rmtDelete(ObjectAllocator, tree->allocator); -} - -static rmtU32 HashCombine(rmtU32 hash_a, rmtU32 hash_b) -{ - // A sequence of 32 uniformly random bits so that each bit of the combined hash is changed on application - // Derived from the golden ratio: UINT_MAX / ((1 + sqrt(5)) / 2) - // In reality it's just an arbitrary value which happens to work well, avoiding mapping all zeros to zeros. - // http://burtleburtle.net/bob/hash/doobs.html - static rmtU32 random_bits = 0x9E3779B9; - hash_a ^= hash_b + random_bits + (hash_a << 6) + (hash_a >> 2); - return hash_a; -} - -static rmtError SampleTree_Push(SampleTree* tree, rmtU32 name_hash, rmtU32 flags, Sample** sample) -{ - Sample* parent; - rmtU32 unique_id; - - // As each tree has a root sample node allocated, a parent must always be present - assert(tree != NULL); - assert(tree->currentParent != NULL); - parent = tree->currentParent; - - // Assume no flags is the common case and predicate branch checks - if (flags != 0) - { - // Check root status - if ((flags & RMTSF_Root) != 0) - { - assert(parent->parent == NULL); - } - - if ((flags & RMTSF_Aggregate) != 0) - { - // Linear search for previous instance of this sample name - Sample* sibling; - for (sibling = parent->first_child; sibling != NULL; sibling = sibling->next_sibling) - { - if (sibling->name_hash == name_hash) - { - tree->currentParent = sibling; - sibling->call_count++; - *sample = sibling; - return RMT_ERROR_NONE; - } - } - } - - // Collapse sample on recursion - if ((flags & RMTSF_Recursive) != 0 && parent->name_hash == name_hash) - { - parent->recurse_depth++; - parent->max_recurse_depth = maxU16(parent->max_recurse_depth, parent->recurse_depth); - parent->call_count++; - *sample = parent; - return RMT_ERROR_RECURSIVE_SAMPLE; - } - - // Allocate a new sample for subsequent flag checks to reference - rmtTry(ObjectAllocator_Alloc(tree->allocator, (void**)sample)); - Sample_Prepare(*sample, name_hash, parent); - - // Check for sending this sample on close - if ((flags & RMTSF_SendOnClose) != 0) - { - assert(tree->currentParent != NULL); - assert(tree->sendSampleOnClose == NULL); - tree->sendSampleOnClose = *sample; - } - } - - else - { - // Allocate a new sample - rmtTry(ObjectAllocator_Alloc(tree->allocator, (void**)sample)); - Sample_Prepare(*sample, name_hash, parent); - } - - // Generate a unique ID for this sample in the tree - unique_id = parent->unique_id; - unique_id = HashCombine(unique_id, (*sample)->name_hash); - unique_id = HashCombine(unique_id, parent->nb_children); - (*sample)->unique_id = unique_id; - - // Add sample to its parent - parent->nb_children++; - if (parent->first_child == NULL) - { - parent->first_child = *sample; - parent->last_child = *sample; - } - else - { - assert(parent->last_child != NULL); - parent->last_child->next_sibling = *sample; - parent->last_child = *sample; - } - - // Make this sample the new parent of any newly created samples - tree->currentParent = *sample; - - return RMT_ERROR_NONE; -} - -static void SampleTree_Pop(SampleTree* tree, Sample* sample) -{ - assert(tree != NULL); - assert(sample != NULL); - assert(sample != tree->root); - tree->currentParent = sample->parent; -} - -static ObjectLink* FlattenSamples(Sample* sample, rmtU32* nb_samples) -{ - Sample* child; - ObjectLink* cur_link = &sample->Link; - - assert(sample != NULL); - assert(nb_samples != NULL); - - *nb_samples += 1; - sample->Link.next = (ObjectLink*)sample->first_child; - - // Link all children together - for (child = sample->first_child; child != NULL; child = child->next_sibling) - { - ObjectLink* last_link = FlattenSamples(child, nb_samples); - last_link->next = (ObjectLink*)child->next_sibling; - cur_link = last_link; - } - - // Clear child info - sample->first_child = NULL; - sample->last_child = NULL; - sample->nb_children = 0; - - return cur_link; -} - -static void FreeSamples(Sample* sample, ObjectAllocator* allocator) -{ - // Chain all samples together in a flat list - rmtU32 nb_cleared_samples = 0; - ObjectLink* last_link = FlattenSamples(sample, &nb_cleared_samples); - - // Release the complete sample memory range - if (sample->Link.next != NULL) - { - ObjectAllocator_FreeRange(allocator, sample, last_link, nb_cleared_samples); - } - else - { - ObjectAllocator_Free(allocator, sample); - } -} - -static rmtError SampleTree_CopySample(Sample** out_dst_sample, Sample* dst_parent_sample, ObjectAllocator* allocator, const Sample* src_sample) -{ - Sample* src_child; - - // Allocate a copy of the sample - Sample* dst_sample; - rmtTry(ObjectAllocator_Alloc(allocator, (void**)&dst_sample)); - Sample_CopyState(dst_sample, src_sample); - - // Link the newly created/copied sample to its parent - // Note that metrics including nb_children have already been copied by the Sample_CopyState call - if (dst_parent_sample != NULL) - { - if (dst_parent_sample->first_child == NULL) - { - dst_parent_sample->first_child = dst_sample; - dst_parent_sample->last_child = dst_sample; - } - else - { - assert(dst_parent_sample->last_child != NULL); - dst_parent_sample->last_child->next_sibling = dst_sample; - dst_parent_sample->last_child = dst_sample; - } - } - - // Copy all children - for (src_child = src_sample->first_child; src_child != NULL; src_child = src_child->next_sibling) - { - Sample* dst_child; - rmtTry(SampleTree_CopySample(&dst_child, dst_sample, allocator, src_child)); - } - - *out_dst_sample = dst_sample; - - return RMT_ERROR_NONE; -} - -static rmtError SampleTree_Copy(SampleTree* dst_tree, const SampleTree* src_tree) -{ - // Sample trees are allocated at startup and their allocators are persistent for the lifetime of the Remotery object. - // It's safe to reference the allocator and use it for sample lifetime. - ObjectAllocator* allocator = src_tree->allocator; - dst_tree->allocator = allocator; - - // Copy from the root - rmtTry(SampleTree_CopySample(&dst_tree->root, NULL, allocator, src_tree->root)); - dst_tree->currentParent = dst_tree->root; - - return RMT_ERROR_NONE; -} - -typedef struct Msg_SampleTree -{ - Sample* rootSample; - - ObjectAllocator* allocator; - - rmtPStr threadName; - - // Data specific to the sample tree that downstream users can inspect/use - rmtU32 userData; - - rmtBool partialTree; -} Msg_SampleTree; - -static void QueueSampleTree(rmtMessageQueue* queue, Sample* sample, ObjectAllocator* allocator, rmtPStr thread_name, rmtU32 user_data, - struct ThreadProfiler* thread_profiler, rmtBool partial_tree) -{ - Msg_SampleTree* payload; - - // Attempt to allocate a message for sending the tree to the viewer - Message* message = rmtMessageQueue_AllocMessage(queue, sizeof(Msg_SampleTree), thread_profiler); - if (message == NULL) - { - // Discard tree samples on failure - FreeSamples(sample, allocator); - return; - } - - // Populate and commit - payload = (Msg_SampleTree*)message->payload; - payload->rootSample = sample; - payload->allocator = allocator; - payload->threadName = thread_name; - payload->userData = user_data; - payload->partialTree = partial_tree; - rmtMessageQueue_CommitMessage(message, MsgID_SampleTree); -} - -typedef struct Msg_AddToStringTable -{ - rmtU32 hash; - rmtU32 length; -} Msg_AddToStringTable; - -static rmtBool QueueAddToStringTable(rmtMessageQueue* queue, rmtU32 hash, const char* string, size_t length, struct ThreadProfiler* thread_profiler) -{ - Msg_AddToStringTable* payload; - - // Attempt to allocate a message om the queue - size_t nb_string_bytes = length + 1; - Message* message = rmtMessageQueue_AllocMessage(queue, sizeof(Msg_AddToStringTable) + nb_string_bytes, thread_profiler); - if (message == NULL) - { - return RMT_FALSE; - } - - // Populate and commit - payload = (Msg_AddToStringTable*)message->payload; - payload->hash = hash; - payload->length = length; - memcpy(payload + 1, string, nb_string_bytes); - rmtMessageQueue_CommitMessage(message, MsgID_AddToStringTable); - - return RMT_TRUE; -} - -/* ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- - @TPROFILER: Thread Profiler data, storing both sampling and instrumentation results ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- -*/ - -#if RMT_USE_D3D11 -typedef struct D3D11 D3D11; -static rmtError D3D11_Create(D3D11** d3d11); -static void D3D11_Destructor(D3D11* d3d11); -#endif - -#if RMT_USE_D3D12 -typedef struct D3D12ThreadData D3D12ThreadData; -static rmtError D3D12ThreadData_Create(D3D12ThreadData** d3d12); -static void D3D12ThreadData_Destructor(D3D12ThreadData* d3d12); -#endif - -typedef struct ThreadProfiler -{ - // Storage for backing up initial register values when modifying a thread's context - rmtU64 registerBackup0; // 0 - rmtU64 registerBackup1; // 8 - rmtU64 registerBackup2; // 16 - - // Used to schedule callbacks taking into account some threads may be sleeping - rmtAtomicS32 nbSamplesWithoutCallback; // 24 - - // Index of the processor the thread was last seen running on - rmtU32 processorIndex; // 28 - rmtU32 lastProcessorIndex; - - // OS thread ID/handle - rmtThreadId threadId; - rmtThreadHandle threadHandle; - - // Thread name stored for sending to the viewer - char threadName[64]; - rmtU32 threadNameHash; - - // Store a unique sample tree for each type - SampleTree* sampleTrees[RMT_SampleType_Count]; - -#if RMT_USE_D3D11 - D3D11* d3d11; -#endif - -#if RMT_USE_D3D12 - D3D12ThreadData* d3d12ThreadData; -#endif -} ThreadProfiler; - -static rmtError ThreadProfiler_Constructor(rmtMessageQueue* mq_to_rmt, ThreadProfiler* thread_profiler, rmtThreadId thread_id) -{ - rmtU32 name_length; - - // Set defaults - thread_profiler->nbSamplesWithoutCallback = 0; - thread_profiler->processorIndex = (rmtU32)-1; - thread_profiler->lastProcessorIndex = (rmtU32)-1; - thread_profiler->threadId = thread_id; - memset(thread_profiler->sampleTrees, 0, sizeof(thread_profiler->sampleTrees)); - -#if RMT_USE_D3D11 - thread_profiler->d3d11 = NULL; -#endif - -#if RMT_USE_D3D12 - thread_profiler->d3d12ThreadData = NULL; -#endif - - // Pre-open the thread handle - rmtTry(rmtOpenThreadHandle(thread_id, &thread_profiler->threadHandle)); - - // Name the thread and add to the string table - // Users can override this at a later point with the Remotery thread name API - rmtGetThreadName(thread_id, thread_profiler->threadHandle, thread_profiler->threadName, sizeof(thread_profiler->threadName)); - name_length = strnlen_s(thread_profiler->threadName, 64); - thread_profiler->threadNameHash = _rmt_HashString32(thread_profiler->threadName, name_length, 0); - QueueAddToStringTable(mq_to_rmt, thread_profiler->threadNameHash, thread_profiler->threadName, name_length, thread_profiler); - - // Create the CPU sample tree only. The rest are created on-demand as they need extra context to function correctly. - rmtTryNew(SampleTree, thread_profiler->sampleTrees[RMT_SampleType_CPU], sizeof(Sample), (ObjConstructor)Sample_Constructor, - (ObjDestructor)Sample_Destructor); - -#if RMT_USE_D3D11 - rmtTry(D3D11_Create(&thread_profiler->d3d11)); -#endif - -#if RMT_USE_D3D12 - rmtTry(D3D12ThreadData_Create(&thread_profiler->d3d12ThreadData)); -#endif - - return RMT_ERROR_NONE; -} - -static void ThreadProfiler_Destructor(ThreadProfiler* thread_profiler) -{ - rmtU32 index; - -#if RMT_USE_D3D12 - rmtDelete(D3D12ThreadData, thread_profiler->d3d12ThreadData); -#endif - -#if RMT_USE_D3D11 - rmtDelete(D3D11, thread_profiler->d3d11); -#endif - - for (index = 0; index < RMT_SampleType_Count; index++) - { - rmtDelete(SampleTree, thread_profiler->sampleTrees[index]); - } - - rmtCloseThreadHandle(thread_profiler->threadHandle); -} - -static rmtError ThreadProfiler_Push(SampleTree* tree, rmtU32 name_hash, rmtU32 flags, Sample** sample) -{ - rmtError error; - ModifySampleTree(tree, - error = SampleTree_Push(tree, name_hash, flags, sample); - ); - return error; -} - -static void CloseOpenSamples(Sample* sample, rmtU64 sample_time_us, rmtU32 parents_are_last) -{ - Sample* child_sample; - - // Depth-first search into children as we want to close child samples before their parents - for (child_sample = sample->first_child; child_sample != NULL; child_sample = child_sample->next_sibling) - { - rmtU32 is_last = parents_are_last & (child_sample == sample->last_child ? 1 : 0); - CloseOpenSamples(child_sample, sample_time_us, is_last); - } - - // A chain of open samples will be linked from the root to the deepest, currently open sample - if (parents_are_last > 0) - { - Sample_Close(sample, sample_time_us); - } -} - -static rmtError MakePartialTreeCopy(SampleTree* sample_tree, rmtU64 sample_time_us, SampleTree* out_sample_tree_copy) -{ - rmtU32 sample_time_s = (rmtU32)(sample_time_us / 1000); - StoreRelease(&sample_tree->msLastTreeSendTime, sample_time_s); - - // Make a local copy of the tree as we want to keep the current tree for active profiling - rmtTry(SampleTree_Copy(out_sample_tree_copy, sample_tree)); - - // Close all samples from the deepest open sample, right back to the root - CloseOpenSamples(out_sample_tree_copy->root, sample_time_us, 1); - - return RMT_ERROR_NONE; -} - -static rmtBool ThreadProfiler_Pop(ThreadProfiler* thread_profiler, rmtMessageQueue* queue, Sample* sample, rmtU32 msg_user_data) -{ - SampleTree* tree = thread_profiler->sampleTrees[sample->type]; - SampleTree_Pop(tree, sample); - - // Are we back at the root? - if (tree->currentParent == tree->root) - { - Sample* root; - - // Disconnect all samples from the root and pack in the chosen message queue - ModifySampleTree(tree, - root = tree->root; - root->first_child = NULL; - root->last_child = NULL; - root->nb_children = 0; - ); - QueueSampleTree(queue, sample, tree->allocator, thread_profiler->threadName, msg_user_data, thread_profiler, RMT_FALSE); - - // Update the last send time for this tree, for stall detection - StoreRelease(&tree->msLastTreeSendTime, (rmtU32)(sample->us_end / 1000)); - - return RMT_TRUE; - } - - if (tree->sendSampleOnClose == sample) - { - // Copy the sample tree as it is and send as a partial tree - SampleTree partial_tree; - if (MakePartialTreeCopy(tree, sample->us_start + sample->us_length, &partial_tree) == RMT_ERROR_NONE) - { - Sample* sample = partial_tree.root->first_child; - assert(sample != NULL); - QueueSampleTree(queue, sample, partial_tree.allocator, thread_profiler->threadName, msg_user_data, thread_profiler, RMT_TRUE); - } - - // Tree has been copied away to the message queue so free up the samples - if (partial_tree.root != NULL) - { - FreeSamples(partial_tree.root, partial_tree.allocator); - } - - tree->sendSampleOnClose = NULL; - } - - return RMT_FALSE; -} - -static rmtU32 ThreadProfiler_GetNameHash(ThreadProfiler* thread_profiler, rmtMessageQueue* queue, rmtPStr name, rmtU32* hash_cache) -{ - size_t name_len; - rmtU32 name_hash; - - // Hash cache provided? - if (hash_cache != NULL) - { - // Calculate the hash first time round only - name_hash = *hash_cache; - if (name_hash == 0) - { - assert(name != NULL); - name_len = strnlen_s(name, 256); - name_hash = _rmt_HashString32(name, name_len, 0); - - // Queue the string for the string table and only cache the hash if it succeeds - if (QueueAddToStringTable(queue, name_hash, name, name_len, thread_profiler) == RMT_TRUE) - { - *hash_cache = name_hash; - } - } - - return name_hash; - } - - // Have to recalculate and speculatively insert the name every time when no cache storage exists - name_len = strnlen_s(name, 256); - name_hash = _rmt_HashString32(name, name_len, 0); - QueueAddToStringTable(queue, name_hash, name, name_len, thread_profiler); - return name_hash; -} - -typedef struct ThreadProfilers -{ - // Timer shared with Remotery threads - usTimer* timer; - - // Queue between clients and main remotery thread - rmtMessageQueue* mqToRmtThread; - - // On x64 machines this points to the sample function - void* compiledSampleFn; - rmtU32 compiledSampleFnSize; - - // Used to store thread profilers bound to an OS thread - rmtTLS threadProfilerTlsHandle; - - // Array of preallocated ThreadProfiler objects - // Read iteration is safe given that no incomplete ThreadProfiler objects will be encountered during iteration. - // The ThreadProfiler count is only incremented once a new ThreadProfiler is fully defined and ready to be used. - // Do not use this list to verify if a ThreadProfiler exists for a given thread. Use the mutex-guarded Get functions instead. - ThreadProfiler threadProfilers[256]; - rmtAtomicU32 nbThreadProfilers; - rmtU32 maxNbThreadProfilers; - - // Guards creation and existence-testing of the ThreadProfiler list - rmtMutex threadProfilerMutex; - - // Periodic thread sampling thread - rmtThread* threadSampleThread; - - // Periodic thread to processor gatherer - rmtThread* threadGatherThread; -} ThreadProfilers; - -static rmtError SampleThreadsLoop(rmtThread* rmt_thread); - -#ifdef RMT_PLATFORM_WINDOWS -#ifdef RMT_ARCH_64BIT -static void* CreateSampleCallback(rmtU32* out_size); -#endif -#endif - -static rmtError ThreadProfilers_Constructor(ThreadProfilers* thread_profilers, usTimer* timer, rmtMessageQueue* mq_to_rmt_thread) -{ - // Set to default - thread_profilers->timer = timer; - thread_profilers->mqToRmtThread = mq_to_rmt_thread; - thread_profilers->compiledSampleFn = NULL; - thread_profilers->compiledSampleFnSize = 0; - thread_profilers->threadProfilerTlsHandle = TLS_INVALID_HANDLE; - thread_profilers->nbThreadProfilers = 0; - thread_profilers->maxNbThreadProfilers = sizeof(thread_profilers->threadProfilers) / sizeof(thread_profilers->threadProfilers[0]); - mtxInit(&thread_profilers->threadProfilerMutex); - thread_profilers->threadSampleThread = NULL; - thread_profilers->threadGatherThread = NULL; - -#ifdef RMT_PLATFORM_WINDOWS -#ifdef RMT_ARCH_64BIT - thread_profilers->compiledSampleFn = CreateSampleCallback(&thread_profilers->compiledSampleFnSize); - if (thread_profilers->compiledSampleFn == NULL) - { - return RMT_ERROR_MALLOC_FAIL; - } -#endif -#endif - - // Allocate a TLS handle for the thread profilers - rmtTry(tlsAlloc(&thread_profilers->threadProfilerTlsHandle)); - - // Kick-off the thread sampler - if (g_Settings.enableThreadSampler == RMT_TRUE) - { - rmtTryNew(rmtThread, thread_profilers->threadSampleThread, SampleThreadsLoop, thread_profilers); - } - - return RMT_ERROR_NONE; -} - -static void ThreadProfilers_Destructor(ThreadProfilers* thread_profilers) -{ - rmtU32 thread_index; - - rmtDelete(rmtThread, thread_profilers->threadSampleThread); - - // Delete all profilers - for (thread_index = 0; thread_index < thread_profilers->nbThreadProfilers; thread_index++) - { - ThreadProfiler* thread_profiler = thread_profilers->threadProfilers + thread_index; - ThreadProfiler_Destructor(thread_profiler); - } - - if (thread_profilers->threadProfilerTlsHandle != TLS_INVALID_HANDLE) - { - tlsFree(thread_profilers->threadProfilerTlsHandle); - } - -#ifdef RMT_PLATFORM_WINDOWS -#ifdef RMT_ARCH_64BIT - if (thread_profilers->compiledSampleFn != NULL) - { - VirtualFree(thread_profilers->compiledSampleFn, 0, MEM_RELEASE); - } -#endif -#endif - - mtxDelete(&thread_profilers->threadProfilerMutex); -} - -static rmtError ThreadProfilers_GetThreadProfiler(ThreadProfilers* thread_profilers, rmtThreadId thread_id, ThreadProfiler** out_thread_profiler) -{ - rmtU32 profiler_index; - ThreadProfiler* thread_profiler; - rmtError error; - - mtxLock(&thread_profilers->threadProfilerMutex); - - // Linear search for a matching thread id - for (profiler_index = 0; profiler_index < thread_profilers->nbThreadProfilers; profiler_index++) - { - thread_profiler = thread_profilers->threadProfilers + profiler_index; - if (thread_profiler->threadId == thread_id) - { - *out_thread_profiler = thread_profiler; - mtxUnlock(&thread_profilers->threadProfilerMutex); - return RMT_ERROR_NONE; - } - } - - // Thread info not found so create a new one at the end - thread_profiler = thread_profilers->threadProfilers + thread_profilers->nbThreadProfilers; - error = ThreadProfiler_Constructor(thread_profilers->mqToRmtThread, thread_profiler, thread_id); - if (error != RMT_ERROR_NONE) - { - ThreadProfiler_Destructor(thread_profiler); - mtxUnlock(&thread_profilers->threadProfilerMutex); - return error; - } - *out_thread_profiler = thread_profiler; - - // Increment count for consume by read iterators - // Within the mutex so that there are no race conditions creating thread profilers - // Using release semantics to ensure a memory barrier for read iterators - StoreRelease(&thread_profilers->nbThreadProfilers, thread_profilers->nbThreadProfilers + 1); - - mtxUnlock(&thread_profilers->threadProfilerMutex); - - return RMT_ERROR_NONE; -} - -static rmtError ThreadProfilers_GetCurrentThreadProfiler(ThreadProfilers* thread_profilers, ThreadProfiler** out_thread_profiler) -{ - // Is there a thread profiler associated with this thread yet? - *out_thread_profiler = (ThreadProfiler*)tlsGet(thread_profilers->threadProfilerTlsHandle); - if (*out_thread_profiler == NULL) - { - // Allocate on-demand - rmtTry(ThreadProfilers_GetThreadProfiler(thread_profilers, rmtGetCurrentThreadId(), out_thread_profiler)); - - // Bind to the curren thread - tlsSet(thread_profilers->threadProfilerTlsHandle, *out_thread_profiler); - } - - return RMT_ERROR_NONE; -} - -static rmtBool ThreadProfilers_ThreadInCallback(ThreadProfilers* thread_profilers, rmtCpuContext* context) -{ -#ifdef RMT_PLATFORM_WINDOWS -#ifdef RMT_ARCH_32BIT - if (context->Eip >= (DWORD)thread_profilers->compiledSampleFn && - context->Eip < (DWORD)((char*)thread_profilers->compiledSampleFn + thread_profilers->compiledSampleFnSize)) - { - return RMT_TRUE; - } -#else - if (context->Rip >= (DWORD64)thread_profilers->compiledSampleFn && - context->Rip < (DWORD64)((char*)thread_profilers->compiledSampleFn + thread_profilers->compiledSampleFnSize)) - { - return RMT_TRUE; - } -#endif -#endif - return RMT_FALSE; -} - -/* ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- - @TGATHER: Thread Gatherer, periodically polling for newly created threads ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- -*/ - -static void GatherThreads(ThreadProfilers* thread_profilers) -{ - rmtThreadHandle handle; - - assert(thread_profilers != NULL); - -#ifdef RMT_ENABLE_THREAD_SAMPLER - - // Create the snapshot - this is a slow call - handle = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); - if (handle != INVALID_HANDLE_VALUE) - { - BOOL success; - - THREADENTRY32 thread_entry; - thread_entry.dwSize = sizeof(thread_entry); - - // Loop through all threads owned by this process - success = Thread32First(handle, &thread_entry); - while (success == TRUE) - { - if (thread_entry.th32OwnerProcessID == GetCurrentProcessId()) - { - // Create thread profilers on-demand if there're not already there - ThreadProfiler* thread_profiler; - rmtError error = ThreadProfilers_GetThreadProfiler(thread_profilers, thread_entry.th32ThreadID, &thread_profiler); - if (error != RMT_ERROR_NONE) - { - // Not really worth bringing the whole profiler down here - rmt_LogText("REMOTERY ERROR: Failed to create Thread Profiler"); - } - } - - success = Thread32Next(handle, &thread_entry); - } - - CloseHandle(handle); - } - -#endif -} - -static rmtError GatherThreadsLoop(rmtThread* thread) -{ - ThreadProfilers* thread_profilers = (ThreadProfilers*)thread->param; - rmtU32 sleep_time = 100; - - assert(thread_profilers != NULL); - - rmt_SetCurrentThreadName("RemoteryGatherThreads"); - - while (thread->request_exit == RMT_FALSE) - { - // We want a long period of time between scanning for new threads as the process is a little expensive (~30ms here). - // However not too long so as to miss potentially detailed process startup data. - // Use reduced sleep time at startup to catch as many early thread creations as possible. - // TODO(don): We could get processes to register themselves to ensure no startup data is lost but the scan must still - // be present, to catch threads in a process that the user doesn't create (e.g. graphics driver threads). - GatherThreads(thread_profilers); - msSleep(sleep_time); - sleep_time = minU32(sleep_time * 2, 2000); - } - - return RMT_ERROR_NONE; -} - -/* ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- - @TSAMPLER: Sampling thread contexts ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- -*/ - -typedef struct Processor -{ - // Current thread profiler sampling this processor - ThreadProfiler* threadProfiler; - - rmtU32 sampleCount; - rmtU64 sampleTime; -} Processor; - -typedef struct Msg_ProcessorThreads -{ - // Running index of processor messages - rmtU64 messageIndex; - - // Processor array, leaking into the memory behind the struct - rmtU32 nbProcessors; - Processor processors[1]; -} Msg_ProcessorThreads; - -static void QueueProcessorThreads(rmtMessageQueue* queue, rmtU64 message_index, rmtU32 nb_processors, Processor* processors) -{ - Msg_ProcessorThreads* payload; - - // Attempt to allocate a message for sending processors to the viewer - rmtU32 array_size = (nb_processors - 1) * sizeof(Processor); - Message* message = rmtMessageQueue_AllocMessage(queue, sizeof(Msg_ProcessorThreads) + array_size, NULL); - if (message == NULL) - { - return; - } - - // Populate and commit - payload = (Msg_ProcessorThreads*)message->payload; - payload->messageIndex = message_index; - payload->nbProcessors = nb_processors; - memcpy(payload->processors, processors, nb_processors * sizeof(Processor)); - rmtMessageQueue_CommitMessage(message, MsgID_ProcessorThreads); -} - -#ifdef RMT_ARCH_32BIT -__declspec(naked) static void SampleCallback() -{ - // - // It's important to realise that this call can be pre-empted by the scheduler and shifted to another processor *while we are - // sampling which processor this thread is on*. - // - // This has two very important implications: - // - // * What we are sampling here is an *approximation* of the path of threads across processors. - // * These samples can't be used to "open" and "close" sample periods on a processor as it's highly likely you'll get many - // open events without a close, or vice versa. - // - // As such, we can only choose a sampling period and for each sample register which threads are on which processor. - // - // This is very different to hooking up the Event Tracing API (requiring Administrator elevation), which raises events for - // each context switch, directly from the kernel. - // - - __asm - { - // Push the EIP return address used by the final ret instruction - push ebx - - // We might be in the middle of something like a cmp/jmp instruction pair so preserve EFLAGS - // (Classic example which seems to pop up regularly is _RTC_CheckESP, with cmp/call/jne) - pushfd - - // Push all volatile registers as we don't know what the function calls below will destroy - push eax - push ecx - push edx - - // Retrieve and store the current processor index - call esi - mov [edi].processorIndex, eax - - // Mark as ready for scheduling another callback - // Intel x86 store release - mov [edi].nbSamplesWithoutCallback, 0 - - // Restore preserved register state - pop edx - pop ecx - pop eax - - // Restore registers used to provide parameters to the callback - mov ebx, dword ptr [edi].registerBackup0 - mov esi, dword ptr [edi].registerBackup1 - mov edi, dword ptr [edi].registerBackup2 - - // Restore EFLAGS - popfd - - // Pops the original EIP off the stack and jmps to origin suspend point in the thread - ret - } -} -#elif defined(RMT_ARCH_64BIT) -// Generated with https://defuse.ca/online-x86-assembler.htm -static rmtU8 SampleCallbackBytes[] = -{ - // Push the RIP return address used by the final ret instruction - 0x53, // push rbx - - // We might be in the middle of something like a cmp/jmp instruction pair so preserve RFLAGS - // (Classic example which seems to pop up regularly is _RTC_CheckESP, with cmp/call/jne) - 0x9C, // pushfq - - // Push all volatile registers as we don't know what the function calls below will destroy - 0x50, // push rax - 0x51, // push rcx - 0x52, // push rdx - 0x41, 0x50, // push r8 - 0x41, 0x51, // push r9 - 0x41, 0x52, // push r10 - 0x41, 0x53, // push r11 - - // Retrieve and store the current processor index - 0xFF, 0xD6, // call rsi - 0x89, 0x47, 0x1C, // mov dword ptr [rdi + 28], eax - - // Mark as ready for scheduling another callback - // Intel x64 store release - 0xC7, 0x47, 0x18, 0x00, 0x00, 0x00, 0x00, // mov dword ptr [rdi + 24], 0 - - // Restore preserved register state - 0x41, 0x5B, // pop r11 - 0x41, 0x5A, // pop r10 - 0x41, 0x59, // pop r9 - 0x41, 0x58, // pop r8 - 0x5A, // pop rdx - 0x59, // pop rcx - 0x58, // pop rax - - // Restore registers used to provide parameters to the callback - 0x48, 0x8B, 0x1F, // mov rbx, qword ptr [rdi + 0] - 0x48, 0x8B, 0x77, 0x08, // mov rsi, qword ptr [rdi + 8] - 0x48, 0x8B, 0x7F, 0x10, // mov rdi, qword ptr [rdi + 16] - - // Restore RFLAGS - 0x9D, // popfq - - // Pops the original EIP off the stack and jmps to origin suspend point in the thread - 0xC3 // ret -}; -#ifdef RMT_PLATFORM_WINDOWS -static void* CreateSampleCallback(rmtU32* out_size) -{ - // Allocate page for the generated code - DWORD size = 4096; - DWORD old_protect; - void* function = VirtualAlloc(NULL, size, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); - if (function == NULL) - { - return NULL; - } - - // Clear whole allocation to int 3h - memset(function, 0xCC, size); - - // Copy over the generated code - memcpy(function, SampleCallbackBytes, sizeof(SampleCallbackBytes)); - *out_size = sizeof(SampleCallbackBytes); - - // Enable execution - VirtualProtect(function, size, PAGE_EXECUTE_READ, &old_protect); - return function; -} -#endif -#endif - -#if defined(__cplusplus) && __cplusplus >= 201103L -static_assert(offsetof(ThreadProfiler, nbSamplesWithoutCallback) == 24, ""); -static_assert(offsetof(ThreadProfiler, processorIndex) == 28, ""); -#endif - -static rmtError CheckForStallingSamples(SampleTree* stalling_sample_tree, ThreadProfiler* thread_profiler, rmtU64 sample_time_us) -{ - SampleTree* sample_tree; - rmtU32 sample_time_s = (rmtU32)(sample_time_us / 1000); - - // Initialise to empty - stalling_sample_tree->root = NULL; - stalling_sample_tree->allocator = NULL; - - // Skip the stall check if the tree is being modified - sample_tree = thread_profiler->sampleTrees[RMT_SampleType_CPU]; - if (LoadAcquire(&sample_tree->treeBeingModified) != 0) - { - return RMT_ERROR_NONE; - } - - if (sample_tree != NULL) - { - // The root is a dummy root inserted on tree creation so check that for children - Sample* root_sample = sample_tree->root; - if (root_sample != NULL && root_sample->nb_children > 0) - { - if (sample_time_s - LoadAcquire(&sample_tree->msLastTreeSendTime) > 1000) - { - rmtTry(MakePartialTreeCopy(sample_tree, sample_time_us, stalling_sample_tree)); - } - } - } - - return RMT_ERROR_NONE; -} - -static rmtError InitThreadSampling(ThreadProfilers* thread_profilers) -{ - rmt_SetCurrentThreadName("RemoterySampleThreads"); - - // Make an initial gather so that we have something to work with - GatherThreads(thread_profilers); - -#ifdef RMT_ENABLE_THREAD_SAMPLER - // Ensure we can wake up every millisecond - if (timeBeginPeriod(1) != TIMERR_NOERROR) - { - return RMT_ERROR_UNKNOWN; - } -#endif - - // Kick-off the background thread that watches for new threads - rmtTryNew(rmtThread, thread_profilers->threadGatherThread, GatherThreadsLoop, thread_profilers); - - // We're going to be shuffling thread visits to avoid the scheduler trying to predict a work-load based on sampling - // Use the global RNG with a random seed to start the shuffle - Well512_Init((rmtU32)time(NULL)); - - return RMT_ERROR_NONE; -} - -static rmtError SampleThreadsLoop(rmtThread* rmt_thread) -{ - rmtCpuContext context; - rmtU32 processor_message_index = 0; - rmtU32 nb_processors; - Processor* processors; - rmtU32 processor_index; - - ThreadProfilers* thread_profilers = (ThreadProfilers*)rmt_thread->param; - - // If we can't figure out how many processors there are then we are running on an unsupported platform - nb_processors = rmtGetNbProcessors(); - if (nb_processors == 0) - { - return RMT_ERROR_UNKNOWN; - } - - rmtTry(InitThreadSampling(thread_profilers)); - - // An array entry for each processor - rmtTryMallocArray(Processor, processors, nb_processors); - for (processor_index = 0; processor_index < nb_processors; processor_index++) - { - processors[processor_index].threadProfiler = NULL; - processors[processor_index].sampleTime = 0; - } - - while (rmt_thread->request_exit == RMT_FALSE) - { - rmtU32 lfsr_seed; - rmtU32 lfsr_value; - - // Query how many threads the gather knows about this time round - rmtU32 nb_thread_profilers = LoadAcquire(&thread_profilers->nbThreadProfilers); - - // Calculate table size log2 required to fit count entries. Normally we would adjust the log2 input by -1 so that - // power-of-2 counts map to their exact bit offset and don't require a twice larger table. You can iterate indices - // 0 to (1<= nb_thread_profilers) - { - continue; - } - - // Ignore our own thread - thread_id = rmtGetCurrentThreadId(); - thread_profiler = thread_profilers->threadProfilers + thread_index; - if (thread_profiler->threadId == thread_id) - { - continue; - } - - // Suspend the thread so we can insert a callback - thread_handle = thread_profiler->threadHandle; - if (rmtSuspendThread(thread_handle) == RMT_FALSE) - { - continue; - } - - // Mark the processor this thread was last recorded as running on. - // Note that a thread might be pre-empted multiple times in-between sampling. Given a sampling rate equal to the - // scheduling quantum, this doesn't happen too often. However in such cases, whoever marks the processor last is - // the one that gets recorded. - sample_time_us = usTimer_Get(thread_profilers->timer); - sample_count = AtomicAddS32(&thread_profiler->nbSamplesWithoutCallback, 1); - processor_index = thread_profiler->processorIndex; - if (processor_index != (rmtU32)-1) - { - assert(processor_index < nb_processors); - processors[processor_index].threadProfiler = thread_profiler; - processors[processor_index].sampleCount = sample_count; - processors[processor_index].sampleTime = sample_time_us; - } - - // Swap in a new context with our callback if one is not already scheduled on this thread - if (sample_count == 0) - { - if (rmtGetUserModeThreadContext(thread_handle, &context) == RMT_TRUE && - // There is a slight window of opportunity, after which the callback sets nbSamplesWithoutCallback=0, - // for this loop to suspend a thread while it's executing the last instructions of the callback. - ThreadProfilers_ThreadInCallback(thread_profilers, &context) == RMT_FALSE) - { - #ifdef RMT_PLATFORM_WINDOWS - #ifdef RMT_ARCH_64BIT - thread_profiler->registerBackup0 = context.Rbx; - thread_profiler->registerBackup1 = context.Rsi; - thread_profiler->registerBackup2 = context.Rdi; - context.Rbx = context.Rip; - context.Rsi = (rmtU64)GetCurrentProcessorNumber; - context.Rdi = (rmtU64)thread_profiler; - context.Rip = (DWORD64)thread_profilers->compiledSampleFn; - #endif - #ifdef RMT_ARCH_32BIT - thread_profiler->registerBackup0 = context.Ebx; - thread_profiler->registerBackup1 = context.Esi; - thread_profiler->registerBackup2 = context.Edi; - context.Ebx = context.Eip; - context.Esi = (rmtU32)GetCurrentProcessorNumber; - context.Edi = (rmtU32)thread_profiler; - context.Eip = (DWORD)&SampleCallback; - #endif - #endif - - rmtSetThreadContext(thread_handle, &context); - } - else - { - AtomicAddS32(&thread_profiler->nbSamplesWithoutCallback, -1); - } - } - - // While the thread is suspended take the chance to check for samples trees that may never complete - // Because SuspendThread on Windows is an async request, this needs to be placed at a point where the request completes - // Calling GetThreadContext will ensure the request is completed so this stall check is placed after that - if (RMT_ERROR_NONE != CheckForStallingSamples(&stalling_sample_tree, thread_profiler, sample_time_us)) - { - assert(stalling_sample_tree.allocator != NULL); - if (stalling_sample_tree.root != NULL) - { - FreeSamples(stalling_sample_tree.root, stalling_sample_tree.allocator); - } - } - - rmtResumeThread(thread_handle); - - if (stalling_sample_tree.root != NULL) - { - // If there is stalling sample tree on this thread then send it to listeners. - // Do the send *outside* of all Suspend/Resume calls as we have no way of knowing who is reading/writing the queue - // Mark this as partial so that the listeners know it will be overwritten. - Sample* sample = stalling_sample_tree.root->first_child; - assert(sample != NULL); - QueueSampleTree(thread_profilers->mqToRmtThread, sample, stalling_sample_tree.allocator, thread_profiler->threadName, 0, thread_profiler, RMT_TRUE); - - // The stalling_sample_tree.root->first_child has been sent to the main Remotery thread. This will get released later - // when the Remotery thread has processed it. This leaves the stalling_sample_tree.root here that must be freed. - // Before freeing the root sample we have to detach the children though. - stalling_sample_tree.root->first_child = NULL; - stalling_sample_tree.root->last_child = NULL; - stalling_sample_tree.root->nb_children = 0; - assert(stalling_sample_tree.allocator != NULL); - FreeSamples(stalling_sample_tree.root, stalling_sample_tree.allocator); - } - - - } while (lfsr_value != lfsr_seed); - - // Filter all processor samples made in this pass - for (processor_index = 0; processor_index < nb_processors; processor_index++) - { - Processor* processor = processors + processor_index; - ThreadProfiler* thread_profiler = processor->threadProfiler; - - if (thread_profiler != NULL) - { - // If this thread was on another processor on a previous pass and that processor is still tracking that thread, - // remove the thread from it. - rmtU32 last_processor_index = thread_profiler->lastProcessorIndex; - if (last_processor_index != (rmtU32)-1 && last_processor_index != processor_index) - { - assert(last_processor_index < nb_processors); - if (processors[last_processor_index].threadProfiler == thread_profiler) - { - processors[last_processor_index].threadProfiler = NULL; - } - } - - // When the thread is still on the same processor, check to see if it hasn't triggered the callback within another - // pass. This suggests the thread has gone to sleep and is no longer assigned to any thread. - else if (processor->sampleCount > 1) - { - processor->threadProfiler = NULL; - } - - thread_profiler->lastProcessorIndex = thread_profiler->processorIndex; - } - } - - // Send current processor state off to remotery - QueueProcessorThreads(thread_profilers->mqToRmtThread, processor_message_index++, nb_processors, processors); - } - - rmtDelete(rmtThread, thread_profilers->threadGatherThread); - -#ifdef RMT_ENABLE_THREAD_SAMPLER - timeEndPeriod(1); -#endif - - rmtFree(processors); - - return RMT_ERROR_NONE; -} - -/* ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- - @REMOTERY: Remotery ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- -*/ - -#if RMT_USE_OPENGL -typedef struct OpenGL_t OpenGL; -static rmtError OpenGL_Create(OpenGL** opengl); -static void OpenGL_Destructor(OpenGL* opengl); -#endif - -#if RMT_USE_METAL -typedef struct Metal_t Metal; -static rmtError Metal_Create(Metal** metal); -static void Metal_Destructor(Metal* metal); -#endif - -typedef struct PropertySnapshot -{ - // Inherit so that property states can be quickly allocated - ObjectLink Link; - - // Data copied from the property at the time of the snapshot - rmtPropertyType type; - rmtPropertyValue value; - rmtPropertyValue prevValue; - rmtU32 prevValueFrame; - rmtU32 nameHash; - rmtU32 uniqueID; - - // Depth calculated as part of the walk - rmtU8 depth; - - // Link to the next property snapshot - rmtU32 nbChildren; - struct PropertySnapshot* nextSnapshot; -} PropertySnapshot; - -typedef struct Msg_PropertySnapshot -{ - PropertySnapshot* rootSnapshot; - rmtU32 nbSnapshots; - rmtU32 propertyFrame; -} Msg_PropertySnapshot; - -static rmtError PropertySnapshot_Constructor(PropertySnapshot* snapshot) -{ - assert(snapshot != NULL); - - ObjectLink_Constructor((ObjectLink*)snapshot); - - snapshot->type = RMT_PropertyType_rmtBool; - snapshot->value.Bool = RMT_FALSE; - snapshot->nameHash = 0; - snapshot->uniqueID = 0; - snapshot->nbChildren = 0; - snapshot->depth = 0; - snapshot->nextSnapshot = NULL; - - return RMT_ERROR_NONE; -} - -static void PropertySnapshot_Destructor(PropertySnapshot* snapshot) -{ - RMT_UNREFERENCED_PARAMETER(snapshot); -} - -struct Remotery -{ - Server* server; - - // Microsecond accuracy timer for CPU timestamps - usTimer timer; - - // Queue between clients and main remotery thread - rmtMessageQueue* mq_to_rmt_thread; - - // The main server thread - rmtThread* thread; - - // String table shared by all threads - StringTable* string_table; - - // Open logfile handle to append events to - FILE* logfile; - - // Set to trigger a map of each message on the remotery thread message queue - void (*map_message_queue_fn)(Remotery* rmt, Message*); - void* map_message_queue_data; - -#if RMT_USE_CUDA - rmtCUDABind cuda; -#endif - -#if RMT_USE_OPENGL - OpenGL* opengl; -#endif - -#if RMT_USE_METAL - Metal* metal; -#endif - -#if RMT_USE_D3D12 - // Linked list of all D3D12 queue samplers - rmtMutex d3d12BindsMutex; - struct D3D12BindImpl* d3d12Binds; -#endif - - ThreadProfilers* threadProfilers; - - // Root of all registered properties, guarded by mutex as property register can come from any thread - rmtMutex propertyMutex; - rmtProperty rootProperty; - - // Allocator for property values that get sent to the viewer - ObjectAllocator* propertyAllocator; - - // Frame used to determine age of property changes - rmtU32 propertyFrame; -}; - -// -// Global remotery context -// -static Remotery* g_Remotery = NULL; - -// -// This flag marks the EXE/DLL that created the global remotery instance. We want to allow -// only the creating EXE/DLL to destroy the remotery instance. -// -static rmtBool g_RemoteryCreated = RMT_FALSE; - -static double saturate(double v) -{ - if (v < 0) - { - return 0; - } - if (v > 1) - { - return 1; - } - return v; -} - -static void PostProcessSamples(Sample* sample, rmtU32* nb_samples) -{ - Sample* child; - - assert(sample != NULL); - assert(nb_samples != NULL); - - (*nb_samples)++; - - { - // Hash integer line position to full hue - double h = (double)sample->name_hash / (double)0xFFFFFFFF; - double r = saturate(fabs(fmod(h * 6 + 0, 6) - 3) - 1); - double g = saturate(fabs(fmod(h * 6 + 4, 6) - 3) - 1); - double b = saturate(fabs(fmod(h * 6 + 2, 6) - 3) - 1); - - // Cubic smooth - r = r * r * (3 - 2 * r); - g = g * g * (3 - 2 * g); - b = b * b * (3 - 2 * b); - - // Lerp to HSV lightness a little - double k = 0.4; - r = r * k + (1 - k); - g = g * k + (1 - k); - b = b * k + (1 - k); - - // To RGB8 - sample->uniqueColour[0] = (rmtU8)maxS32(minS32((rmtS32)(r * 255), 255), 0); - sample->uniqueColour[1] = (rmtU8)maxS32(minS32((rmtS32)(g * 255), 255), 0); - sample->uniqueColour[2] = (rmtU8)maxS32(minS32((rmtS32)(b * 255), 255), 0); - - //rmtU32 hash = sample->name_hash; - //sample->uniqueColour[0] = 127 + ((hash & 255) >> 1); - //sample->uniqueColour[1] = 127 + (((hash >> 4) & 255) >> 1); - //sample->uniqueColour[2] = 127 + (((hash >> 8) & 255) >> 1); - } - - // Concatenate children - for (child = sample->first_child; child != NULL; child = child->next_sibling) - { - PostProcessSamples(child, nb_samples); - } -} - -static rmtError Remotery_SendLogTextMessage(Remotery* rmt, Message* message) -{ - rmtError error = RMT_ERROR_NONE; - Buffer* bin_buf; - rmtU32 write_start_offset; - - // Build the buffer as if it's being sent to the server - assert(rmt != NULL); - assert(message != NULL); - bin_buf = rmt->server->bin_buf; - WebSocket_PrepareBuffer(bin_buf); - rmtTry(bin_MessageHeader(bin_buf, "LOGM", &write_start_offset)); - rmtTry(Buffer_Write(bin_buf, message->payload, message->payload_size)); - rmtTry(bin_MessageFooter(bin_buf, write_start_offset)); - - // Pass to either the server or the log file - if (Server_IsClientConnected(rmt->server) == RMT_TRUE) - { - error = Server_Send(rmt->server, bin_buf->data, bin_buf->bytes_used, 20); - } - if (rmt->logfile != NULL) - { - rmtWriteFile(rmt->logfile, bin_buf->data + WEBSOCKET_MAX_FRAME_HEADER_SIZE, bin_buf->bytes_used - WEBSOCKET_MAX_FRAME_HEADER_SIZE); - } - - return error; -} - -static rmtError bin_SampleName(Buffer* buffer, const char* name, rmtU32 name_hash, rmtU32 name_length) -{ - rmtU32 write_start_offset; - rmtTry(bin_MessageHeader(buffer, "SSMP", &write_start_offset)); - rmtTry(Buffer_WriteU32(buffer, name_hash)); - rmtTry(Buffer_WriteU32(buffer, name_length)); - rmtTry(Buffer_Write(buffer, (void*)name, name_length)); - rmtTry(bin_MessageFooter(buffer, write_start_offset)); - - return RMT_ERROR_NONE; -} - -static rmtError Remotery_AddToStringTable(Remotery* rmt, Message* message) -{ - // Add to the string table - Msg_AddToStringTable* payload = (Msg_AddToStringTable*)message->payload; - const char* name = (const char*)(payload + 1); - rmtBool name_inserted = StringTable_Insert(rmt->string_table, payload->hash, name); - - // Emit to log file if one is open - if (name_inserted == RMT_TRUE && rmt->logfile != NULL) - { - Buffer* bin_buf = rmt->server->bin_buf; - bin_buf->bytes_used = 0; - rmtTry(bin_SampleName(bin_buf, name, payload->hash, payload->length)); - - rmtWriteFile(rmt->logfile, bin_buf->data, bin_buf->bytes_used); - } - - return RMT_ERROR_NONE; -} - -static rmtError bin_SampleTree(Buffer* buffer, Msg_SampleTree* msg) -{ - Sample* root_sample; - char thread_name[256]; - rmtU32 nb_samples = 0; - rmtU32 write_start_offset = 0; - - assert(buffer != NULL); - assert(msg != NULL); - - // Get the message root sample - root_sample = msg->rootSample; - assert(root_sample != NULL); - - // Add any sample types as a thread name post-fix to ensure they get their own viewer - thread_name[0] = 0; - strncat_s(thread_name, sizeof(thread_name), msg->threadName, strnlen_s(msg->threadName, 255)); - if (root_sample->type == RMT_SampleType_CUDA) - { - strncat_s(thread_name, sizeof(thread_name), " (CUDA)", 7); - } - if (root_sample->type == RMT_SampleType_D3D11) - { - strncat_s(thread_name, sizeof(thread_name), " (D3D11)", 8); - } - if (root_sample->type == RMT_SampleType_D3D12) - { - strncat_s(thread_name, sizeof(thread_name), " (D3D12)", 8); - } - if (root_sample->type == RMT_SampleType_OpenGL) - { - strncat_s(thread_name, sizeof(thread_name), " (OpenGL)", 9); - } - if (root_sample->type == RMT_SampleType_Metal) - { - strncat_s(thread_name, sizeof(thread_name), " (Metal)", 8); - } - - // Get digest hash of samples so that viewer can efficiently rebuild its tables - PostProcessSamples(root_sample, &nb_samples); - - // Write sample message header - rmtTry(bin_MessageHeader(buffer, "SMPL", &write_start_offset)); - rmtTry(Buffer_WriteStringWithLength(buffer, thread_name)); - rmtTry(Buffer_WriteU32(buffer, nb_samples)); - rmtTry(Buffer_WriteU32(buffer, msg->partialTree ? 1 : 0)); - - // Align serialised sample tree to 32-bit boundary - rmtTry(Buffer_AlignedPad(buffer, write_start_offset)); - - // Write entire sample tree - rmtTry(bin_Sample(buffer, root_sample, 0)); - - rmtTry(bin_MessageFooter(buffer, write_start_offset)); - - return RMT_ERROR_NONE; -} - -#if RMT_USE_CUDA -static rmtBool AreCUDASamplesReady(Sample* sample); -static rmtBool GetCUDASampleTimes(Sample* root_sample, Sample* sample); -#endif - -static rmtError Remotery_SendToViewerAndLog(Remotery* rmt, Buffer* bin_buf, rmtU32 timeout) -{ - rmtError error = RMT_ERROR_NONE; - - if (Server_IsClientConnected(rmt->server) == RMT_TRUE) - { - rmt_BeginCPUSample(Server_Send, RMTSF_Aggregate); - error = Server_Send(rmt->server, bin_buf->data, bin_buf->bytes_used, timeout); - rmt_EndCPUSample(); - } - - if (rmt->logfile != NULL) - { - // Write the data after the websocket header - rmtWriteFile(rmt->logfile, bin_buf->data + WEBSOCKET_MAX_FRAME_HEADER_SIZE, bin_buf->bytes_used - WEBSOCKET_MAX_FRAME_HEADER_SIZE); - } - - return error; -} - -static rmtError Remotery_SendSampleTreeMessage(Remotery* rmt, Message* message) -{ - rmtError error = RMT_ERROR_NONE; - - Msg_SampleTree* sample_tree; - Sample* sample; - Buffer* bin_buf; - - assert(rmt != NULL); - assert(message != NULL); - - // Get the message root sample - sample_tree = (Msg_SampleTree*)message->payload; - sample = sample_tree->rootSample; - assert(sample != NULL); - -#if RMT_USE_CUDA - if (sample->type == RMT_SampleType_CUDA) - { - // If these CUDA samples aren't ready yet, stick them to the back of the queue and continue - rmtBool are_samples_ready; - rmt_BeginCPUSample(AreCUDASamplesReady, 0); - are_samples_ready = AreCUDASamplesReady(sample); - rmt_EndCPUSample(); - if (!are_samples_ready) - { - QueueSampleTree(rmt->mq_to_rmt_thread, sample, sample_tree->allocator, sample_tree->threadName, - message->threadProfiler, RMT_FALSE); - return RMT_ERROR_NONE; - } - - // Retrieve timing of all CUDA samples - rmt_BeginCPUSample(GetCUDASampleTimes, 0); - GetCUDASampleTimes(sample->parent, sample); - rmt_EndCPUSample(); - } -#endif - - // Reset the buffer for sending a websocket message - bin_buf = rmt->server->bin_buf; - WebSocket_PrepareBuffer(bin_buf); - - // Serialise the sample tree - rmt_BeginCPUSample(bin_SampleTree, RMTSF_Aggregate); - error = bin_SampleTree(bin_buf, sample_tree); - rmt_EndCPUSample(); - - if (g_Settings.sampletree_handler != NULL) - { - g_Settings.sampletree_handler(g_Settings.sampletree_context, sample_tree); - } - - // Release sample tree samples back to their allocator - FreeSamples(sample, sample_tree->allocator); - - if (error != RMT_ERROR_NONE) - { - return error; - } - - // Send to the viewer with a reasonably long timeout as the size of the sample data may be large - return Remotery_SendToViewerAndLog(rmt, bin_buf, 50000); -} - -static rmtError Remotery_SendProcessorThreads(Remotery* rmt, Message* message) -{ - rmtU32 processor_index; - rmtError error = RMT_ERROR_NONE; - - Msg_ProcessorThreads* processor_threads = (Msg_ProcessorThreads*)message->payload; - - Buffer* bin_buf; - rmtU32 write_start_offset; - - // Reset the buffer for sending a websocket message - bin_buf = rmt->server->bin_buf; - WebSocket_PrepareBuffer(bin_buf); - - // Serialise the message - rmtTry(bin_MessageHeader(bin_buf, "PRTH", &write_start_offset)); - rmtTry(Buffer_WriteU32(bin_buf, processor_threads->nbProcessors)); - rmtTry(Buffer_WriteU64(bin_buf, processor_threads->messageIndex)); - for (processor_index = 0; processor_index < processor_threads->nbProcessors; processor_index++) - { - Processor* processor = processor_threads->processors + processor_index; - if (processor->threadProfiler != NULL) - { - rmtTry(Buffer_WriteU32(bin_buf, processor->threadProfiler->threadId)); - rmtTry(Buffer_WriteU32(bin_buf, processor->threadProfiler->threadNameHash)); - rmtTry(Buffer_WriteU64(bin_buf, processor->sampleTime)); - } - else - { - rmtTry(Buffer_WriteU32(bin_buf, (rmtU32)-1)); - rmtTry(Buffer_WriteU32(bin_buf, 0)); - rmtTry(Buffer_WriteU64(bin_buf, 0)); - } - } - - rmtTry(bin_MessageFooter(bin_buf, write_start_offset)); - - return Remotery_SendToViewerAndLog(rmt, bin_buf, 50); -} - -static void FreePropertySnapshots(PropertySnapshot* snapshot) -{ - // Allows root call to pass null - if (snapshot == NULL) - { - return; - } - - // Depth first free - if (snapshot->nextSnapshot != NULL) - { - FreePropertySnapshots(snapshot->nextSnapshot); - } - - ObjectAllocator_Free(g_Remotery->propertyAllocator, snapshot); -} - -static rmtError Remotery_SerialisePropertySnapshots(Buffer* bin_buf, Msg_PropertySnapshot* msg_snapshot) -{ - PropertySnapshot* snapshot; - rmtU8 empty_group[16] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; - rmtU32 write_start_offset; - - // Header - rmtTry(bin_MessageHeader(bin_buf, "PSNP", &write_start_offset)); - rmtTry(Buffer_WriteU32(bin_buf, msg_snapshot->nbSnapshots)); - rmtTry(Buffer_WriteU32(bin_buf, msg_snapshot->propertyFrame)); - - // Linearised snapshots - for (snapshot = msg_snapshot->rootSnapshot; snapshot != NULL; snapshot = snapshot->nextSnapshot) - { - rmtU8 colour_depth[4] = {0, 0, 0}; - - // Same place as samples so that the GPU renderer can easily pick them out - rmtTry(Buffer_WriteU32(bin_buf, snapshot->nameHash)); - rmtTry(Buffer_WriteU32(bin_buf, snapshot->uniqueID)); - - // 3 byte place holder for viewer-side colour, with snapshot depth packed next to it - colour_depth[3] = snapshot->depth; - rmtTry(Buffer_Write(bin_buf, colour_depth, 4)); - - // Dispatch on property type, but maintaining 64-bits per value - rmtTry(Buffer_WriteU32(bin_buf, snapshot->type)); - switch (snapshot->type) - { - // Empty - case RMT_PropertyType_rmtGroup: - rmtTry(Buffer_Write(bin_buf, empty_group, 16)); - break; - - // All value ranges here are double-representable, so convert them early in C where it's cheap - case RMT_PropertyType_rmtBool: - rmtTry(Buffer_WriteF64(bin_buf, snapshot->value.Bool)); - rmtTry(Buffer_WriteF64(bin_buf, snapshot->prevValue.Bool)); - break; - case RMT_PropertyType_rmtS32: - rmtTry(Buffer_WriteF64(bin_buf, snapshot->value.S32)); - rmtTry(Buffer_WriteF64(bin_buf, snapshot->prevValue.S32)); - break; - case RMT_PropertyType_rmtU32: - rmtTry(Buffer_WriteF64(bin_buf, snapshot->value.U32)); - rmtTry(Buffer_WriteF64(bin_buf, snapshot->prevValue.U32)); - break; - case RMT_PropertyType_rmtF32: - rmtTry(Buffer_WriteF64(bin_buf, snapshot->value.F32)); - rmtTry(Buffer_WriteF64(bin_buf, snapshot->prevValue.F32)); - break; - - // The high end of these are not double representable but store their full pattern so we don't lose data - case RMT_PropertyType_rmtS64: - case RMT_PropertyType_rmtU64: - rmtTry(Buffer_WriteU64(bin_buf, snapshot->value.U64)); - rmtTry(Buffer_WriteU64(bin_buf, snapshot->prevValue.U64)); - break; - - case RMT_PropertyType_rmtF64: - rmtTry(Buffer_WriteF64(bin_buf, snapshot->value.F64)); - rmtTry(Buffer_WriteF64(bin_buf, snapshot->prevValue.F64)); - break; - } - - rmtTry(Buffer_WriteU32(bin_buf, snapshot->prevValueFrame)); - rmtTry(Buffer_WriteU32(bin_buf, snapshot->nbChildren)); - } - - rmtTry(bin_MessageFooter(bin_buf, write_start_offset)); - - return RMT_ERROR_NONE; -} - -static rmtError Remotery_SendPropertySnapshot(Remotery* rmt, Message* message) -{ - Msg_PropertySnapshot* msg_snapshot = (Msg_PropertySnapshot*)message->payload; - - rmtError error = RMT_ERROR_NONE; - - Buffer* bin_buf; - - // Reset the buffer for sending a websocket message - bin_buf = rmt->server->bin_buf; - WebSocket_PrepareBuffer(bin_buf); - - // Serialise the message and send - error = Remotery_SerialisePropertySnapshots(bin_buf, msg_snapshot); - if (error == RMT_ERROR_NONE) - { - error = Remotery_SendToViewerAndLog(rmt, bin_buf, 50); - } - - FreePropertySnapshots(msg_snapshot->rootSnapshot); - - return error; -} - -static rmtError Remotery_ConsumeMessageQueue(Remotery* rmt) -{ - rmtU32 nb_messages_sent = 0; - const rmtU32 maxNbMessagesPerUpdate = g_Settings.maxNbMessagesPerUpdate; - - assert(rmt != NULL); - - // Loop reading the max number of messages for this update - // Note some messages don't consume the sent message count as they are small enough to not cause performance issues - while (nb_messages_sent < maxNbMessagesPerUpdate) - { - rmtError error = RMT_ERROR_NONE; - Message* message = rmtMessageQueue_PeekNextMessage(rmt->mq_to_rmt_thread); - if (message == NULL) - break; - - switch (message->id) - { - // This shouldn't be possible - case MsgID_NotReady: - assert(RMT_FALSE); - break; - - // Dispatch to message handler - case MsgID_AddToStringTable: - error = Remotery_AddToStringTable(rmt, message); - break; - case MsgID_LogText: - error = Remotery_SendLogTextMessage(rmt, message); - nb_messages_sent++; - break; - case MsgID_SampleTree: - rmt_BeginCPUSample(SendSampleTreeMessage, RMTSF_Aggregate); - error = Remotery_SendSampleTreeMessage(rmt, message); - nb_messages_sent++; - rmt_EndCPUSample(); - break; - case MsgID_ProcessorThreads: - Remotery_SendProcessorThreads(rmt, message); - nb_messages_sent++; - break; - case MsgID_PropertySnapshot: - error = Remotery_SendPropertySnapshot(rmt, message); - break; - - default: - break; - } - - // Consume the message before reacting to any errors - rmtMessageQueue_ConsumeNextMessage(rmt->mq_to_rmt_thread, message); - if (error != RMT_ERROR_NONE) - { - return error; - } - } - - return RMT_ERROR_NONE; -} - -static void Remotery_FlushMessageQueue(Remotery* rmt) -{ - assert(rmt != NULL); - - // Loop reading all remaining messages - for (;;) - { - Message* message = rmtMessageQueue_PeekNextMessage(rmt->mq_to_rmt_thread); - if (message == NULL) - break; - - switch (message->id) - { - // These can be safely ignored - case MsgID_NotReady: - case MsgID_AddToStringTable: - case MsgID_LogText: - break; - - // Release all samples back to their allocators - case MsgID_SampleTree: { - Msg_SampleTree* sample_tree = (Msg_SampleTree*)message->payload; - FreeSamples(sample_tree->rootSample, sample_tree->allocator); - break; - } - - case MsgID_PropertySnapshot: { - Msg_PropertySnapshot* msg_snapshot = (Msg_PropertySnapshot*)message->payload; - FreePropertySnapshots(msg_snapshot->rootSnapshot); - break; - } - - default: - break; - } - - rmtMessageQueue_ConsumeNextMessage(rmt->mq_to_rmt_thread, message); - } -} - -static void Remotery_MapMessageQueue(Remotery* rmt) -{ - rmtU32 read_pos, write_pos; - rmtMessageQueue* queue; - - assert(rmt != NULL); - - // Wait until the caller sets the custom data - while (LoadAcquirePointer((long* volatile*)&rmt->map_message_queue_data) == NULL) - msSleep(1); - - // Snapshot the current write position so that we're not constantly chasing other threads - // that can have no effect on the thread requesting the map. - queue = rmt->mq_to_rmt_thread; - write_pos = LoadAcquire(&queue->write_pos); - - // Walk every message in the queue and call the map function - read_pos = queue->read_pos; - while (read_pos < write_pos) - { - rmtU32 r = read_pos & (queue->size - 1); - Message* message = (Message*)(queue->data->ptr + r); - rmtU32 message_size = rmtMessageQueue_SizeForPayload(message->payload_size); - rmt->map_message_queue_fn(rmt, message); - read_pos += message_size; - } - - StoreReleasePointer((long* volatile*)&rmt->map_message_queue_data, NULL); -} - -static rmtError Remotery_ThreadMain(rmtThread* thread) -{ - Remotery* rmt = (Remotery*)thread->param; - assert(rmt != NULL); - - rmt_SetCurrentThreadName("Remotery"); - - while (thread->request_exit == RMT_FALSE) - { - rmt_BeginCPUSample(Wakeup, 0); - - rmt_BeginCPUSample(ServerUpdate, 0); - Server_Update(rmt->server); - rmt_EndCPUSample(); - - rmt_BeginCPUSample(ConsumeMessageQueue, 0); - Remotery_ConsumeMessageQueue(rmt); - rmt_EndCPUSample(); - - rmt_EndCPUSample(); - - // Process any queue map requests - if (LoadAcquirePointer((long* volatile*)&rmt->map_message_queue_fn) != NULL) - { - Remotery_MapMessageQueue(rmt); - StoreReleasePointer((long* volatile*)&rmt->map_message_queue_fn, NULL); - } - - // - // [NOTE-A] - // - // Possible sequence of user events at this point: - // - // 1. Add samples to the queue. - // 2. Shutdown remotery. - // - // This loop will exit with unrelease samples. - // - - msSleep(g_Settings.msSleepBetweenServerUpdates); - } - - // Release all samples to their allocators as a consequence of [NOTE-A] - Remotery_FlushMessageQueue(rmt); - - return RMT_ERROR_NONE; -} - -static rmtError Remotery_ReceiveMessage(void* context, char* message_data, rmtU32 message_length) -{ - Remotery* rmt = (Remotery*)context; - -// Manual dispatch on 4-byte message headers (message ID is little-endian encoded) -#define FOURCC(a, b, c, d) (rmtU32)(((d) << 24) | ((c) << 16) | ((b) << 8) | (a)) - rmtU32 message_id = *(rmtU32*)message_data; - - switch (message_id) - { - case FOURCC('C', 'O', 'N', 'I'): { - rmt_LogText("Console message received..."); - rmt_LogText(message_data + 4); - - // Pass on to any registered handler - if (g_Settings.input_handler != NULL) - g_Settings.input_handler(message_data + 4, g_Settings.input_handler_context); - - break; - } - - case FOURCC('G', 'S', 'M', 'P'): { - rmtPStr name; - - // Convert name hash to integer - rmtU32 name_hash = 0; - const char* cur = message_data + 4; - const char* end = cur + message_length - 4; - while (cur < end) - name_hash = name_hash * 10 + *cur++ - '0'; - - // Search for a matching string hash - name = StringTable_Find(rmt->string_table, name_hash); - if (name != NULL) - { - rmtU32 name_length = (rmtU32)strnlen_s_safe_c(name, 256 - 12); - - // Construct a response message containing the matching name - Buffer* bin_buf = rmt->server->bin_buf; - WebSocket_PrepareBuffer(bin_buf); - bin_SampleName(bin_buf, name, name_hash, name_length); - - // Send back immediately as we're on the server thread - return Server_Send(rmt->server, bin_buf->data, bin_buf->bytes_used, 10); - } - - break; - } - } - -#undef FOURCC - - return RMT_ERROR_NONE; -} - -static rmtError Remotery_Constructor(Remotery* rmt) -{ - assert(rmt != NULL); - - // Set default state - rmt->server = NULL; - rmt->mq_to_rmt_thread = NULL; - rmt->thread = NULL; - rmt->string_table = NULL; - rmt->logfile = NULL; - rmt->map_message_queue_fn = NULL; - rmt->map_message_queue_data = NULL; - rmt->threadProfilers = NULL; - mtxInit(&rmt->propertyMutex); - rmt->propertyAllocator = NULL; - rmt->propertyFrame = 0; - - // Set default state on the root property - rmtProperty* root_property = &rmt->rootProperty; - root_property->initialised = RMT_TRUE; - root_property->type = RMT_PropertyType_rmtGroup; - root_property->value.Bool = RMT_FALSE; - root_property->flags = RMT_PropertyFlags_NoFlags; - root_property->name = "Root Property"; - root_property->description = ""; - root_property->defaultValue.Bool = RMT_FALSE; - root_property->parent = NULL; - root_property->firstChild = NULL; - root_property->lastChild = NULL; - root_property->nextSibling = NULL; - root_property->nameHash = 0; - root_property->uniqueID = 0; - -#if RMT_USE_CUDA - rmt->cuda.CtxSetCurrent = NULL; - rmt->cuda.EventCreate = NULL; - rmt->cuda.EventDestroy = NULL; - rmt->cuda.EventElapsedTime = NULL; - rmt->cuda.EventQuery = NULL; - rmt->cuda.EventRecord = NULL; -#endif - -#if RMT_USE_OPENGL - rmt->opengl = NULL; -#endif - -#if RMT_USE_METAL - rmt->metal = NULL; -#endif - -#if RMT_USE_D3D12 - mtxInit(&rmt->d3d12BindsMutex); - rmt->d3d12Binds = NULL; -#endif - - // Kick-off the timer - usTimer_Init(&rmt->timer); - - // Create the server - rmtTryNew(Server, rmt->server, g_Settings.port, g_Settings.reuse_open_port, g_Settings.limit_connections_to_localhost); - - // Setup incoming message handler - rmt->server->receive_handler = Remotery_ReceiveMessage; - rmt->server->receive_handler_context = rmt; - - // Create the main message thread with only one page - rmtTryNew(rmtMessageQueue, rmt->mq_to_rmt_thread, g_Settings.messageQueueSizeInBytes); - - // Create sample name string table - rmtTryNew(StringTable, rmt->string_table); - - if (g_Settings.logPath != NULL) - { - // Get current date/time - struct tm* now_tm = TimeDateNow(); - - // Start the log path off - char filename[512] = { 0 }; - strncat_s(filename, sizeof(filename), g_Settings.logPath, 512); - strncat_s(filename, sizeof(filename), "/remotery-log-", 14); - - // Append current date and time - strncat_s(filename, sizeof(filename), itoa_s(now_tm->tm_year + 1900), 11); - strncat_s(filename, sizeof(filename), "-", 1); - strncat_s(filename, sizeof(filename), itoa_s(now_tm->tm_mon + 1), 11); - strncat_s(filename, sizeof(filename), "-", 1); - strncat_s(filename, sizeof(filename), itoa_s(now_tm->tm_mday), 11); - strncat_s(filename, sizeof(filename), "-", 1); - strncat_s(filename, sizeof(filename), itoa_s(now_tm->tm_hour), 11); - strncat_s(filename, sizeof(filename), "-", 1); - strncat_s(filename, sizeof(filename), itoa_s(now_tm->tm_min), 11); - strncat_s(filename, sizeof(filename), "-", 1); - strncat_s(filename, sizeof(filename), itoa_s(now_tm->tm_sec), 11); - - // Just append a custom extension - strncat_s(filename, sizeof(filename), ".rbin", 5); - - // Open and assume any failure simply sets NULL and the file isn't written - rmt->logfile = rmtOpenFile(filename, "w"); - - // Write the header - if (rmt->logfile != NULL) - { - rmtWriteFile(rmt->logfile, "RMTBLOGF", 8); - } - } - -#if RMT_USE_OPENGL - rmtTry(OpenGL_Create(&rmt->opengl)); -#endif - -#if RMT_USE_METAL - rmtTry(Metal_Create(&rmt->metal)); -#endif - - // Create the thread profilers container - rmtTryNew(ThreadProfilers, rmt->threadProfilers, &rmt->timer, rmt->mq_to_rmt_thread); - - // Create the property state allocator - rmtTryNew(ObjectAllocator, rmt->propertyAllocator, sizeof(PropertySnapshot), (ObjConstructor)PropertySnapshot_Constructor, (ObjDestructor)PropertySnapshot_Destructor); - - // Set as the global instance before creating any threads that uses it for sampling itself - assert(g_Remotery == NULL); - g_Remotery = rmt; - g_RemoteryCreated = RMT_TRUE; - - // Ensure global instance writes complete before other threads get a chance to use it - CompilerWriteFence(); - - // Create the main update thread once everything has been defined for the global remotery object - rmtTryNew(rmtThread, rmt->thread, Remotery_ThreadMain, rmt); - - return RMT_ERROR_NONE; -} - -static void Remotery_Destructor(Remotery* rmt) -{ - assert(rmt != NULL); - - // Join the remotery thread before clearing the global object as the thread is profiling itself - rmtDelete(rmtThread, rmt->thread); - - if (g_RemoteryCreated) - { - g_Remotery = NULL; - g_RemoteryCreated = RMT_FALSE; - } - - rmtDelete(ObjectAllocator, rmt->propertyAllocator); - - - rmtDelete(ThreadProfilers, rmt->threadProfilers); - -#if RMT_USE_D3D12 - while (rmt->d3d12Binds != NULL) - { - _rmt_UnbindD3D12((rmtD3D12Bind*)rmt->d3d12Binds); - } - mtxDelete(&rmt->d3d12BindsMutex); -#endif - -#if RMT_USE_OPENGL - rmtDelete(OpenGL, rmt->opengl); -#endif - -#if RMT_USE_METAL - rmtDelete(Metal, rmt->metal); -#endif - - rmtCloseFile(rmt->logfile); - - rmtDelete(StringTable, rmt->string_table); - rmtDelete(rmtMessageQueue, rmt->mq_to_rmt_thread); - - rmtDelete(Server, rmt->server); - - // Free the error message TLS - // TODO(don): The allocated messages will need to be freed as well - if (g_lastErrorMessageTlsHandle != TLS_INVALID_HANDLE) - { - tlsFree(g_lastErrorMessageTlsHandle); - g_lastErrorMessageTlsHandle = TLS_INVALID_HANDLE; - } - - mtxDelete(&rmt->propertyMutex); -} - -static void* CRTMalloc(void* mm_context, rmtU32 size) -{ - RMT_UNREFERENCED_PARAMETER(mm_context); - return malloc((size_t)size); -} - -static void CRTFree(void* mm_context, void* ptr) -{ - RMT_UNREFERENCED_PARAMETER(mm_context); - free(ptr); -} - -static void* CRTRealloc(void* mm_context, void* ptr, rmtU32 size) -{ - RMT_UNREFERENCED_PARAMETER(mm_context); - return realloc(ptr, size); -} - -RMT_API rmtSettings* _rmt_Settings(void) -{ - // Default-initialize on first call - if (g_SettingsInitialized == RMT_FALSE) - { - g_Settings.port = 0x4597; - g_Settings.reuse_open_port = RMT_FALSE; - g_Settings.limit_connections_to_localhost = RMT_FALSE; - g_Settings.enableThreadSampler = RMT_TRUE; - g_Settings.msSleepBetweenServerUpdates = 4; - g_Settings.messageQueueSizeInBytes = 1024 * 1024; - g_Settings.maxNbMessagesPerUpdate = 1000; - g_Settings.malloc = CRTMalloc; - g_Settings.free = CRTFree; - g_Settings.realloc = CRTRealloc; - g_Settings.input_handler = NULL; - g_Settings.input_handler_context = NULL; - g_Settings.logPath = NULL; - g_Settings.sampletree_handler = NULL; - g_Settings.sampletree_context = NULL; - g_Settings.snapshot_callback = NULL; - g_Settings.snapshot_context = NULL; - - g_SettingsInitialized = RMT_TRUE; - } - - return &g_Settings; -} - -RMT_API rmtError _rmt_CreateGlobalInstance(Remotery** remotery) -{ - // Ensure load/acquire store/release operations match this enum size - assert(sizeof(MessageID) == sizeof(rmtU32)); - - // Default-initialise if user has not set values - rmt_Settings(); - - // Creating the Remotery instance also records it as the global instance - assert(remotery != NULL); - rmtTryNew(Remotery, *remotery); - return RMT_ERROR_NONE; -} - -RMT_API void _rmt_DestroyGlobalInstance(Remotery* remotery) -{ - // Ensure this is the module that created it - assert(g_RemoteryCreated == RMT_TRUE); - assert(g_Remotery == remotery); - rmtDelete(Remotery, remotery); -} - -RMT_API void _rmt_SetGlobalInstance(Remotery* remotery) -{ - // Default-initialise if user has not set values - rmt_Settings(); - - g_Remotery = remotery; -} - -RMT_API Remotery* _rmt_GetGlobalInstance(void) -{ - return g_Remotery; -} - -#ifdef RMT_PLATFORM_WINDOWS -#pragma pack(push, 8) -typedef struct tagTHREADNAME_INFO -{ - DWORD dwType; // Must be 0x1000. - LPCSTR szName; // Pointer to name (in user addr space). - DWORD dwThreadID; // Thread ID (-1=caller thread). - DWORD dwFlags; // Reserved for future use, must be zero. -} THREADNAME_INFO; -#pragma pack(pop) -#endif - -wchar_t* MakeWideString(const char* string) -{ - size_t wlen; - wchar_t* wstr; - - // First get the converted length -#if defined(RMT_PLATFORM_WINDOWS) && !RMT_USE_TINYCRT - if (mbstowcs_s(&wlen, NULL, 0, string, INT_MAX) != 0) - { - return NULL; - } -#else - wlen = mbstowcs(NULL, string, 256); -#endif - - // Allocate enough words for the converted result - wstr = (wchar_t*)(rmtMalloc((wlen + 1) * sizeof(wchar_t))); - if (wstr == NULL) - { - return NULL; - } - - // Convert -#if defined(RMT_PLATFORM_WINDOWS) && !RMT_USE_TINYCRT - if (mbstowcs_s(&wlen, wstr, wlen + 1, string, wlen) != 0) -#else - if (mbstowcs(wstr, string, wlen + 1) != wlen) -#endif - { - rmtFree(wstr); - return NULL; - } - - return wstr; -} - -static void SetDebuggerThreadName(const char* name) -{ -#ifdef RMT_PLATFORM_WINDOWS - THREADNAME_INFO info; - - // See if SetThreadDescription is available in this version of Windows - // Introduced in Windows 10 build 1607 - HMODULE kernel32 = GetModuleHandleA("Kernel32.dll"); - if (kernel32 != NULL) - { - typedef HRESULT(WINAPI* SETTHREADDESCRIPTION)(HANDLE hThread, PCWSTR lpThreadDescription); - SETTHREADDESCRIPTION SetThreadDescription = (SETTHREADDESCRIPTION)GetProcAddress(kernel32, "SetThreadDescription"); - if (SetThreadDescription != NULL) - { - // Create a wide-string version of the thread name - wchar_t* wstr = MakeWideString(name); - if (wstr != NULL) - { - // Set and return, leaving a fall-through for any failure cases to use the old exception method - SetThreadDescription(GetCurrentThread(), wstr); - rmtFree(wstr); - return; - } - } - } - - info.dwType = 0x1000; - info.szName = name; - info.dwThreadID = (DWORD)-1; - info.dwFlags = 0; - -#ifndef __MINGW32__ - __try - { - RaiseException(0x406D1388, 0, sizeof(info) / sizeof(ULONG_PTR), (ULONG_PTR*)&info); - } - __except (1 /* EXCEPTION_EXECUTE_HANDLER */) - { - } -#endif -#else - RMT_UNREFERENCED_PARAMETER(name); -#endif - -#ifdef RMT_PLATFORM_LINUX - // pthread_setname_np is a non-standard GNU extension. - char name_clamp[16]; - name_clamp[0] = 0; - strncat_s(name_clamp, sizeof(name_clamp), name, 15); -#if defined(__FreeBSD__) || defined(__OpenBSD__) - pthread_set_name_np(pthread_self(), name_clamp); -#else - prctl(PR_SET_NAME, name_clamp, 0, 0, 0); -#endif -#endif -} - -RMT_API void _rmt_SetCurrentThreadName(rmtPStr thread_name) -{ - ThreadProfiler* thread_profiler; - rmtU32 name_length; - - if (g_Remotery == NULL) - { - return; - } - - // Get data for this thread - if (ThreadProfilers_GetCurrentThreadProfiler(g_Remotery->threadProfilers, &thread_profiler) != RMT_ERROR_NONE) - { - return; - } - - // Copy name and apply to the debugger - strcpy_s(thread_profiler->threadName, sizeof(thread_profiler->threadName), thread_name); - thread_profiler->threadNameHash = _rmt_HashString32(thread_name, strnlen_s(thread_name, 64), 0); - SetDebuggerThreadName(thread_name); - - // Send the thread name for lookup -#ifdef RMT_PLATFORM_WINDOWS - name_length = strnlen_s(thread_profiler->threadName, 64); - QueueAddToStringTable(g_Remotery->mq_to_rmt_thread, thread_profiler->threadNameHash, thread_name, name_length, NULL); -#endif -} - -static rmtBool QueueLine(rmtMessageQueue* queue, unsigned char* text, rmtU32 size, struct ThreadProfiler* thread_profiler) -{ - Message* message; - rmtU32 text_size; - - assert(queue != NULL); - - // Patch line size - text_size = size - 4; - U32ToByteArray(text, text_size); - - // Allocate some space for the line - message = rmtMessageQueue_AllocMessage(queue, size, thread_profiler); - if (message == NULL) - return RMT_FALSE; - - // Copy the text and commit the message - memcpy(message->payload, text, size); - rmtMessageQueue_CommitMessage(message, MsgID_LogText); - - return RMT_TRUE; -} - -RMT_API void _rmt_LogText(rmtPStr text) -{ - int start_offset, offset, i; - unsigned char line_buffer[1024] = {0}; - ThreadProfiler* thread_profiler; - - if (g_Remotery == NULL) - return; - - if (ThreadProfilers_GetCurrentThreadProfiler(g_Remotery->threadProfilers, &thread_profiler) != RMT_ERROR_NONE) - { - return; - } - - // Start with empty line size - // Fill with spaces to enable viewing line_buffer without offset in a debugger - // (will be overwritten later by QueueLine/rmtMessageQueue_AllocMessage) - line_buffer[0] = ' '; - line_buffer[1] = ' '; - line_buffer[2] = ' '; - line_buffer[3] = ' '; - start_offset = 4; - - // There might be newlines in the buffer, so split them into multiple network calls - offset = start_offset; - for (i = 0; text[i] != 0; i++) - { - char c = text[i]; - - // Line wrap when too long or newline encountered - if (offset == sizeof(line_buffer) - 1 || c == '\n') - { - // Send the line up to now - if (QueueLine(g_Remotery->mq_to_rmt_thread, line_buffer, offset, thread_profiler) == RMT_FALSE) - return; - - // Restart line - offset = start_offset; - - // Don't add the newline character (if this was the reason for the flush) - // to the restarted line_buffer, let's skip it - if (c == '\n') - continue; - } - - line_buffer[offset++] = c; - } - - // Send the last line - if (offset > start_offset) - { - assert(offset < (int)sizeof(line_buffer)); - QueueLine(g_Remotery->mq_to_rmt_thread, line_buffer, offset, thread_profiler); - } -} - -RMT_API void _rmt_BeginCPUSample(rmtPStr name, rmtU32 flags, rmtU32* hash_cache) -{ - // 'hash_cache' stores a pointer to a sample name's hash value. Internally this is used to identify unique - // callstacks and it would be ideal that it's not recalculated each time the sample is used. This can be statically - // cached at the point of call or stored elsewhere when dynamic names are required. - // - // If 'hash_cache' is NULL then this call becomes more expensive, as it has to recalculate the hash of the name. - - ThreadProfiler* thread_profiler; - - if (g_Remotery == NULL) - return; - - // TODO: Time how long the bits outside here cost and subtract them from the parent - - if (ThreadProfilers_GetCurrentThreadProfiler(g_Remotery->threadProfilers, &thread_profiler) == RMT_ERROR_NONE) - { - Sample* sample; - rmtU32 name_hash = ThreadProfiler_GetNameHash(thread_profiler, g_Remotery->mq_to_rmt_thread, name, hash_cache); - if (ThreadProfiler_Push(thread_profiler->sampleTrees[RMT_SampleType_CPU], name_hash, flags, &sample) == RMT_ERROR_NONE) - { - // If this is an aggregate sample, store the time in 'end' as we want to preserve 'start' - if (sample->call_count > 1) - sample->us_end = usTimer_Get(&g_Remotery->timer); - else - sample->us_start = usTimer_Get(&g_Remotery->timer); - } - } -} - -RMT_API void _rmt_EndCPUSample(void) -{ - ThreadProfiler* thread_profiler; - - if (g_Remotery == NULL) - return; - - if (ThreadProfilers_GetCurrentThreadProfiler(g_Remotery->threadProfilers, &thread_profiler) == RMT_ERROR_NONE) - { - Sample* sample = thread_profiler->sampleTrees[RMT_SampleType_CPU]->currentParent; - - if (sample->recurse_depth > 0) - { - sample->recurse_depth--; - } - else - { - rmtU64 us_end = usTimer_Get(&g_Remotery->timer); - Sample_Close(sample, us_end); - ThreadProfiler_Pop(thread_profiler, g_Remotery->mq_to_rmt_thread, sample, 0); - } - } -} - -#if RMT_USE_D3D12 -static rmtError D3D12MarkFrame(struct D3D12BindImpl* bind); -#endif - -RMT_API rmtError _rmt_MarkFrame(void) -{ - if (g_Remotery == NULL) - { - return RMT_ERROR_REMOTERY_NOT_CREATED; - } - - #if RMT_USE_D3D12 - // This will kick off mark frames on the complete chain of binds - rmtTry(D3D12MarkFrame(g_Remotery->d3d12Binds)); - #endif - - return RMT_ERROR_NONE; -} - -#if RMT_USE_OPENGL || RMT_USE_D3D11 || RMT_USE_D3D12 -static void Remotery_DeleteSampleTree(Remotery* rmt, enum rmtSampleType sample_type) -{ - ThreadProfiler* thread_profiler; - - // Get the attached thread sampler and delete the sample tree - assert(rmt != NULL); - if (ThreadProfilers_GetCurrentThreadProfiler(rmt->threadProfilers, &thread_profiler) == RMT_ERROR_NONE) - { - SampleTree* sample_tree = thread_profiler->sampleTrees[sample_type]; - if (sample_tree != NULL) - { - rmtDelete(SampleTree, sample_tree); - thread_profiler->sampleTrees[sample_type] = NULL; - } - } -} - -static rmtBool rmtMessageQueue_IsEmpty(rmtMessageQueue* queue) -{ - assert(queue != NULL); - return queue->write_pos - queue->read_pos == 0; -} - -typedef struct -{ - rmtSampleType sample_type; - Buffer* flush_samples; -} GatherQueuedSampleData; - -static void MapMessageQueueAndWait(Remotery* rmt, void (*map_message_queue_fn)(Remotery* rmt, Message*), void* data) -{ - // Basic spin lock on the map function itself - while (AtomicCompareAndSwapPointer((long* volatile*)&rmt->map_message_queue_fn, NULL, - (long*)map_message_queue_fn) == RMT_FALSE) - msSleep(1); - - StoreReleasePointer((long* volatile*)&rmt->map_message_queue_data, (long*)data); - - // Wait until map completes - while (LoadAcquirePointer((long* volatile*)&rmt->map_message_queue_fn) != NULL) - msSleep(1); -} - -static void GatherQueuedSamples(Remotery* rmt, Message* message) -{ - GatherQueuedSampleData* gather_data = (GatherQueuedSampleData*)rmt->map_message_queue_data; - - // Filter sample trees - if (message->id == MsgID_SampleTree) - { - Msg_SampleTree* sample_tree = (Msg_SampleTree*)message->payload; - Sample* sample = sample_tree->rootSample; - if (sample->type == gather_data->sample_type) - { - // Make a copy of the entire sample tree as the remotery thread may overwrite it while - // the calling thread tries to delete - rmtU32 message_size = rmtMessageQueue_SizeForPayload(message->payload_size); - Buffer_Write(gather_data->flush_samples, message, message_size); - - // Mark the message empty - message->id = MsgID_None; - } - } -} - -static void FreePendingSampleTrees(Remotery* rmt, rmtSampleType sample_type, Buffer* flush_samples) -{ - rmtU8* data; - rmtU8* data_end; - - // Gather all sample trees currently queued for the Remotery thread - GatherQueuedSampleData gather_data; - gather_data.sample_type = sample_type; - gather_data.flush_samples = flush_samples; - MapMessageQueueAndWait(rmt, GatherQueuedSamples, &gather_data); - - // Release all sample trees to their allocators - data = flush_samples->data; - data_end = data + flush_samples->bytes_used; - while (data < data_end) - { - Message* message = (Message*)data; - rmtU32 message_size = rmtMessageQueue_SizeForPayload(message->payload_size); - Msg_SampleTree* sample_tree = (Msg_SampleTree*)message->payload; - FreeSamples(sample_tree->rootSample, sample_tree->allocator); - data += message_size; - } -} - -#endif - -/* ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- - @CUDA: CUDA event sampling ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- -*/ - -#if RMT_USE_CUDA - -typedef struct CUDASample -{ - // IS-A inheritance relationship - Sample base; - - // Pair of events that wrap the sample - CUevent event_start; - CUevent event_end; - -} CUDASample; - -static rmtError MapCUDAResult(CUresult result) -{ - switch (result) - { - case CUDA_SUCCESS: - return RMT_ERROR_NONE; - case CUDA_ERROR_DEINITIALIZED: - return RMT_ERROR_CUDA_DEINITIALIZED; - case CUDA_ERROR_NOT_INITIALIZED: - return RMT_ERROR_CUDA_NOT_INITIALIZED; - case CUDA_ERROR_INVALID_CONTEXT: - return RMT_ERROR_CUDA_INVALID_CONTEXT; - case CUDA_ERROR_INVALID_VALUE: - return RMT_ERROR_CUDA_INVALID_VALUE; - case CUDA_ERROR_INVALID_HANDLE: - return RMT_ERROR_CUDA_INVALID_HANDLE; - case CUDA_ERROR_OUT_OF_MEMORY: - return RMT_ERROR_CUDA_OUT_OF_MEMORY; - case CUDA_ERROR_NOT_READY: - return RMT_ERROR_ERROR_NOT_READY; - default: - return RMT_ERROR_CUDA_UNKNOWN; - } -} - -#define CUDA_MAKE_FUNCTION(name, params) \ - typedef CUresult(CUDAAPI* name##Ptr) params; \ - name##Ptr name = (name##Ptr)g_Remotery->cuda.name; - -#define CUDA_GUARD(call) \ - { \ - rmtError error = call; \ - if (error != RMT_ERROR_NONE) \ - return error; \ - } - -// Wrappers around CUDA driver functions that manage the active context. -static rmtError CUDASetContext(void* context) -{ - CUDA_MAKE_FUNCTION(CtxSetCurrent, (CUcontext ctx)); - assert(CtxSetCurrent != NULL); - return MapCUDAResult(CtxSetCurrent((CUcontext)context)); -} -static rmtError CUDAGetContext(void** context) -{ - CUDA_MAKE_FUNCTION(CtxGetCurrent, (CUcontext * ctx)); - assert(CtxGetCurrent != NULL); - return MapCUDAResult(CtxGetCurrent((CUcontext*)context)); -} -static rmtError CUDAEnsureContext() -{ - void* current_context; - CUDA_GUARD(CUDAGetContext(¤t_context)); - - assert(g_Remotery != NULL); - if (current_context != g_Remotery->cuda.context) - CUDA_GUARD(CUDASetContext(g_Remotery->cuda.context)); - - return RMT_ERROR_NONE; -} - -// Wrappers around CUDA driver functions that manage events -static rmtError CUDAEventCreate(CUevent* phEvent, unsigned int Flags) -{ - CUDA_MAKE_FUNCTION(EventCreate, (CUevent * phEvent, unsigned int Flags)); - CUDA_GUARD(CUDAEnsureContext()); - return MapCUDAResult(EventCreate(phEvent, Flags)); -} -static rmtError CUDAEventDestroy(CUevent hEvent) -{ - CUDA_MAKE_FUNCTION(EventDestroy, (CUevent hEvent)); - CUDA_GUARD(CUDAEnsureContext()); - return MapCUDAResult(EventDestroy(hEvent)); -} -static rmtError CUDAEventRecord(CUevent hEvent, void* hStream) -{ - CUDA_MAKE_FUNCTION(EventRecord, (CUevent hEvent, CUstream hStream)); - CUDA_GUARD(CUDAEnsureContext()); - return MapCUDAResult(EventRecord(hEvent, (CUstream)hStream)); -} -static rmtError CUDAEventQuery(CUevent hEvent) -{ - CUDA_MAKE_FUNCTION(EventQuery, (CUevent hEvent)); - CUDA_GUARD(CUDAEnsureContext()); - return MapCUDAResult(EventQuery(hEvent)); -} -static rmtError CUDAEventElapsedTime(float* pMilliseconds, CUevent hStart, CUevent hEnd) -{ - CUDA_MAKE_FUNCTION(EventElapsedTime, (float* pMilliseconds, CUevent hStart, CUevent hEnd)); - CUDA_GUARD(CUDAEnsureContext()); - return MapCUDAResult(EventElapsedTime(pMilliseconds, hStart, hEnd)); -} - -static rmtError CUDASample_Constructor(CUDASample* sample) -{ - rmtError error; - - assert(sample != NULL); - - // Chain to sample constructor - Sample_Constructor((Sample*)sample); - sample->base.type = RMT_SampleType_CUDA; - sample->event_start = NULL; - sample->event_end = NULL; - - // Create non-blocking events with timing - assert(g_Remotery != NULL); - error = CUDAEventCreate(&sample->event_start, CU_EVENT_DEFAULT); - if (error == RMT_ERROR_NONE) - error = CUDAEventCreate(&sample->event_end, CU_EVENT_DEFAULT); - return error; -} - -static void CUDASample_Destructor(CUDASample* sample) -{ - assert(sample != NULL); - - // Destroy events - if (sample->event_start != NULL) - CUDAEventDestroy(sample->event_start); - if (sample->event_end != NULL) - CUDAEventDestroy(sample->event_end); - - Sample_Destructor((Sample*)sample); -} - -static rmtBool AreCUDASamplesReady(Sample* sample) -{ - rmtError error; - Sample* child; - - CUDASample* cuda_sample = (CUDASample*)sample; - assert(sample->type == RMT_SampleType_CUDA); - - // Check to see if both of the CUDA events have been processed - error = CUDAEventQuery(cuda_sample->event_start); - if (error != RMT_ERROR_NONE) - return RMT_FALSE; - error = CUDAEventQuery(cuda_sample->event_end); - if (error != RMT_ERROR_NONE) - return RMT_FALSE; - - // Check child sample events - for (child = sample->first_child; child != NULL; child = child->next_sibling) - { - if (!AreCUDASamplesReady(child)) - return RMT_FALSE; - } - - return RMT_TRUE; -} - -static rmtBool GetCUDASampleTimes(Sample* root_sample, Sample* sample) -{ - Sample* child; - - CUDASample* cuda_root_sample = (CUDASample*)root_sample; - CUDASample* cuda_sample = (CUDASample*)sample; - - float ms_start, ms_end; - - assert(root_sample != NULL); - assert(sample != NULL); - - // Get millisecond timing of each sample event, relative to initial root sample - if (CUDAEventElapsedTime(&ms_start, cuda_root_sample->event_start, cuda_sample->event_start) != RMT_ERROR_NONE) - return RMT_FALSE; - if (CUDAEventElapsedTime(&ms_end, cuda_root_sample->event_start, cuda_sample->event_end) != RMT_ERROR_NONE) - return RMT_FALSE; - - // Convert to microseconds and add to the sample - sample->us_start = (rmtU64)(ms_start * 1000); - sample->us_end = (rmtU64)(ms_end * 1000); - sample->us_length = sample->us_end - sample->us_start; - - // Get child sample times - for (child = sample->first_child; child != NULL; child = child->next_sibling) - { - if (!GetCUDASampleTimes(root_sample, child)) - return RMT_FALSE; - } - - return RMT_TRUE; -} - -RMT_API void _rmt_BindCUDA(const rmtCUDABind* bind) -{ - assert(bind != NULL); - if (g_Remotery != NULL) - g_Remotery->cuda = *bind; -} - -RMT_API void _rmt_BeginCUDASample(rmtPStr name, rmtU32* hash_cache, void* stream) -{ - ThreadProfiler* thread_profiler; - - if (g_Remotery == NULL) - return; - - if (ThreadProfilers_GetCurrentThreadProfiler(g_Remotery->threadProfilers, &thread_profiler) == RMT_ERROR_NONE) - { - rmtError error; - Sample* sample; - rmtU32 name_hash = ThreadProfiler_GetNameHash(thread_profiler, g_Remotery->mq_to_rmt_thread, name, hash_cache); - - // Create the CUDA tree on-demand as the tree needs an up-front-created root. - // This is not possible to create on initialisation as a CUDA binding is not yet available. - SampleTree** cuda_tree = &thread_profiler->sampleTrees[RMT_SampleType_CUDA]; - if (*cuda_tree == NULL) - { - CUDASample* root_sample; - - rmtTryNew(SampleTree, *cuda_tree, sizeof(CUDASample), (ObjConstructor)CUDASample_Constructor, - (ObjDestructor)CUDASample_Destructor); - if (error != RMT_ERROR_NONE) - return; - - // Record an event once on the root sample, used to measure absolute sample - // times since this point - root_sample = (CUDASample*)(*cuda_tree)->root; - error = CUDAEventRecord(root_sample->event_start, stream); - if (error != RMT_ERROR_NONE) - return; - } - - // Push the same and record its event - if (ThreadProfiler_Push(*cuda_tree, name_hash, 0, &sample) == RMT_ERROR_NONE) - { - CUDASample* cuda_sample = (CUDASample*)sample; - cuda_sample->base.usGpuIssueOnCpu = usTimer_Get(&g_Remotery->timer); - CUDAEventRecord(cuda_sample->event_start, stream); - } - } -} - -RMT_API void _rmt_EndCUDASample(void* stream) -{ - ThreadProfiler* thread_profiler; - - if (g_Remotery == NULL) - return; - - if (ThreadProfilers_GetCurrentThreadProfiler(g_Remotery->threadProfilers, &thread_profiler) == RMT_ERROR_NONE) - { - CUDASample* sample = (CUDASample*)thread_profiler->sampleTrees[RMT_SampleType_CUDA]->currentParent; - if (sample->base.recurse_depth > 0) - { - sample->base.recurse_depth--; - } - else - { - CUDAEventRecord(sample->event_end, stream); - ThreadProfiler_Pop(thread_profiler, g_Remotery->mq_to_rmt_thread, (Sample*)sample, 0); - } - } -} - -#endif // RMT_USE_CUDA - -/* ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- - @D3D11: Direct3D 11 event sampling ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- -*/ - -#if RMT_USE_D3D11 - -// As clReflect has no way of disabling C++ compile mode, this forces C interfaces everywhere... -#define CINTERFACE - -// ...unfortunately these C++ helpers aren't wrapped by the same macro but they can be disabled individually -#define D3D11_NO_HELPERS - -// Allow use of the D3D11 helper macros for accessing the C-style vtable -#define COBJMACROS - -#ifdef _MSC_VER -// Disable for d3d11.h -// warning C4201: nonstandard extension used : nameless struct/union -#pragma warning(push) -#pragma warning(disable : 4201) -#endif - -#include - -#ifdef _MSC_VER -#pragma warning(pop) -#endif - -typedef struct D3D11 -{ - // Context set by user - ID3D11Device* device; - ID3D11DeviceContext* context; - - HRESULT last_error; - - // Queue to the D3D 11 main update thread - // Given that BeginSample/EndSample need to be called from the same thread that does the update, there - // is really no need for this to be a thread-safe queue. I'm using it for its convenience. - rmtMessageQueue* mq_to_d3d11_main; - - // Mark the first time so that remaining timestamps are offset from this - rmtU64 first_timestamp; - // Last time in us (CPU time, via usTimer_Get) since we last resync'ed CPU & GPU - rmtU64 last_resync; - - // Sample trees in transit in the message queue for release on shutdown - Buffer* flush_samples; -} D3D11; - -static rmtError D3D11_Create(D3D11** d3d11) -{ - assert(d3d11 != NULL); - - // Allocate space for the D3D11 data - rmtTryMalloc(D3D11, *d3d11); - - // Set defaults - (*d3d11)->device = NULL; - (*d3d11)->context = NULL; - (*d3d11)->last_error = S_OK; - (*d3d11)->mq_to_d3d11_main = NULL; - (*d3d11)->first_timestamp = 0; - (*d3d11)->last_resync = 0; - (*d3d11)->flush_samples = NULL; - - rmtTryNew(rmtMessageQueue, (*d3d11)->mq_to_d3d11_main, g_Settings.messageQueueSizeInBytes); - rmtTryNew(Buffer, (*d3d11)->flush_samples, 8 * 1024); - - return RMT_ERROR_NONE; -} - -static void D3D11_Destructor(D3D11* d3d11) -{ - assert(d3d11 != NULL); - rmtDelete(Buffer, d3d11->flush_samples); - rmtDelete(rmtMessageQueue, d3d11->mq_to_d3d11_main); -} - -static HRESULT rmtD3D11Finish(ID3D11Device* device, ID3D11DeviceContext* context, rmtU64* out_timestamp, - double* out_frequency) -{ - HRESULT result; - ID3D11Query* full_stall_fence; - ID3D11Query* query_disjoint; - D3D11_QUERY_DESC query_desc; - D3D11_QUERY_DESC disjoint_desc; - UINT64 timestamp; - D3D11_QUERY_DATA_TIMESTAMP_DISJOINT disjoint; - - query_desc.Query = D3D11_QUERY_TIMESTAMP; - query_desc.MiscFlags = 0; - result = ID3D11Device_CreateQuery(device, &query_desc, &full_stall_fence); - if (result != S_OK) - return result; - - disjoint_desc.Query = D3D11_QUERY_TIMESTAMP_DISJOINT; - disjoint_desc.MiscFlags = 0; - result = ID3D11Device_CreateQuery(device, &disjoint_desc, &query_disjoint); - if (result != S_OK) - { - ID3D11Query_Release(full_stall_fence); - return result; - } - - ID3D11DeviceContext_Begin(context, (ID3D11Asynchronous*)query_disjoint); - ID3D11DeviceContext_End(context, (ID3D11Asynchronous*)full_stall_fence); - ID3D11DeviceContext_End(context, (ID3D11Asynchronous*)query_disjoint); - - result = S_FALSE; - - while (result == S_FALSE) - { - result = - ID3D11DeviceContext_GetData(context, (ID3D11Asynchronous*)query_disjoint, &disjoint, sizeof(disjoint), 0); - if (result != S_OK && result != S_FALSE) - { - ID3D11Query_Release(full_stall_fence); - ID3D11Query_Release(query_disjoint); - return result; - } - if (result == S_OK) - { - result = ID3D11DeviceContext_GetData(context, (ID3D11Asynchronous*)full_stall_fence, ×tamp, - sizeof(timestamp), 0); - if (result != S_OK && result != S_FALSE) - { - ID3D11Query_Release(full_stall_fence); - ID3D11Query_Release(query_disjoint); - return result; - } - } - // Give HyperThreading threads a breath on this spinlock. - YieldProcessor(); - } - - if (disjoint.Disjoint == FALSE) - { - double frequency = disjoint.Frequency / 1000000.0; - *out_timestamp = timestamp; - *out_frequency = frequency; - } - else - { - result = S_FALSE; - } - - ID3D11Query_Release(full_stall_fence); - ID3D11Query_Release(query_disjoint); - return result; -} - -static HRESULT SyncD3D11CpuGpuTimes(ID3D11Device* device, ID3D11DeviceContext* context, rmtU64* out_first_timestamp, - rmtU64* out_last_resync) -{ - rmtU64 cpu_time_start = 0; - rmtU64 cpu_time_stop = 0; - rmtU64 average_half_RTT = 0; // RTT = Rountrip Time. - UINT64 gpu_base = 0; - double frequency = 1; - int i; - - HRESULT result; - result = rmtD3D11Finish(device, context, &gpu_base, &frequency); - if (result != S_OK && result != S_FALSE) - return result; - - for (i = 0; i < RMT_GPU_CPU_SYNC_NUM_ITERATIONS; ++i) - { - rmtU64 half_RTT; - cpu_time_start = usTimer_Get(&g_Remotery->timer); - result = rmtD3D11Finish(device, context, &gpu_base, &frequency); - cpu_time_stop = usTimer_Get(&g_Remotery->timer); - - if (result != S_OK && result != S_FALSE) - return result; - - // Ignore attempts where there was a disjoint, since there would - // be a lot of noise in those readings for measuring the RTT - if (result == S_OK) - { - // Average the time it takes a roundtrip from CPU to GPU - // while doing nothing other than getting timestamps - half_RTT = (cpu_time_stop - cpu_time_start) >> 1ULL; - if (i == 0) - average_half_RTT = half_RTT; - else - average_half_RTT = (average_half_RTT + half_RTT) >> 1ULL; - } - } - - // All GPU times are offset from gpu_base, and then taken to - // the same relative origin CPU timestamps are based on. - // CPU is in us, we must translate it to ns. - *out_first_timestamp = gpu_base - (rmtU64)((cpu_time_start + average_half_RTT) * frequency); - *out_last_resync = cpu_time_stop; - - return result; -} - -typedef struct D3D11Timestamp -{ - // Inherit so that timestamps can be quickly allocated - ObjectLink Link; - - // Pair of timestamp queries that wrap the sample - ID3D11Query* query_start; - ID3D11Query* query_end; - - // A disjoint to measure frequency/stability - // TODO: Does *each* sample need one of these? - ID3D11Query* query_disjoint; - - rmtU64 cpu_timestamp; -} D3D11Timestamp; - -static rmtError D3D11Timestamp_Constructor(D3D11Timestamp* stamp) -{ - ThreadProfiler* thread_profiler; - D3D11_QUERY_DESC timestamp_desc; - D3D11_QUERY_DESC disjoint_desc; - ID3D11Device* device; - HRESULT* last_error; - rmtError rmt_error; - - assert(stamp != NULL); - - ObjectLink_Constructor((ObjectLink*)stamp); - - // Set defaults - stamp->query_start = NULL; - stamp->query_end = NULL; - stamp->query_disjoint = NULL; - stamp->cpu_timestamp = 0; - - assert(g_Remotery != NULL); - rmt_error = ThreadProfilers_GetCurrentThreadProfiler(g_Remotery->threadProfilers, &thread_profiler); - if (rmt_error != RMT_ERROR_NONE) - { - return rmt_error; - } - assert(thread_profiler->d3d11 != NULL); - device = thread_profiler->d3d11->device; - last_error = &thread_profiler->d3d11->last_error; - - // Create start/end timestamp queries - timestamp_desc.Query = D3D11_QUERY_TIMESTAMP; - timestamp_desc.MiscFlags = 0; - *last_error = ID3D11Device_CreateQuery(device, ×tamp_desc, &stamp->query_start); - if (*last_error != S_OK) - return RMT_ERROR_D3D11_FAILED_TO_CREATE_QUERY; - *last_error = ID3D11Device_CreateQuery(device, ×tamp_desc, &stamp->query_end); - if (*last_error != S_OK) - return RMT_ERROR_D3D11_FAILED_TO_CREATE_QUERY; - - // Create disjoint query - disjoint_desc.Query = D3D11_QUERY_TIMESTAMP_DISJOINT; - disjoint_desc.MiscFlags = 0; - *last_error = ID3D11Device_CreateQuery(device, &disjoint_desc, &stamp->query_disjoint); - if (*last_error != S_OK) - return RMT_ERROR_D3D11_FAILED_TO_CREATE_QUERY; - - return RMT_ERROR_NONE; -} - -static void D3D11Timestamp_Destructor(D3D11Timestamp* stamp) -{ - assert(stamp != NULL); - - // Destroy queries - if (stamp->query_disjoint != NULL) - ID3D11Query_Release(stamp->query_disjoint); - if (stamp->query_end != NULL) - ID3D11Query_Release(stamp->query_end); - if (stamp->query_start != NULL) - ID3D11Query_Release(stamp->query_start); -} - -static void D3D11Timestamp_Begin(D3D11Timestamp* stamp, ID3D11DeviceContext* context) -{ - assert(stamp != NULL); - - // Start of disjoint and first query - stamp->cpu_timestamp = usTimer_Get(&g_Remotery->timer); - ID3D11DeviceContext_Begin(context, (ID3D11Asynchronous*)stamp->query_disjoint); - ID3D11DeviceContext_End(context, (ID3D11Asynchronous*)stamp->query_start); -} - -static void D3D11Timestamp_End(D3D11Timestamp* stamp, ID3D11DeviceContext* context) -{ - assert(stamp != NULL); - - // End of disjoint and second query - ID3D11DeviceContext_End(context, (ID3D11Asynchronous*)stamp->query_end); - ID3D11DeviceContext_End(context, (ID3D11Asynchronous*)stamp->query_disjoint); -} - -static HRESULT D3D11Timestamp_GetData(D3D11Timestamp* stamp, ID3D11Device* device, ID3D11DeviceContext* context, - rmtU64* out_start, rmtU64* out_end, rmtU64* out_first_timestamp, - rmtU64* out_last_resync) -{ - ID3D11Asynchronous* query_start; - ID3D11Asynchronous* query_end; - ID3D11Asynchronous* query_disjoint; - HRESULT result; - - UINT64 start; - UINT64 end; - D3D11_QUERY_DATA_TIMESTAMP_DISJOINT disjoint; - - assert(stamp != NULL); - query_start = (ID3D11Asynchronous*)stamp->query_start; - query_end = (ID3D11Asynchronous*)stamp->query_end; - query_disjoint = (ID3D11Asynchronous*)stamp->query_disjoint; - - // Check to see if all queries are ready - // If any fail to arrive, wait until later - result = ID3D11DeviceContext_GetData(context, query_start, &start, sizeof(start), D3D11_ASYNC_GETDATA_DONOTFLUSH); - if (result != S_OK) - return result; - result = ID3D11DeviceContext_GetData(context, query_end, &end, sizeof(end), D3D11_ASYNC_GETDATA_DONOTFLUSH); - if (result != S_OK) - return result; - result = ID3D11DeviceContext_GetData(context, query_disjoint, &disjoint, sizeof(disjoint), - D3D11_ASYNC_GETDATA_DONOTFLUSH); - if (result != S_OK) - return result; - - if (disjoint.Disjoint == FALSE) - { - double frequency = disjoint.Frequency / 1000000.0; - - // Mark the first timestamp. We may resync if we detect the GPU timestamp is in the - // past (i.e. happened before the CPU command) since it should be impossible. - assert(out_first_timestamp != NULL); - if (*out_first_timestamp == 0 || ((start - *out_first_timestamp) / frequency) < stamp->cpu_timestamp) - { - result = SyncD3D11CpuGpuTimes(device, context, out_first_timestamp, out_last_resync); - if (result != S_OK) - return result; - } - - // Calculate start and end timestamps from the disjoint info - *out_start = (rmtU64)((start - *out_first_timestamp) / frequency); - *out_end = (rmtU64)((end - *out_first_timestamp) / frequency); - } - else - { -#if RMT_D3D11_RESYNC_ON_DISJOINT - result = SyncD3D11CpuGpuTimes(device, context, out_first_timestamp, out_last_resync); - if (result != S_OK) - return result; -#endif - } - - return S_OK; -} - -typedef struct D3D11Sample -{ - // IS-A inheritance relationship - Sample base; - - D3D11Timestamp* timestamp; - -} D3D11Sample; - -static rmtError D3D11Sample_Constructor(D3D11Sample* sample) -{ - assert(sample != NULL); - - // Chain to sample constructor - Sample_Constructor((Sample*)sample); - sample->base.type = RMT_SampleType_D3D11; - rmtTryNew(D3D11Timestamp, sample->timestamp); - - return RMT_ERROR_NONE; -} - -static void D3D11Sample_Destructor(D3D11Sample* sample) -{ - rmtDelete(D3D11Timestamp, sample->timestamp); - Sample_Destructor((Sample*)sample); -} - -RMT_API void _rmt_BindD3D11(void* device, void* context) -{ - if (g_Remotery != NULL) - { - ThreadProfiler* thread_profiler; - if (ThreadProfilers_GetCurrentThreadProfiler(g_Remotery->threadProfilers, &thread_profiler) == RMT_ERROR_NONE) - { - assert(thread_profiler->d3d11 != NULL); - - assert(device != NULL); - thread_profiler->d3d11->device = (ID3D11Device*)device; - assert(context != NULL); - thread_profiler->d3d11->context = (ID3D11DeviceContext*)context; - } - } -} - -static void UpdateD3D11Frame(ThreadProfiler* thread_profiler); - -RMT_API void _rmt_UnbindD3D11(void) -{ - if (g_Remotery != NULL) - { - ThreadProfiler* thread_profiler; - if (ThreadProfilers_GetCurrentThreadProfiler(g_Remotery->threadProfilers, &thread_profiler) == RMT_ERROR_NONE) - { - D3D11* d3d11 = thread_profiler->d3d11; - assert(d3d11 != NULL); - - // Stall waiting for the D3D queue to empty into the Remotery queue - while (!rmtMessageQueue_IsEmpty(d3d11->mq_to_d3d11_main)) - UpdateD3D11Frame(thread_profiler); - - // There will be a whole bunch of D3D11 sample trees queued up the remotery queue that need releasing - FreePendingSampleTrees(g_Remotery, RMT_SampleType_D3D11, d3d11->flush_samples); - - // Inform sampler to not add any more samples - d3d11->device = NULL; - d3d11->context = NULL; - - // Forcefully delete sample tree on this thread to release time stamps from - // the same thread that created them - Remotery_DeleteSampleTree(g_Remotery, RMT_SampleType_D3D11); - } - } -} - -static rmtError AllocateD3D11SampleTree(SampleTree** d3d_tree) -{ - rmtTryNew(SampleTree, *d3d_tree, sizeof(D3D11Sample), (ObjConstructor)D3D11Sample_Constructor, - (ObjDestructor)D3D11Sample_Destructor); - return RMT_ERROR_NONE; -} - -RMT_API void _rmt_BeginD3D11Sample(rmtPStr name, rmtU32* hash_cache) -{ - ThreadProfiler* thread_profiler; - D3D11* d3d11; - - if (g_Remotery == NULL) - return; - - if (ThreadProfilers_GetCurrentThreadProfiler(g_Remotery->threadProfilers, &thread_profiler) == RMT_ERROR_NONE) - { - Sample* sample; - rmtU32 name_hash; - SampleTree** d3d_tree; - - // Has D3D11 been unbound? - d3d11 = thread_profiler->d3d11; - assert(d3d11 != NULL); - if (d3d11->device == NULL || d3d11->context == NULL) - return; - - name_hash = ThreadProfiler_GetNameHash(thread_profiler, g_Remotery->mq_to_rmt_thread, name, hash_cache); - - // Create the D3D11 tree on-demand as the tree needs an up-front-created root. - // This is not possible to create on initialisation as a D3D11 binding is not yet available. - d3d_tree = &thread_profiler->sampleTrees[RMT_SampleType_D3D11]; - if (*d3d_tree == NULL) - { - AllocateD3D11SampleTree(d3d_tree); - } - - // Push the sample and activate the timestamp - if (ThreadProfiler_Push(*d3d_tree, name_hash, 0, &sample) == RMT_ERROR_NONE) - { - D3D11Sample* d3d_sample = (D3D11Sample*)sample; - d3d_sample->base.usGpuIssueOnCpu = usTimer_Get(&g_Remotery->timer); - D3D11Timestamp_Begin(d3d_sample->timestamp, d3d11->context); - } - } -} - -static rmtBool GetD3D11SampleTimes(Sample* sample, ThreadProfiler* thread_profiler, rmtU64* out_first_timestamp, - rmtU64* out_last_resync) -{ - Sample* child; - - D3D11Sample* d3d_sample = (D3D11Sample*)sample; - - assert(sample != NULL); - if (d3d_sample->timestamp != NULL) - { - HRESULT result; - - D3D11* d3d11 = thread_profiler->d3d11; - assert(d3d11 != NULL); - - assert(out_last_resync != NULL); - -#if (RMT_GPU_CPU_SYNC_SECONDS > 0) - if (*out_last_resync < d3d_sample->timestamp->cpu_timestamp) - { - // Convert from us to seconds. - rmtU64 time_diff = (d3d_sample->timestamp->cpu_timestamp - *out_last_resync) / 1000000ULL; - if (time_diff > RMT_GPU_CPU_SYNC_SECONDS) - { - result = SyncD3D11CpuGpuTimes(d3d11->device, d3d11->context, out_first_timestamp, out_last_resync); - if (result != S_OK) - { - d3d11->last_error = result; - return RMT_FALSE; - } - } - } -#endif - - result = D3D11Timestamp_GetData(d3d_sample->timestamp, d3d11->device, d3d11->context, &sample->us_start, - &sample->us_end, out_first_timestamp, out_last_resync); - - if (result != S_OK) - { - d3d11->last_error = result; - return RMT_FALSE; - } - - sample->us_length = sample->us_end - sample->us_start; - } - - // Sum length on the parent to track un-sampled time in the parent - if (sample->parent != NULL) - { - sample->parent->us_sampled_length += sample->us_length; - } - - // Get child sample times - for (child = sample->first_child; child != NULL; child = child->next_sibling) - { - if (!GetD3D11SampleTimes(child, thread_profiler, out_first_timestamp, out_last_resync)) - return RMT_FALSE; - } - - return RMT_TRUE; -} - -static void UpdateD3D11Frame(ThreadProfiler* thread_profiler) -{ - D3D11* d3d11; - - if (g_Remotery == NULL) - return; - - d3d11 = thread_profiler->d3d11; - assert(d3d11 != NULL); - - rmt_BeginCPUSample(rmt_UpdateD3D11Frame, 0); - - // Process all messages in the D3D queue - for (;;) - { - Msg_SampleTree* sample_tree; - Sample* sample; - - Message* message = rmtMessageQueue_PeekNextMessage(d3d11->mq_to_d3d11_main); - if (message == NULL) - break; - - // There's only one valid message type in this queue - assert(message->id == MsgID_SampleTree); - sample_tree = (Msg_SampleTree*)message->payload; - sample = sample_tree->rootSample; - assert(sample->type == RMT_SampleType_D3D11); - - // Retrieve timing of all D3D11 samples - // If they aren't ready leave the message unconsumed, holding up later frames and maintaining order - if (!GetD3D11SampleTimes(sample, thread_profiler, &d3d11->first_timestamp, &d3d11->last_resync)) - break; - - // Pass samples onto the remotery thread for sending to the viewer - QueueSampleTree(g_Remotery->mq_to_rmt_thread, sample, sample_tree->allocator, sample_tree->threadName, 0, - message->threadProfiler, RMT_FALSE); - rmtMessageQueue_ConsumeNextMessage(d3d11->mq_to_d3d11_main, message); - } - - rmt_EndCPUSample(); -} - -RMT_API void _rmt_EndD3D11Sample(void) -{ - ThreadProfiler* thread_profiler; - D3D11* d3d11; - - if (g_Remotery == NULL) - return; - - if (ThreadProfilers_GetCurrentThreadProfiler(g_Remotery->threadProfilers, &thread_profiler) == RMT_ERROR_NONE) - { - D3D11Sample* d3d_sample; - - // Has D3D11 been unbound? - d3d11 = thread_profiler->d3d11; - assert(d3d11 != NULL); - if (d3d11->device == NULL || d3d11->context == NULL) - return; - - // Close the timestamp - d3d_sample = (D3D11Sample*)thread_profiler->sampleTrees[RMT_SampleType_D3D11]->currentParent; - if (d3d_sample->base.recurse_depth > 0) - { - d3d_sample->base.recurse_depth--; - } - else - { - if (d3d_sample->timestamp != NULL) - D3D11Timestamp_End(d3d_sample->timestamp, d3d11->context); - - // Send to the update loop for ready-polling - if (ThreadProfiler_Pop(thread_profiler, d3d11->mq_to_d3d11_main, (Sample*)d3d_sample, 0)) - // Perform ready-polling on popping of the root sample - UpdateD3D11Frame(thread_profiler); - } - } -} - -#endif // RMT_USE_D3D11 - -/* ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- - @D3D12: Direct3D 12 event sampling ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- -*/ - -#if RMT_USE_D3D12 - -// As clReflect has no way of disabling C++ compile mode, this forces C interfaces everywhere... -#define CINTERFACE - -#include - -typedef struct D3D12ThreadData -{ - rmtU32 lastAllocatedQueryIndex; - - // Sample trees in transit in the message queue for release on shutdown - Buffer* flushSamples; -} D3D12ThreadData; - -static rmtError D3D12ThreadData_Create(D3D12ThreadData** d3d12_thread_data) -{ - assert(d3d12_thread_data != NULL); - - // Allocate space for the D3D12 data - rmtTryMalloc(D3D12ThreadData, *d3d12_thread_data); - - // Set defaults - (*d3d12_thread_data)->lastAllocatedQueryIndex = 0; - (*d3d12_thread_data)->flushSamples = NULL; - - rmtTryNew(Buffer, (*d3d12_thread_data)->flushSamples, 8 * 1024); - - return RMT_ERROR_NONE; -} - -static void D3D12ThreadData_Destructor(D3D12ThreadData* d3d12_thread_data) -{ - assert(d3d12_thread_data != NULL); - rmtDelete(Buffer, d3d12_thread_data->flushSamples); -} - -typedef struct D3D12Sample -{ - // IS-A inheritance relationship - Sample base; - - // Cached bind and command list used to create the sample so that the user doesn't have to pass it - struct D3D12BindImpl* bind; - ID3D12GraphicsCommandList* commandList; - - // Begin/End timestamp indices in the query heap - rmtU32 queryIndex; - -} D3D12Sample; - -static rmtError D3D12Sample_Constructor(D3D12Sample* sample) -{ - assert(sample != NULL); - - // Chain to sample constructor - Sample_Constructor((Sample*)sample); - sample->base.type = RMT_SampleType_D3D12; - sample->bind = NULL; - sample->commandList = NULL; - sample->queryIndex = 0; - - return RMT_ERROR_NONE; -} - -static void D3D12Sample_Destructor(D3D12Sample* sample) -{ - Sample_Destructor((Sample*)sample); -} - -typedef struct D3D12BindImpl -{ - rmtD3D12Bind base; - - // Ring buffer of GPU timestamp destinations for all queries - rmtU32 maxNbQueries; - ID3D12QueryHeap* gpuTimestampRingBuffer; - - // CPU-accessible copy destination for all timestamps - ID3D12Resource* cpuTimestampRingBuffer; - - // Pointers to samples that expect the result of timestamps - D3D12Sample** sampleRingBuffer; - - // Read/write positions of the ring buffer allocator, synchronising access to all the ring buffers at once - // TODO(don): Separate by cache line? - rmtAtomicU32 ringBufferRead; - rmtAtomicU32 ringBufferWrite; - - ID3D12Fence* gpuQueryFence; - - - - // Queue to the D3D 12 main update thread - rmtMessageQueue* mqToD3D12Update; - - struct D3D12BindImpl* next; - -} D3D12BindImpl; - -#ifdef IID_PPV_ARGS -#define C_IID_PPV_ARGS(iid, addr) IID_PPV_ARGS(addr) -#else -#define C_IID_PPV_ARGS(iid, addr) &iid, (void**)addr -#endif - -#include - -static rmtError CreateQueryHeap(D3D12BindImpl* bind, ID3D12Device* d3d_device, ID3D12CommandQueue* d3d_queue, rmtU32 nb_queries) -{ - HRESULT hr; - D3D12_QUERY_HEAP_TYPE query_heap_type = D3D12_QUERY_HEAP_TYPE_TIMESTAMP; - D3D12_COMMAND_QUEUE_DESC queue_desc; - D3D12_QUERY_HEAP_DESC query_heap_desc; - - // Select the correct query heap type for the copy queue - #if WDK_NTDDI_VERSION >= NTDDI_WIN10_CO - //d3d_queue->lpVtbl->GetDesc(d3d_queue, &queue_desc); - /*if (queue_desc.Type == D3D12_COMMAND_LIST_TYPE_COPY) - { - D3D12_FEATURE_DATA_D3D12_OPTIONS3 feature_data; - hr = d3d_device->lpVtbl->CheckFeatureSupport(d3d_device, D3D12_FEATURE_D3D12_OPTIONS3, &feature_data, sizeof(feature_data)); - if (hr != S_OK || feature_data.CopyQueueTimestampQueriesSupported == FALSE) - { - return rmtMakeError(RMT_ERROR_INVALID_INPUT, "Copy queues on this device do not support timestamps"); - } - - query_heap_type = D3D12_QUERY_HEAP_TYPE_COPY_QUEUE_TIMESTAMP; - }*/ - #else - if (queue_desc.Type == D3D12_COMMAND_LIST_TYPE_COPY) - { - // On old versions of Windows SDK the D3D C headers incorrectly returned structures - // The ABI is different and C++ expects return structures to be silently passed as parameters - // The newer headers add an extra out parameter to make this explicit - return rmtMakeError(RMT_ERROR_INVALID_INPUT, "Your Win10 SDK version is too old to determine if this device supports timestamps on copy queues"); - } - #endif - - // Create the heap for all the queries - ZeroMemory(&query_heap_desc, sizeof(query_heap_desc)); - query_heap_desc.Type = query_heap_type; - query_heap_desc.Count = nb_queries; - hr = d3d_device->lpVtbl->CreateQueryHeap(d3d_device, &query_heap_desc, C_IID_PPV_ARGS(IID_ID3D12QueryHeap, &bind->gpuTimestampRingBuffer)); - if (hr != S_OK) - { - return rmtMakeError(RMT_ERROR_RESOURCE_CREATE_FAIL, "Failed to create D3D12 Query Heap"); - } - - return RMT_ERROR_NONE; -} - -static rmtError CreateCpuQueries(D3D12BindImpl* bind, ID3D12Device* d3d_device) -{ - D3D12_HEAP_PROPERTIES results_heap_props; - HRESULT hr; - - // We want a readback resource that the GPU can copy to and the CPU can read from - ZeroMemory(&results_heap_props, sizeof(results_heap_props)); - results_heap_props.Type = D3D12_HEAP_TYPE_READBACK; - - // Describe resource dimensions, enough to store a timestamp for each query - D3D12_RESOURCE_DESC results_desc; - ZeroMemory(&results_desc, sizeof(results_desc)); - results_desc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER; - results_desc.Width = bind->maxNbQueries * sizeof(rmtU64); - results_desc.Height = 1; - results_desc.DepthOrArraySize = 1; - results_desc.MipLevels = 1; - results_desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; - results_desc.SampleDesc.Count = 1; - - hr = d3d_device->lpVtbl->CreateCommittedResource(d3d_device, &results_heap_props, D3D12_HEAP_FLAG_NONE, - &results_desc, D3D12_RESOURCE_STATE_COPY_DEST, NULL, - C_IID_PPV_ARGS(IID_ID3D12Resource, &bind->cpuTimestampRingBuffer)); - if (hr != S_OK) - { - return rmtMakeError(RMT_ERROR_RESOURCE_CREATE_FAIL, "Failed to create D3D12 Query Results Buffer"); - } - - return RMT_ERROR_NONE; -} - -static rmtError CreateQueryFence(D3D12BindImpl* bind, ID3D12Device* d3d_device) -{ - HRESULT hr = d3d_device->lpVtbl->CreateFence(d3d_device, 0, D3D12_FENCE_FLAG_NONE, C_IID_PPV_ARGS(IID_ID3D12Fence, &bind->gpuQueryFence)); - if (hr != S_OK) - { - return rmtMakeError(RMT_ERROR_RESOURCE_CREATE_FAIL, "Failed to create D3D12 Query Fence"); - } - - return RMT_ERROR_NONE; -} - -static rmtError CopyTimestamps(D3D12BindImpl* bind, rmtU32 ring_pos_a, rmtU32 ring_pos_b, double gpu_ticks_to_us, rmtS64 gpu_to_cpu_timestamp_us) -{ - rmtU32 query_index; - D3D12_RANGE map; - rmtU64* cpu_timestamps; - - ID3D12Resource* cpu_timestamp_buffer = (ID3D12Resource*)bind->cpuTimestampRingBuffer; - D3D12Sample** cpu_sample_buffer = bind->sampleRingBuffer; - - // Map the range we're interesting in reading - map.Begin = ring_pos_a * sizeof(rmtU64); - map.End = ring_pos_b * sizeof(rmtU64); - if (cpu_timestamp_buffer->lpVtbl->Map(cpu_timestamp_buffer, 0, &map, (void**)&cpu_timestamps) != S_OK) - { - return rmtMakeError(RMT_ERROR_RESOURCE_ACCESS_FAIL, "Failed to Map D3D12 CPU Timestamp Ring Buffer"); - } - - // Copy all timestamps to their expectant samples - for (query_index = ring_pos_a; query_index < ring_pos_b; query_index += 2) - { - rmtU64 us_start = (rmtU64)(cpu_timestamps[query_index] * gpu_ticks_to_us + gpu_to_cpu_timestamp_us); - rmtU64 us_end = (rmtU64)(cpu_timestamps[query_index + 1] * gpu_ticks_to_us + gpu_to_cpu_timestamp_us); - - D3D12Sample* sample = cpu_sample_buffer[query_index >> 1]; - sample->base.us_start = us_start; - Sample_Close(&sample->base, us_end); - sample->base.us_end = us_end; - } - - cpu_timestamp_buffer->lpVtbl->Unmap(cpu_timestamp_buffer, 0, NULL); - - return RMT_ERROR_NONE; -} - -static rmtError D3D12MarkFrame(D3D12BindImpl* bind) -{ - if (bind == NULL) - { - return RMT_ERROR_NONE; - } - - rmtU32 index_mask = bind->maxNbQueries - 1; - rmtU32 current_read_cpu = LoadAcquire(&bind->ringBufferRead); - rmtU32 current_write_cpu = LoadAcquire(&bind->ringBufferWrite); - - // Tell the GPU where the CPU write position is - ID3D12CommandQueue* d3d_queue = (ID3D12CommandQueue*)bind->base.queue; - d3d_queue->lpVtbl->Signal(d3d_queue, bind->gpuQueryFence, current_write_cpu); - - // Has the GPU processed any writes? - rmtU32 current_write_gpu = (rmtU32)bind->gpuQueryFence->lpVtbl->GetCompletedValue(bind->gpuQueryFence); - if (current_write_gpu > current_read_cpu) - { - rmtU64 gpu_tick_frequency; - double gpu_ticks_to_us; - rmtU64 gpu_timestamp_us; - rmtU64 cpu_timestamp_us; - rmtS64 gpu_to_cpu_timestamp_us; - - // Physical ring buffer positions - rmtU32 ring_pos_a = current_read_cpu & index_mask; - rmtU32 ring_pos_b = current_write_gpu & index_mask; - - // Get current ticks of both CPU and GPU for synchronisation - rmtU64 gpu_timestamp_ticks; - rmtU64 cpu_timestamp_ticks; - if (d3d_queue->lpVtbl->GetClockCalibration(d3d_queue, &gpu_timestamp_ticks, &cpu_timestamp_ticks) != S_OK) - { - return rmtMakeError(RMT_ERROR_RESOURCE_ACCESS_FAIL, "Failed to D3D12 CPU/GPU Clock Calibration"); - } - - // Convert GPU ticks to microseconds - d3d_queue->lpVtbl->GetTimestampFrequency(d3d_queue, &gpu_tick_frequency); - gpu_ticks_to_us = 1000000.0 / gpu_tick_frequency; - gpu_timestamp_us = (rmtU64)(gpu_timestamp_ticks * gpu_ticks_to_us); - - // Convert CPU ticks to microseconds, offset from the global timer start - cpu_timestamp_us = (rmtU64)((cpu_timestamp_ticks - g_Remotery->timer.counter_start.QuadPart) * g_Remotery->timer.counter_scale); - - // And we now have the offset from GPU microseconds to CPU microseconds - gpu_to_cpu_timestamp_us = cpu_timestamp_us - gpu_timestamp_us; - - // Copy resulting timestamps to their samples - // Will have to split the copies into two passes if they cross the ring buffer wrap around - if (ring_pos_b < ring_pos_a) - { - rmtTry(CopyTimestamps(bind, ring_pos_a, bind->maxNbQueries, gpu_ticks_to_us, gpu_to_cpu_timestamp_us)); - rmtTry(CopyTimestamps(bind, 0, ring_pos_b, gpu_ticks_to_us, gpu_to_cpu_timestamp_us)); - } - else - { - rmtTry(CopyTimestamps(bind, ring_pos_a, ring_pos_b, gpu_ticks_to_us, gpu_to_cpu_timestamp_us)); - } - - // Release the ring buffer entries just processed - StoreRelease(&bind->ringBufferRead, current_write_gpu); - } - - // Attempt to empty the queue of complete message trees - Message* message; - while ((message = rmtMessageQueue_PeekNextMessage(bind->mqToD3D12Update))) - { - Msg_SampleTree* msg_sample_tree; - Sample* root_sample; - - // Ensure only D3D12 sample tree messages come through here - assert(message->id == MsgID_SampleTree); - msg_sample_tree = (Msg_SampleTree*)message->payload; - root_sample = msg_sample_tree->rootSample; - assert(root_sample->type == RMT_SampleType_D3D12); - - // If the last-allocated query in this tree has been GPU-processed it's safe to now send the tree to Remotery thread - if (current_write_gpu > msg_sample_tree->userData) - { - QueueSampleTree(g_Remotery->mq_to_rmt_thread, root_sample, msg_sample_tree->allocator, msg_sample_tree->threadName, - 0, message->threadProfiler, RMT_FALSE); - rmtMessageQueue_ConsumeNextMessage(bind->mqToD3D12Update, message); - } - else - { - break; - } - } - - // Chain to the next bind here so that root calling code doesn't need to know the definition of D3D12BindImpl - rmtTry(D3D12MarkFrame(bind->next)); - - return RMT_ERROR_NONE; -} - -static rmtError SampleD3D12GPUThreadLoop(rmtThread* rmt_thread) -{ - D3D12BindImpl* bind = (D3D12BindImpl*)rmt_thread->param; - - while (rmt_thread->request_exit == RMT_FALSE) - { - msSleep(15); - } - - return RMT_ERROR_NONE; -} - -RMT_API rmtError _rmt_BindD3D12(void* device, void* queue, rmtD3D12Bind** out_bind) -{ - D3D12BindImpl* bind; - ID3D12Device* d3d_device = (ID3D12Device*)device; - ID3D12CommandQueue* d3d_queue = (ID3D12CommandQueue*)queue; - - if (g_Remotery == NULL) - { - return RMT_ERROR_REMOTERY_NOT_CREATED; - } - - assert(device != NULL); - assert(queue != NULL); - assert(out_bind != NULL); - - // Allocate the bind container - rmtTryMalloc(D3D12BindImpl, bind); - - // Set default state - bind->base.device = device; - bind->base.queue = queue; - bind->maxNbQueries = 32 * 1024; - bind->gpuTimestampRingBuffer = NULL; - bind->cpuTimestampRingBuffer = NULL; - bind->sampleRingBuffer = NULL; - bind->ringBufferRead = 0; - bind->ringBufferWrite = 0; - bind->gpuQueryFence = NULL; - bind->mqToD3D12Update = NULL; - bind->next = NULL; - - // Create the independent ring buffer storage items - // TODO(don): Leave space beetween start and end to stop invalidating cache lines? - // NOTE(don): ABA impossible due to non-wrapping ring buffer indices - rmtTry(CreateQueryHeap(bind, d3d_device, d3d_queue, bind->maxNbQueries)); - rmtTry(CreateCpuQueries(bind, d3d_device)); - rmtTryMallocArray(D3D12Sample*, bind->sampleRingBuffer, bind->maxNbQueries / 2); - rmtTry(CreateQueryFence(bind, d3d_device)); - - rmtTryNew(rmtMessageQueue, bind->mqToD3D12Update, g_Settings.messageQueueSizeInBytes); - - // Add to the global linked list of binds - { - mtxLock(&g_Remotery->d3d12BindsMutex); - bind->next = g_Remotery->d3d12Binds; - g_Remotery->d3d12Binds = bind; - mtxUnlock(&g_Remotery->d3d12BindsMutex); - } - - *out_bind = &bind->base; - - return RMT_ERROR_NONE; -} - -RMT_API void _rmt_UnbindD3D12(rmtD3D12Bind* bind) -{ - D3D12BindImpl* d3d_bind = (D3D12BindImpl*)bind; - - assert(bind != NULL); - - // Remove from the linked list - { - mtxLock(&g_Remotery->d3d12BindsMutex); - D3D12BindImpl* cur = g_Remotery->d3d12Binds; - D3D12BindImpl* prev = NULL; - for ( ; cur != NULL; cur = cur->next) - { - if (cur == d3d_bind) - { - if (prev != NULL) - { - prev->next = cur->next; - } - else - { - g_Remotery->d3d12Binds = cur->next; - } - - break; - } - } - mtxUnlock(&g_Remotery->d3d12BindsMutex); - } - - if (d3d_bind->gpuQueryFence != NULL) - { - d3d_bind->gpuQueryFence->lpVtbl->Release(d3d_bind->gpuQueryFence); - } - - rmtFree(d3d_bind->sampleRingBuffer); - - if (d3d_bind->cpuTimestampRingBuffer != NULL) - { - d3d_bind->cpuTimestampRingBuffer->lpVtbl->Release(d3d_bind->cpuTimestampRingBuffer); - } - - if (d3d_bind->gpuTimestampRingBuffer != NULL) - { - d3d_bind->gpuTimestampRingBuffer->lpVtbl->Release(d3d_bind->gpuTimestampRingBuffer); - } -} - -static rmtError AllocateD3D12SampleTree(SampleTree** d3d_tree) -{ - rmtTryNew(SampleTree, *d3d_tree, sizeof(D3D12Sample), (ObjConstructor)D3D12Sample_Constructor, - (ObjDestructor)D3D12Sample_Destructor); - return RMT_ERROR_NONE; -} - -static rmtError AllocQueryPair(D3D12BindImpl* d3d_bind, rmtAtomicU32* out_allocation_index) -{ - // Check for overflow against a tail which is only ever written by one thread - rmtU32 read = LoadAcquire(&d3d_bind->ringBufferRead); - rmtU32 write = LoadAcquire(&d3d_bind->ringBufferWrite); - rmtU32 nb_queries = (write - read); - rmtU32 queries_left = d3d_bind->maxNbQueries - nb_queries; - if (queries_left < 2) - { - return rmtMakeError(RMT_ERROR_RESOURCE_CREATE_FAIL, "D3D12 query ring buffer overflow"); - } - - *out_allocation_index = AtomicAddU32(&d3d_bind->ringBufferWrite, 2); - return RMT_ERROR_NONE; -} - -RMT_API void _rmt_BeginD3D12Sample(rmtD3D12Bind* bind, void* command_list, rmtPStr name, rmtU32* hash_cache) -{ - ThreadProfiler* thread_profiler; - - if (g_Remotery == NULL || bind == NULL) - return; - - assert(command_list != NULL); - - if (ThreadProfilers_GetCurrentThreadProfiler(g_Remotery->threadProfilers, &thread_profiler) == RMT_ERROR_NONE) - { - Sample* sample; - rmtU32 name_hash; - SampleTree** d3d_tree; - - name_hash = ThreadProfiler_GetNameHash(thread_profiler, g_Remotery->mq_to_rmt_thread, name, hash_cache); - - // Create the D3D12 tree on-demand as the tree needs an up-front-created root. - // This is not possible to create on initialisation as a D3D12 binding is not yet available. - d3d_tree = &thread_profiler->sampleTrees[RMT_SampleType_D3D12]; - if (*d3d_tree == NULL) - { - AllocateD3D12SampleTree(d3d_tree); - } - - // Push the sample and activate the timestamp - if (ThreadProfiler_Push(*d3d_tree, name_hash, 0, &sample) == RMT_ERROR_NONE) - { - rmtError error; - - D3D12BindImpl* d3d_bind = (D3D12BindImpl*)bind; - ID3D12GraphicsCommandList* d3d_command_list = (ID3D12GraphicsCommandList*)command_list; - - D3D12Sample* d3d_sample = (D3D12Sample*)sample; - d3d_sample->bind = d3d_bind; - d3d_sample->commandList = d3d_command_list; - d3d_sample->base.usGpuIssueOnCpu = usTimer_Get(&g_Remotery->timer); - - error = AllocQueryPair(d3d_bind, &d3d_sample->queryIndex); - if (error == RMT_ERROR_NONE) - { - rmtU32 physical_query_index = d3d_sample->queryIndex & (d3d_bind->maxNbQueries - 1); - d3d_command_list->lpVtbl->EndQuery(d3d_command_list, d3d_bind->gpuTimestampRingBuffer, D3D12_QUERY_TYPE_TIMESTAMP, physical_query_index); - - // Track which D3D sample expects the timestamp results - d3d_bind->sampleRingBuffer[physical_query_index / 2] = d3d_sample; - - // Keep track of the last allocated query so we can check when the GPU has finished with them all - thread_profiler->d3d12ThreadData->lastAllocatedQueryIndex = d3d_sample->queryIndex; - } - else - { - // SET QUERY INDEX TO INVALID so that pop doesn't release it - } - } - } -} - -RMT_API void _rmt_EndD3D12Sample() -{ - ThreadProfiler* thread_profiler; - - if (g_Remotery == NULL) - return; - - if (ThreadProfilers_GetCurrentThreadProfiler(g_Remotery->threadProfilers, &thread_profiler) == RMT_ERROR_NONE) - { - D3D12ThreadData* d3d_thread_data = thread_profiler->d3d12ThreadData; - D3D12Sample* d3d_sample; - - // Sample tree isn't there if D3D12 hasn't been initialised - SampleTree* d3d_tree = thread_profiler->sampleTrees[RMT_SampleType_D3D12]; - if (d3d_tree == NULL) - { - return; - } - - // Close the timestamp - d3d_sample = (D3D12Sample*)d3d_tree->currentParent; - if (d3d_sample->base.recurse_depth > 0) - { - d3d_sample->base.recurse_depth--; - } - else - { - // Issue the timestamp query for the end of the sample - D3D12BindImpl* d3d_bind = d3d_sample->bind; - ID3D12GraphicsCommandList* d3d_command_list = d3d_sample->commandList; - rmtU32 query_index = d3d_sample->queryIndex & (d3d_bind->maxNbQueries - 1); - d3d_command_list->lpVtbl->EndQuery(d3d_command_list, d3d_bind->gpuTimestampRingBuffer, D3D12_QUERY_TYPE_TIMESTAMP, - query_index + 1); - - // Immediately schedule resolve of the timestamps to CPU-visible memory - d3d_command_list->lpVtbl->ResolveQueryData(d3d_command_list, d3d_bind->gpuTimestampRingBuffer, - D3D12_QUERY_TYPE_TIMESTAMP, query_index, 2, - d3d_bind->cpuTimestampRingBuffer, query_index * sizeof(rmtU64)); - - if (ThreadProfiler_Pop(thread_profiler, d3d_bind->mqToD3D12Update, (Sample*)d3d_sample, - d3d_thread_data->lastAllocatedQueryIndex)) - { - } - } - } -} - -#endif // RMT_USE_D3D12 - -/* ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- -@OpenGL: OpenGL event sampling ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- -*/ - -#if RMT_USE_OPENGL - -#ifndef APIENTRY -#if defined(__MINGW32__) || defined(__CYGWIN__) -#define APIENTRY __stdcall -#elif (defined(_MSC_VER) && (_MSC_VER >= 800)) || defined(_STDCALL_SUPPORTED) || defined(__BORLANDC__) -#define APIENTRY __stdcall -#else -#define APIENTRY -#endif -#endif - -#ifndef GLAPI -#if defined(__MINGW32__) || defined(__CYGWIN__) -#define GLAPI extern -#elif defined(_WIN32) -#define GLAPI WINGDIAPI -#else -#define GLAPI extern -#endif -#endif - -#ifndef GLAPIENTRY -#define GLAPIENTRY APIENTRY -#endif - -typedef rmtU32 GLenum; -typedef rmtU32 GLuint; -typedef rmtS32 GLint; -typedef rmtS32 GLsizei; -typedef rmtU64 GLuint64; -typedef rmtS64 GLint64; -typedef unsigned char GLubyte; - -typedef GLenum(GLAPIENTRY* PFNGLGETERRORPROC)(void); -typedef void(GLAPIENTRY* PFNGLGENQUERIESPROC)(GLsizei n, GLuint* ids); -typedef void(GLAPIENTRY* PFNGLDELETEQUERIESPROC)(GLsizei n, const GLuint* ids); -typedef void(GLAPIENTRY* PFNGLBEGINQUERYPROC)(GLenum target, GLuint id); -typedef void(GLAPIENTRY* PFNGLENDQUERYPROC)(GLenum target); -typedef void(GLAPIENTRY* PFNGLGETQUERYOBJECTIVPROC)(GLuint id, GLenum pname, GLint* params); -typedef void(GLAPIENTRY* PFNGLGETQUERYOBJECTUIVPROC)(GLuint id, GLenum pname, GLuint* params); -typedef void(GLAPIENTRY* PFNGLGETQUERYOBJECTI64VPROC)(GLuint id, GLenum pname, GLint64* params); -typedef void(GLAPIENTRY* PFNGLGETQUERYOBJECTUI64VPROC)(GLuint id, GLenum pname, GLuint64* params); -typedef void(GLAPIENTRY* PFNGLQUERYCOUNTERPROC)(GLuint id, GLenum target); -typedef void(GLAPIENTRY* PFNGLGETINTEGER64VPROC)(GLenum pname, GLint64* data); -typedef void(GLAPIENTRY* PFNGLFINISHPROC)(void); - -#define GL_NO_ERROR 0 -#define GL_QUERY_RESULT 0x8866 -#define GL_QUERY_RESULT_AVAILABLE 0x8867 -#define GL_TIME_ELAPSED 0x88BF -#define GL_TIMESTAMP 0x8E28 - -#define RMT_GL_GET_FUN(x) \ - assert(g_Remotery->opengl->x != NULL); \ - g_Remotery->opengl->x - -#define rmtglGenQueries RMT_GL_GET_FUN(__glGenQueries) -#define rmtglDeleteQueries RMT_GL_GET_FUN(__glDeleteQueries) -#define rmtglBeginQuery RMT_GL_GET_FUN(__glBeginQuery) -#define rmtglEndQuery RMT_GL_GET_FUN(__glEndQuery) -#define rmtglGetQueryObjectiv RMT_GL_GET_FUN(__glGetQueryObjectiv) -#define rmtglGetQueryObjectuiv RMT_GL_GET_FUN(__glGetQueryObjectuiv) -#define rmtglGetQueryObjecti64v RMT_GL_GET_FUN(__glGetQueryObjecti64v) -#define rmtglGetQueryObjectui64v RMT_GL_GET_FUN(__glGetQueryObjectui64v) -#define rmtglQueryCounter RMT_GL_GET_FUN(__glQueryCounter) -#define rmtglGetInteger64v RMT_GL_GET_FUN(__glGetInteger64v) -#define rmtglFinish RMT_GL_GET_FUN(__glFinish) - -struct OpenGL_t -{ - // Handle to the OS OpenGL DLL - void* dll_handle; - - PFNGLGETERRORPROC __glGetError; - PFNGLGENQUERIESPROC __glGenQueries; - PFNGLDELETEQUERIESPROC __glDeleteQueries; - PFNGLBEGINQUERYPROC __glBeginQuery; - PFNGLENDQUERYPROC __glEndQuery; - PFNGLGETQUERYOBJECTIVPROC __glGetQueryObjectiv; - PFNGLGETQUERYOBJECTUIVPROC __glGetQueryObjectuiv; - PFNGLGETQUERYOBJECTI64VPROC __glGetQueryObjecti64v; - PFNGLGETQUERYOBJECTUI64VPROC __glGetQueryObjectui64v; - PFNGLQUERYCOUNTERPROC __glQueryCounter; - PFNGLGETINTEGER64VPROC __glGetInteger64v; - PFNGLFINISHPROC __glFinish; - - // Queue to the OpenGL main update thread - // Given that BeginSample/EndSample need to be called from the same thread that does the update, there - // is really no need for this to be a thread-safe queue. I'm using it for its convenience. - rmtMessageQueue* mq_to_opengl_main; - - // Mark the first time so that remaining timestamps are offset from this - rmtU64 first_timestamp; - // Last time in us (CPU time, via usTimer_Get) since we last resync'ed CPU & GPU - rmtU64 last_resync; - - // Sample trees in transit in the message queue for release on shutdown - Buffer* flush_samples; -}; - -static GLenum rmtglGetError(void) -{ - if (g_Remotery != NULL) - { - assert(g_Remotery->opengl != NULL); - if (g_Remotery->opengl->__glGetError != NULL) - return g_Remotery->opengl->__glGetError(); - } - - return (GLenum)0; -} - -#ifdef RMT_PLATFORM_LINUX -#ifdef __cplusplus -extern "C" void* glXGetProcAddressARB(const GLubyte*); -#else -extern void* glXGetProcAddressARB(const GLubyte*); -#endif -#endif - -static ProcReturnType rmtglGetProcAddress(OpenGL* opengl, const char* symbol) -{ -#if defined(RMT_PLATFORM_WINDOWS) - { - // Get OpenGL extension-loading function for each call - typedef ProcReturnType(WINAPI * wglGetProcAddressFn)(LPCSTR); - assert(opengl != NULL); - { - wglGetProcAddressFn wglGetProcAddress = - (wglGetProcAddressFn)rmtGetProcAddress(opengl->dll_handle, "wglGetProcAddress"); - if (wglGetProcAddress != NULL) - return wglGetProcAddress(symbol); - } - } - -#elif defined(RMT_PLATFORM_MACOS) && !defined(GLEW_APPLE_GLX) - - return rmtGetProcAddress(opengl->dll_handle, symbol); - -#elif defined(RMT_PLATFORM_LINUX) - - return glXGetProcAddressARB((const GLubyte*)symbol); - -#endif - - return NULL; -} - -static rmtError OpenGL_Create(OpenGL** opengl) -{ - assert(opengl != NULL); - - rmtTryMalloc(OpenGL, *opengl); - - (*opengl)->dll_handle = NULL; - - (*opengl)->__glGetError = NULL; - (*opengl)->__glGenQueries = NULL; - (*opengl)->__glDeleteQueries = NULL; - (*opengl)->__glBeginQuery = NULL; - (*opengl)->__glEndQuery = NULL; - (*opengl)->__glGetQueryObjectiv = NULL; - (*opengl)->__glGetQueryObjectuiv = NULL; - (*opengl)->__glGetQueryObjecti64v = NULL; - (*opengl)->__glGetQueryObjectui64v = NULL; - (*opengl)->__glQueryCounter = NULL; - (*opengl)->__glGetInteger64v = NULL; - (*opengl)->__glFinish = NULL; - - (*opengl)->mq_to_opengl_main = NULL; - (*opengl)->first_timestamp = 0; - (*opengl)->last_resync = 0; - (*opengl)->flush_samples = NULL; - - rmtTryNew(Buffer, (*opengl)->flush_samples, 8 * 1024); - rmtTryNew(rmtMessageQueue, (*opengl)->mq_to_opengl_main, g_Settings.messageQueueSizeInBytes); - - return RMT_ERROR_NONE; -} - -static void OpenGL_Destructor(OpenGL* opengl) -{ - assert(opengl != NULL); - rmtDelete(rmtMessageQueue, opengl->mq_to_opengl_main); - rmtDelete(Buffer, opengl->flush_samples); -} - -static void SyncOpenGLCpuGpuTimes(rmtU64* out_first_timestamp, rmtU64* out_last_resync) -{ - rmtU64 cpu_time_start = 0; - rmtU64 cpu_time_stop = 0; - rmtU64 average_half_RTT = 0; // RTT = Rountrip Time. - GLint64 gpu_base = 0; - int i; - - rmtglFinish(); - - for (i = 0; i < RMT_GPU_CPU_SYNC_NUM_ITERATIONS; ++i) - { - rmtU64 half_RTT; - - rmtglFinish(); - cpu_time_start = usTimer_Get(&g_Remotery->timer); - rmtglGetInteger64v(GL_TIMESTAMP, &gpu_base); - cpu_time_stop = usTimer_Get(&g_Remotery->timer); - // Average the time it takes a roundtrip from CPU to GPU - // while doing nothing other than getting timestamps - half_RTT = (cpu_time_stop - cpu_time_start) >> 1ULL; - if (i == 0) - average_half_RTT = half_RTT; - else - average_half_RTT = (average_half_RTT + half_RTT) >> 1ULL; - } - - // All GPU times are offset from gpu_base, and then taken to - // the same relative origin CPU timestamps are based on. - // CPU is in us, we must translate it to ns. - *out_first_timestamp = (rmtU64)(gpu_base) - (cpu_time_start + average_half_RTT) * 1000ULL; - *out_last_resync = cpu_time_stop; -} - -typedef struct OpenGLTimestamp -{ - // Inherit so that timestamps can be quickly allocated - ObjectLink Link; - - // Pair of timestamp queries that wrap the sample - GLuint queries[2]; - rmtU64 cpu_timestamp; -} OpenGLTimestamp; - -static rmtError OpenGLTimestamp_Constructor(OpenGLTimestamp* stamp) -{ - GLenum error; - - assert(stamp != NULL); - - ObjectLink_Constructor((ObjectLink*)stamp); - - // Set defaults - stamp->queries[0] = stamp->queries[1] = 0; - stamp->cpu_timestamp = 0; - - // Empty the error queue before using it for glGenQueries - while ((error = rmtglGetError()) != GL_NO_ERROR) - ; - - // Create start/end timestamp queries - assert(g_Remotery != NULL); - rmtglGenQueries(2, stamp->queries); - error = rmtglGetError(); - if (error != GL_NO_ERROR) - return RMT_ERROR_OPENGL_ERROR; - - return RMT_ERROR_NONE; -} - -static void OpenGLTimestamp_Destructor(OpenGLTimestamp* stamp) -{ - assert(stamp != NULL); - - // Destroy queries - if (stamp->queries[0] != 0) - rmtglDeleteQueries(2, stamp->queries); -} - -static void OpenGLTimestamp_Begin(OpenGLTimestamp* stamp) -{ - assert(stamp != NULL); - - // First query - assert(g_Remotery != NULL); - stamp->cpu_timestamp = usTimer_Get(&g_Remotery->timer); - rmtglQueryCounter(stamp->queries[0], GL_TIMESTAMP); -} - -static void OpenGLTimestamp_End(OpenGLTimestamp* stamp) -{ - assert(stamp != NULL); - - // Second query - assert(g_Remotery != NULL); - rmtglQueryCounter(stamp->queries[1], GL_TIMESTAMP); -} - -static rmtBool OpenGLTimestamp_GetData(OpenGLTimestamp* stamp, rmtU64* out_start, rmtU64* out_end, - rmtU64* out_first_timestamp, rmtU64* out_last_resync) -{ - GLuint64 start = 0, end = 0; - GLint startAvailable = 0, endAvailable = 0; - - assert(g_Remotery != NULL); - - assert(stamp != NULL); - assert(stamp->queries[0] != 0 && stamp->queries[1] != 0); - - // Check to see if all queries are ready - // If any fail to arrive, wait until later - rmtglGetQueryObjectiv(stamp->queries[0], GL_QUERY_RESULT_AVAILABLE, &startAvailable); - if (!startAvailable) - return RMT_FALSE; - rmtglGetQueryObjectiv(stamp->queries[1], GL_QUERY_RESULT_AVAILABLE, &endAvailable); - if (!endAvailable) - return RMT_FALSE; - - rmtglGetQueryObjectui64v(stamp->queries[0], GL_QUERY_RESULT, &start); - rmtglGetQueryObjectui64v(stamp->queries[1], GL_QUERY_RESULT, &end); - - // Mark the first timestamp. We may resync if we detect the GPU timestamp is in the - // past (i.e. happened before the CPU command) since it should be impossible. - assert(out_first_timestamp != NULL); - if (*out_first_timestamp == 0 || ((start - *out_first_timestamp) / 1000ULL) < stamp->cpu_timestamp) - SyncOpenGLCpuGpuTimes(out_first_timestamp, out_last_resync); - - // Calculate start and end timestamps (we want us, the queries give us ns) - *out_start = (rmtU64)(start - *out_first_timestamp) / 1000ULL; - *out_end = (rmtU64)(end - *out_first_timestamp) / 1000ULL; - - return RMT_TRUE; -} - -typedef struct OpenGLSample -{ - // IS-A inheritance relationship - Sample base; - - OpenGLTimestamp* timestamp; - -} OpenGLSample; - -static rmtError OpenGLSample_Constructor(OpenGLSample* sample) -{ - assert(sample != NULL); - - // Chain to sample constructor - Sample_Constructor((Sample*)sample); - sample->base.type = RMT_SampleType_OpenGL; - rmtTryNew(OpenGLTimestamp, sample->timestamp); - - return RMT_ERROR_NONE; -} - -static void OpenGLSample_Destructor(OpenGLSample* sample) -{ - rmtDelete(OpenGLTimestamp, sample->timestamp); - Sample_Destructor((Sample*)sample); -} - -RMT_API void _rmt_BindOpenGL() -{ - if (g_Remotery != NULL) - { - OpenGL* opengl = g_Remotery->opengl; - assert(opengl != NULL); - -#if defined(RMT_PLATFORM_WINDOWS) - opengl->dll_handle = rmtLoadLibrary("opengl32.dll"); -#elif defined(RMT_PLATFORM_MACOS) - opengl->dll_handle = rmtLoadLibrary("/System/Library/Frameworks/OpenGL.framework/Versions/Current/OpenGL"); -#elif defined(RMT_PLATFORM_LINUX) - opengl->dll_handle = rmtLoadLibrary("libGL.so"); -#endif - - opengl->__glGetError = (PFNGLGETERRORPROC)rmtGetProcAddress(opengl->dll_handle, "glGetError"); - opengl->__glGenQueries = (PFNGLGENQUERIESPROC)rmtglGetProcAddress(opengl, "glGenQueries"); - opengl->__glDeleteQueries = (PFNGLDELETEQUERIESPROC)rmtglGetProcAddress(opengl, "glDeleteQueries"); - opengl->__glBeginQuery = (PFNGLBEGINQUERYPROC)rmtglGetProcAddress(opengl, "glBeginQuery"); - opengl->__glEndQuery = (PFNGLENDQUERYPROC)rmtglGetProcAddress(opengl, "glEndQuery"); - opengl->__glGetQueryObjectiv = (PFNGLGETQUERYOBJECTIVPROC)rmtglGetProcAddress(opengl, "glGetQueryObjectiv"); - opengl->__glGetQueryObjectuiv = (PFNGLGETQUERYOBJECTUIVPROC)rmtglGetProcAddress(opengl, "glGetQueryObjectuiv"); - opengl->__glGetQueryObjecti64v = - (PFNGLGETQUERYOBJECTI64VPROC)rmtglGetProcAddress(opengl, "glGetQueryObjecti64v"); - opengl->__glGetQueryObjectui64v = - (PFNGLGETQUERYOBJECTUI64VPROC)rmtglGetProcAddress(opengl, "glGetQueryObjectui64v"); - opengl->__glQueryCounter = (PFNGLQUERYCOUNTERPROC)rmtglGetProcAddress(opengl, "glQueryCounter"); - opengl->__glGetInteger64v = (PFNGLGETINTEGER64VPROC)rmtglGetProcAddress(opengl, "glGetInteger64v"); - opengl->__glFinish = (PFNGLFINISHPROC)rmtGetProcAddress(opengl->dll_handle, "glFinish"); - } -} - -static void UpdateOpenGLFrame(void); - -RMT_API void _rmt_UnbindOpenGL(void) -{ - if (g_Remotery != NULL) - { - OpenGL* opengl = g_Remotery->opengl; - assert(opengl != NULL); - - // Stall waiting for the OpenGL queue to empty into the Remotery queue - while (!rmtMessageQueue_IsEmpty(opengl->mq_to_opengl_main)) - UpdateOpenGLFrame(); - - // There will be a whole bunch of OpenGL sample trees queued up the remotery queue that need releasing - FreePendingSampleTrees(g_Remotery, RMT_SampleType_OpenGL, opengl->flush_samples); - - // Forcefully delete sample tree on this thread to release time stamps from - // the same thread that created them - Remotery_DeleteSampleTree(g_Remotery, RMT_SampleType_OpenGL); - - // Release reference to the OpenGL DLL - if (opengl->dll_handle != NULL) - { - rmtFreeLibrary(opengl->dll_handle); - opengl->dll_handle = NULL; - } - } -} - -static rmtError AllocateOpenGLSampleTree(SampleTree** ogl_tree) -{ - rmtTryNew(SampleTree, *ogl_tree, sizeof(OpenGLSample), (ObjConstructor)OpenGLSample_Constructor, - (ObjDestructor)OpenGLSample_Destructor); - return RMT_ERROR_NONE; -} - -RMT_API void _rmt_BeginOpenGLSample(rmtPStr name, rmtU32* hash_cache) -{ - ThreadProfiler* thread_profiler; - - if (g_Remotery == NULL) - return; - - if (ThreadProfilers_GetCurrentThreadProfiler(g_Remotery->threadProfilers, &thread_profiler) == RMT_ERROR_NONE) - { - Sample* sample; - rmtU32 name_hash = ThreadProfiler_GetNameHash(thread_profiler, g_Remotery->mq_to_rmt_thread, name, hash_cache); - - // Create the OpenGL tree on-demand as the tree needs an up-front-created root. - // This is not possible to create on initialisation as a OpenGL binding is not yet available. - SampleTree** ogl_tree = &thread_profiler->sampleTrees[RMT_SampleType_OpenGL]; - if (*ogl_tree == NULL) - { - AllocateOpenGLSampleTree(ogl_tree); - } - - // Push the sample and activate the timestamp - if (ThreadProfiler_Push(*ogl_tree, name_hash, 0, &sample) == RMT_ERROR_NONE) - { - OpenGLSample* ogl_sample = (OpenGLSample*)sample; - ogl_sample->base.usGpuIssueOnCpu = usTimer_Get(&g_Remotery->timer); - OpenGLTimestamp_Begin(ogl_sample->timestamp); - } - } -} - -static rmtBool GetOpenGLSampleTimes(Sample* sample, rmtU64* out_first_timestamp, rmtU64* out_last_resync) -{ - Sample* child; - - OpenGLSample* ogl_sample = (OpenGLSample*)sample; - - assert(sample != NULL); - if (ogl_sample->timestamp != NULL) - { - assert(out_last_resync != NULL); -#if (RMT_GPU_CPU_SYNC_SECONDS > 0) - if (*out_last_resync < ogl_sample->timestamp->cpu_timestamp) - { - // Convert from us to seconds. - rmtU64 time_diff = (ogl_sample->timestamp->cpu_timestamp - *out_last_resync) / 1000000ULL; - if (time_diff > RMT_GPU_CPU_SYNC_SECONDS) - SyncOpenGLCpuGpuTimes(out_first_timestamp, out_last_resync); - } -#endif - - if (!OpenGLTimestamp_GetData(ogl_sample->timestamp, &sample->us_start, &sample->us_end, out_first_timestamp, - out_last_resync)) - return RMT_FALSE; - - sample->us_length = sample->us_end - sample->us_start; - } - - // Get child sample times - for (child = sample->first_child; child != NULL; child = child->next_sibling) - { - if (!GetOpenGLSampleTimes(child, out_first_timestamp, out_last_resync)) - return RMT_FALSE; - } - - return RMT_TRUE; -} - -static void UpdateOpenGLFrame(void) -{ - OpenGL* opengl; - - if (g_Remotery == NULL) - return; - - opengl = g_Remotery->opengl; - assert(opengl != NULL); - - rmt_BeginCPUSample(rmt_UpdateOpenGLFrame, 0); - - // Process all messages in the OpenGL queue - while (1) - { - Msg_SampleTree* sample_tree; - Sample* sample; - - Message* message = rmtMessageQueue_PeekNextMessage(opengl->mq_to_opengl_main); - if (message == NULL) - break; - - // There's only one valid message type in this queue - assert(message->id == MsgID_SampleTree); - sample_tree = (Msg_SampleTree*)message->payload; - sample = sample_tree->rootSample; - assert(sample->type == RMT_SampleType_OpenGL); - - // Retrieve timing of all OpenGL samples - // If they aren't ready leave the message unconsumed, holding up later frames and maintaining order - if (!GetOpenGLSampleTimes(sample, &opengl->first_timestamp, &opengl->last_resync)) - break; - - // Pass samples onto the remotery thread for sending to the viewer - QueueSampleTree(g_Remotery->mq_to_rmt_thread, sample, sample_tree->allocator, sample_tree->threadName, 0, - message->threadProfiler, RMT_FALSE); - rmtMessageQueue_ConsumeNextMessage(opengl->mq_to_opengl_main, message); - } - - rmt_EndCPUSample(); -} - -RMT_API void _rmt_EndOpenGLSample(void) -{ - ThreadProfiler* thread_profiler; - - if (g_Remotery == NULL) - return; - - if (ThreadProfilers_GetCurrentThreadProfiler(g_Remotery->threadProfilers, &thread_profiler) == RMT_ERROR_NONE) - { - // Close the timestamp - OpenGLSample* ogl_sample = (OpenGLSample*)thread_profiler->sampleTrees[RMT_SampleType_OpenGL]->currentParent; - if (ogl_sample->base.recurse_depth > 0) - { - ogl_sample->base.recurse_depth--; - } - else - { - if (ogl_sample->timestamp != NULL) - OpenGLTimestamp_End(ogl_sample->timestamp); - - // Send to the update loop for ready-polling - if (ThreadProfiler_Pop(thread_profiler, g_Remotery->opengl->mq_to_opengl_main, (Sample*)ogl_sample, 0)) - // Perform ready-polling on popping of the root sample - UpdateOpenGLFrame(); - } - } -} - -#endif // RMT_USE_OPENGL - -/* - ------------------------------------------------------------------------------------------------------------------------ - ------------------------------------------------------------------------------------------------------------------------ - @Metal: Metal event sampling - ------------------------------------------------------------------------------------------------------------------------ - ------------------------------------------------------------------------------------------------------------------------ - */ - -#if RMT_USE_METAL - -struct Metal_t -{ - // Queue to the Metal main update thread - // Given that BeginSample/EndSample need to be called from the same thread that does the update, there - // is really no need for this to be a thread-safe queue. I'm using it for its convenience. - rmtMessageQueue* mq_to_metal_main; -}; - -static rmtError Metal_Create(Metal** metal) -{ - assert(metal != NULL); - - rmtTryMallocArray(Metal, *metal); - - (*metal)->mq_to_metal_main = NULL; - - rmtTryNew(rmtMessageQueue, (*metal)->mq_to_metal_main, g_Settings.messageQueueSizeInBytes); - return error; -} - -static void Metal_Destructor(Metal* metal) -{ - assert(metal != NULL); - rmtDelete(rmtMessageQueue, metal->mq_to_metal_main); -} - -typedef struct MetalTimestamp -{ - // Inherit so that timestamps can be quickly allocated - ObjectLink Link; - - // Output from GPU callbacks - rmtU64 start; - rmtU64 end; - rmtBool ready; -} MetalTimestamp; - -static rmtError MetalTimestamp_Constructor(MetalTimestamp* stamp) -{ - assert(stamp != NULL); - - ObjectLink_Constructor((ObjectLink*)stamp); - - // Set defaults - stamp->start = 0; - stamp->end = 0; - stamp->ready = RMT_FALSE; - - return RMT_ERROR_NONE; -} - -static void MetalTimestamp_Destructor(MetalTimestamp* stamp) -{ - assert(stamp != NULL); -} - -rmtU64 rmtMetal_usGetTime() -{ - // Share the CPU timer for auto-sync - assert(g_Remotery != NULL); - return usTimer_Get(&g_Remotery->timer); -} - -void rmtMetal_MeasureCommandBuffer(unsigned long long* out_start, unsigned long long* out_end, unsigned int* out_ready); - -static void MetalTimestamp_Begin(MetalTimestamp* stamp) -{ - assert(stamp != NULL); - stamp->ready = RMT_FALSE; - - // Metal can currently only issue callbacks at the command buffer level - // So for now measure execution of the entire command buffer - rmtMetal_MeasureCommandBuffer(&stamp->start, &stamp->end, &stamp->ready); -} - -static void MetalTimestamp_End(MetalTimestamp* stamp) -{ - assert(stamp != NULL); - - // As Metal can currently only measure entire command buffers, this function is a no-op - // as the completed handler was already issued in Begin -} - -static rmtBool MetalTimestamp_GetData(MetalTimestamp* stamp, rmtU64* out_start, rmtU64* out_end) -{ - assert(g_Remotery != NULL); - assert(stamp != NULL); - - // GPU writes ready flag when complete handler is called - if (stamp->ready == RMT_FALSE) - return RMT_FALSE; - - *out_start = stamp->start; - *out_end = stamp->end; - - return RMT_TRUE; -} - -typedef struct MetalSample -{ - // IS-A inheritance relationship - Sample base; - - MetalTimestamp* timestamp; - -} MetalSample; - -static rmtError MetalSample_Constructor(MetalSample* sample) -{ - assert(sample != NULL); - - // Chain to sample constructor - Sample_Constructor((Sample*)sample); - sample->base.type = RMT_SampleType_Metal; - rmtTryNew(MetalTimestamp, sample->timestamp); - - return RMT_ERROR_NONE; -} - -static void MetalSample_Destructor(MetalSample* sample) -{ - rmtDelete(MetalTimestamp, sample->timestamp); - Sample_Destructor((Sample*)sample); -} - -static void UpdateOpenGLFrame(void); - -/*RMT_API void _rmt_UnbindMetal(void) -{ - if (g_Remotery != NULL) - { - Metal* metal = g_Remotery->metal; - assert(metal != NULL); - - // Stall waiting for the Metal queue to empty into the Remotery queue - while (!rmtMessageQueue_IsEmpty(metal->mq_to_metal_main)) - UpdateMetalFrame(); - - // Forcefully delete sample tree on this thread to release time stamps from - // the same thread that created them - Remotery_BlockingDeleteSampleTree(g_Remotery, RMT_SampleType_Metal); - } -}*/ - -RMT_API void _rmt_BeginMetalSample(rmtPStr name, rmtU32* hash_cache) -{ - ThreadProfiler* thread_profiler; - - if (g_Remotery == NULL) - return; - - if (ThreadProfilers_GetCurrentThreadProfiler(g_Remotery->threadProfilers, &thread_profiler) == RMT_ERROR_NONE) - { - Sample* sample; - rmtU32 name_hash = ThreadProfiler_GetNameHash(thread_profiler, g_Remotery->mq_to_rmt_thread, name, hash_cache); - - // Create the Metal tree on-demand as the tree needs an up-front-created root. - // This is not possible to create on initialisation as a Metal binding is not yet available. - SampleTree** metal_tree = &thread_profiler->sampleTrees[RMT_SampleType_Metal]; - if (*metal_tree == NULL) - { - rmtError error; - rmtTryNew(SampleTree, *metal_tree, sizeof(MetalSample), (ObjConstructor)MetalSample_Constructor, - (ObjDestructor)MetalSample_Destructor); - if (error != RMT_ERROR_NONE) - return; - } - - // Push the sample and activate the timestamp - if (ThreadProfiler_Push(*metal_tree, name_hash, 0, &sample) == RMT_ERROR_NONE) - { - MetalSample* metal_sample = (MetalSample*)sample; - metal_sample->base.usGpuIssueOnCpu = usTimer_Get(&g_Remotery->timer); - MetalTimestamp_Begin(metal_sample->timestamp); - } - } -} - -static rmtBool GetMetalSampleTimes(Sample* sample) -{ - Sample* child; - - MetalSample* metal_sample = (MetalSample*)sample; - - assert(sample != NULL); - if (metal_sample->timestamp != NULL) - { - if (!MetalTimestamp_GetData(metal_sample->timestamp, &sample->us_start, &sample->us_end)) - return RMT_FALSE; - - sample->us_length = sample->us_end - sample->us_start; - } - - // Get child sample times - for (child = sample->first_child; child != NULL; child = child->next_sibling) - { - if (!GetMetalSampleTimes(child)) - return RMT_FALSE; - } - - return RMT_TRUE; -} - -static void UpdateMetalFrame(void) -{ - Metal* metal; - - if (g_Remotery == NULL) - return; - - metal = g_Remotery->metal; - assert(metal != NULL); - - rmt_BeginCPUSample(rmt_UpdateMetalFrame, 0); - - // Process all messages in the Metal queue - while (1) - { - Msg_SampleTree* sample_tree; - Sample* sample; - - Message* message = rmtMessageQueue_PeekNextMessage(metal->mq_to_metal_main); - if (message == NULL) - break; - - // There's only one valid message type in this queue - assert(message->id == MsgID_SampleTree); - sample_tree = (Msg_SampleTree*)message->payload; - sample = sample_tree->rootSample; - assert(sample->type == RMT_SampleType_Metal); - - // Retrieve timing of all Metal samples - // If they aren't ready leave the message unconsumed, holding up later frames and maintaining order - if (!GetMetalSampleTimes(sample)) - break; - - // Pass samples onto the remotery thread for sending to the viewer - QueueSampleTree(g_Remotery->mq_to_rmt_thread, sample, sample_tree->allocator, sample_tree->threadName, 0, - message->threadProfiler, RMT_FALSE); - rmtMessageQueue_ConsumeNextMessage(metal->mq_to_metal_main, message); - } - - rmt_EndCPUSample(); -} - -RMT_API void _rmt_EndMetalSample(void) -{ - ThreadProfiler* thread_profiler; - - if (g_Remotery == NULL) - return; - - if (ThreadProfilers_GetCurrentThreadProfiler(g_Remotery->threadProfilers, &thread_profiler) == RMT_ERROR_NONE) - { - // Close the timestamp - MetalSample* metal_sample = (MetalSample*)thread_profiler->sampleTrees[RMT_SampleType_Metal]->currentParent; - if (metal_sample->base.recurse_depth > 0) - { - metal_sample->base.recurse_depth--; - } - else - { - if (metal_sample->timestamp != NULL) - MetalTimestamp_End(metal_sample->timestamp); - - // Send to the update loop for ready-polling - if (ThreadProfiler_Pop(thread_profiler, g_Remotery->metal->mq_to_metal_main, (Sample*)metal_sample, 0)) - // Perform ready-polling on popping of the root sample - UpdateMetalFrame(); - } - } -} - -#endif // RMT_USE_METAL - -/* ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- -@SAMPLEAPI: Sample API for user callbacks ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- -*/ - -// Iterator -RMT_API void _rmt_IterateChildren(rmtSampleIterator* iterator, rmtSample* sample) -{ - iterator->sample = 0; - iterator->initial = sample != NULL ? sample->first_child : 0; -} - -RMT_API rmtBool _rmt_IterateNext(rmtSampleIterator* iter) -{ - if (iter->initial != NULL) - { - iter->sample = iter->initial; - iter->initial = 0; - } - else - { - if (iter->sample != NULL) - iter->sample = iter->sample->next_sibling; - } - - return iter->sample != NULL ? RMT_TRUE : RMT_FALSE; -} - -// Sample tree accessors -RMT_API const char* _rmt_SampleTreeGetThreadName(rmtSampleTree* sample_tree) -{ - return sample_tree->threadName; -} - -RMT_API rmtSample* _rmt_SampleTreeGetRootSample(rmtSampleTree* sample_tree) -{ - return sample_tree->rootSample; -} - -// Sample accessors -RMT_API const char* _rmt_SampleGetName(rmtSample* sample) -{ - const char* name = StringTable_Find(g_Remotery->string_table, sample->name_hash); - if (name == NULL) - { - return "null"; - } - return name; -} - -RMT_API rmtU32 _rmt_SampleGetNameHash(rmtSample* sample) -{ - return sample->name_hash; -} - -RMT_API rmtU32 _rmt_SampleGetCallCount(rmtSample* sample) -{ - return sample->call_count; -} - -RMT_API rmtU64 _rmt_SampleGetStart(rmtSample* sample) -{ - return sample->us_start; -} - -RMT_API rmtU64 _rmt_SampleGetTime(rmtSample* sample) -{ - return sample->us_length; -} - -RMT_API rmtU64 _rmt_SampleGetSelfTime(rmtSample* sample) -{ - return (rmtU64)maxS64(sample->us_length - sample->us_sampled_length, 0); -} - -RMT_API rmtSampleType _rmt_SampleGetType(rmtSample* sample) -{ - return sample->type; -} - -RMT_API void _rmt_SampleGetColour(rmtSample* sample, rmtU8* r, rmtU8* g, rmtU8* b) -{ - *r = sample->uniqueColour[0]; - *g = sample->uniqueColour[1]; - *b = sample->uniqueColour[2]; -} - -/* ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- -@PROPERTYAPI: Property API for user callbacks ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- -*/ - -// Iterator -RMT_API void _rmt_PropertyIterateChildren(rmtPropertyIterator* iterator, rmtProperty* property) -{ - iterator->property = 0; - iterator->initial = property != NULL ? property->firstChild : 0; -} - -RMT_API rmtBool _rmt_PropertyIterateNext(rmtPropertyIterator* iter) -{ - if (iter->initial != NULL) - { - iter->property = iter->initial; - iter->initial = 0; - } - else - { - if (iter->property != NULL) - iter->property = iter->property->nextSibling; - } - - return iter->property != NULL ? RMT_TRUE : RMT_FALSE; -} - -// Property accessors -RMT_API const char* _rmt_PropertyGetName(rmtProperty* property) -{ - return property->name; -} - -RMT_API const char* _rmt_PropertyGetDescription(rmtProperty* property) -{ - return property->description; -} - -RMT_API rmtU32 _rmt_PropertyGetNameHash(rmtProperty* property) -{ - return property->nameHash; -} - -RMT_API rmtPropertyType _rmt_PropertyGetType(rmtProperty* property) -{ - return property->type; -} - -RMT_API rmtPropertyValue _rmt_PropertyGetValue(rmtProperty* property) -{ - return property->value; -} - -/* ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- -@PROPERTIES: Property API ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- -*/ - -static void RegisterProperty(rmtProperty* property, rmtBool can_lock) -{ - if (property->initialised == RMT_FALSE) - { - // Apply for a lock once at the start of the recursive walk - if (can_lock) - { - mtxLock(&g_Remotery->propertyMutex); - } - - // Multiple threads accessing the same property can apply for the lock at the same time as the `initialised` property for - // each of them may not be set yet. One thread only will get the lock successfully while the others will only come through - // here when the first thread has finished initialising. The first thread through will have `initialised` set to RMT_FALSE - // while all other threads will see it in its initialised state. Skip those so that we don't register multiple times. - if (property->initialised == RMT_FALSE) - { - rmtU32 name_len; - - // With no parent, add this to the root property - rmtProperty* parent_property = property->parent; - if (parent_property == NULL) - { - property->parent = &g_Remotery->rootProperty; - parent_property = property->parent; - } - - // Walk up to parent properties first in case they haven't been registered - RegisterProperty(parent_property, RMT_FALSE); - - // Link this property into the parent's list - if (parent_property->firstChild != NULL) - { - parent_property->lastChild->nextSibling = property; - parent_property->lastChild = property; - } - else - { - parent_property->firstChild = property; - parent_property->lastChild = property; - } - - // Calculate the name hash and send it to the viewer - name_len = strnlen_s(property->name, 256); - property->nameHash = _rmt_HashString32(property->name, name_len, 0); - QueueAddToStringTable(g_Remotery->mq_to_rmt_thread, property->nameHash, property->name, name_len, NULL); - - // Generate a unique ID for this property in the tree - property->uniqueID = parent_property->uniqueID; - property->uniqueID = HashCombine(property->uniqueID, property->nameHash); - - property->initialised = RMT_TRUE; - } - - // Unlock on the way out of recursive walk - if (can_lock) - { - mtxUnlock(&g_Remotery->propertyMutex); - } - } -} - -RMT_API void _rmt_PropertySetValue(rmtProperty* property) -{ - if (g_Remotery == NULL) - { - return; - } - - RegisterProperty(property, RMT_TRUE); - - // on this thread, create a new sample that encodes the value just set - - // send the sample to remotery UI and disk log - - // value resets and sets don't have delta values, really -} - -RMT_API void _rmt_PropertyAddValue(rmtProperty* property, rmtPropertyValue add_value) -{ - if (g_Remotery == NULL) - { - return; - } - - RegisterProperty(property, RMT_TRUE); - - // use `add_value` to determine how much this property was changed - - // on this thread, create a new sample that encodes the delta and parents itself to `property` - // could also encode the current value of the property at this point - - // send the sample to remotery UI and disk log -} - -static rmtError TakePropertySnapshot(rmtProperty* property, PropertySnapshot* parent_snapshot, PropertySnapshot** first_snapshot, PropertySnapshot** prev_snapshot, rmtU32 depth) -{ - rmtError error; - rmtProperty* child_property; - - // Allocate some state for the property - PropertySnapshot* snapshot; - error = ObjectAllocator_Alloc(g_Remotery->propertyAllocator, (void**)&snapshot); - if (error != RMT_ERROR_NONE) - { - return error; - } - - // Snapshot the property - snapshot->type = property->type; - snapshot->value = property->value; - snapshot->prevValue = property->prevValue; - snapshot->prevValueFrame = property->prevValueFrame; - snapshot->nameHash = property->nameHash; - snapshot->uniqueID = property->uniqueID; - snapshot->nbChildren = 0; - snapshot->depth = depth; - snapshot->nextSnapshot = NULL; - - // Keep count of the number of children in the parent - if (parent_snapshot != NULL) - { - parent_snapshot->nbChildren++; - } - - // Link into the linear list - if (*first_snapshot == NULL) - { - *first_snapshot = snapshot; - } - if (*prev_snapshot != NULL) - { - (*prev_snapshot)->nextSnapshot = snapshot; - } - *prev_snapshot = snapshot; - - // Snapshot the children - for (child_property = property->firstChild; child_property != NULL; child_property = child_property->nextSibling) - { - error = TakePropertySnapshot(child_property, snapshot, first_snapshot, prev_snapshot, depth + 1); - if (error != RMT_ERROR_NONE) - { - return error; - } - } - - return RMT_ERROR_NONE; -} - -RMT_API rmtError _rmt_PropertySnapshotAll() -{ - rmtError error; - PropertySnapshot* first_snapshot; - PropertySnapshot* prev_snapshot; - Msg_PropertySnapshot* payload; - Message* message; - rmtU32 nb_snapshot_allocs; - - if (g_Remotery == NULL) - { - return RMT_ERROR_REMOTERY_NOT_CREATED; - } - - // Don't do anything if any properties haven't been registered yet - if (g_Remotery->rootProperty.firstChild == NULL) - { - return RMT_ERROR_NONE; - } - - // Mark current allocation count so we can quickly calculate the number of snapshots being sent - nb_snapshot_allocs = g_Remotery->propertyAllocator->nb_inuse; - - // Snapshot from the root into a linear list - first_snapshot = NULL; - prev_snapshot = NULL; - mtxLock(&g_Remotery->propertyMutex); - error = TakePropertySnapshot(&g_Remotery->rootProperty, NULL, &first_snapshot, &prev_snapshot, 0); - - if (g_Settings.snapshot_callback != NULL) - { - g_Settings.snapshot_callback(g_Settings.snapshot_context, &g_Remotery->rootProperty); - } - - mtxUnlock(&g_Remotery->propertyMutex); - if (error != RMT_ERROR_NONE) - { - FreePropertySnapshots(first_snapshot); - return error; - } - - // Attempt to allocate a message for sending the snapshot to the viewer - message = rmtMessageQueue_AllocMessage(g_Remotery->mq_to_rmt_thread, sizeof(Msg_PropertySnapshot), NULL); - if (message == NULL) - { - FreePropertySnapshots(first_snapshot); - return RMT_ERROR_UNKNOWN; - } - - // Populate and commit - payload = (Msg_PropertySnapshot*)message->payload; - payload->rootSnapshot = first_snapshot; - payload->nbSnapshots = g_Remotery->propertyAllocator->nb_inuse - nb_snapshot_allocs; - payload->propertyFrame = g_Remotery->propertyFrame; - rmtMessageQueue_CommitMessage(message, MsgID_PropertySnapshot); - - return RMT_ERROR_NONE; -} - -static void PropertyFrameReset(Remotery* rmt, rmtProperty* first_property) -{ - rmtProperty* property; - for (property = first_property; property != NULL; property = property->nextSibling) - { - // TODO(don): It might actually be quicker to sign-extend assignments but this gives me a nice debug hook for now - rmtBool changed = RMT_FALSE; - switch (property->type) - { - case RMT_PropertyType_rmtGroup: - PropertyFrameReset(rmt, property->firstChild); - break; - - case RMT_PropertyType_rmtBool: - changed = property->lastFrameValue.Bool != property->value.Bool; - break; - - case RMT_PropertyType_rmtS32: - case RMT_PropertyType_rmtU32: - case RMT_PropertyType_rmtF32: - changed = property->lastFrameValue.U32 != property->value.U32; - break; - - case RMT_PropertyType_rmtS64: - case RMT_PropertyType_rmtU64: - case RMT_PropertyType_rmtF64: - changed = property->lastFrameValue.U64 != property->value.U64; - break; - } - - if (changed) - { - property->prevValue = property->lastFrameValue; - property->prevValueFrame = rmt->propertyFrame; - } - - property->lastFrameValue = property->value; - - if ((property->flags & RMT_PropertyFlags_FrameReset) != 0) - { - property->value = property->defaultValue; - } - } -} - -RMT_API void _rmt_PropertyFrameResetAll() -{ - if (g_Remotery == NULL) - { - return; - } - - mtxLock(&g_Remotery->propertyMutex); - PropertyFrameReset(g_Remotery, g_Remotery->rootProperty.firstChild); - mtxUnlock(&g_Remotery->propertyMutex); - - g_Remotery->propertyFrame++; -} - -#endif // RMT_ENABLED diff --git a/profiler/lib/Remotery.h b/profiler/lib/Remotery.h deleted file mode 100644 index 9cd0c85..0000000 --- a/profiler/lib/Remotery.h +++ /dev/null @@ -1,1095 +0,0 @@ - -/* -Copyright 2014-2022 Celtoys Ltd - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -/* - -Compiling ---------- - -* Windows (MSVC) - add lib/Remotery.c and lib/Remotery.h to your program. Set include - directories to add Remotery/lib path. The required library ws2_32.lib should be picked - up through the use of the #pragma comment(lib, "ws2_32.lib") directive in Remotery.c. - -* Mac OS X (XCode) - simply add lib/Remotery.c and lib/Remotery.h to your program. - -* Linux (GCC) - add the source in lib folder. Compilation of the code requires -pthreads for - library linkage. For example to compile the same run: cc lib/Remotery.c sample/sample.c - -I lib -pthread -lm - -You can define some extra macros to modify what features are compiled into Remotery. These are -documented just below this comment. - -*/ - - -#ifndef RMT_INCLUDED_H -#define RMT_INCLUDED_H - - -// Set to 0 to not include any bits of Remotery in your build -#ifndef RMT_ENABLED -#define RMT_ENABLED 1 -#endif - -// Help performance of the server sending data to the client by marking this machine as little-endian -#ifndef RMT_ASSUME_LITTLE_ENDIAN -#define RMT_ASSUME_LITTLE_ENDIAN 0 -#endif - -// Used by the Celtoys TinyCRT library (not released yet) -#ifndef RMT_USE_TINYCRT -#define RMT_USE_TINYCRT 0 -#endif - -// Assuming CUDA headers/libs are setup, allow CUDA profiling -#ifndef RMT_USE_CUDA -#define RMT_USE_CUDA 0 -#endif - -// Assuming Direct3D 11 headers/libs are setup, allow D3D11 profiling -#ifndef RMT_USE_D3D11 -#define RMT_USE_D3D11 0 -#endif - -// Allow D3D12 profiling -#ifndef RMT_USE_D3D12 -#define RMT_USE_D3D12 0 -#endif - -// Allow OpenGL profiling -#ifndef RMT_USE_OPENGL -#define RMT_USE_OPENGL 0 -#endif - -// Allow Metal profiling -#ifndef RMT_USE_METAL -#define RMT_USE_METAL 0 -#endif - -// Initially use POSIX thread names to name threads instead of Thread0, 1, ... -#ifndef RMT_USE_POSIX_THREADNAMES -#define RMT_USE_POSIX_THREADNAMES 0 -#endif - -// How many times we spin data back and forth between CPU & GPU -// to calculate average RTT (Roundtrip Time). Cannot be 0. -// Affects OpenGL & D3D11 -#ifndef RMT_GPU_CPU_SYNC_NUM_ITERATIONS -#define RMT_GPU_CPU_SYNC_NUM_ITERATIONS 16 -#endif - -// Time in seconds between each resync to compensate for drifting between GPU & CPU timers, -// effects of power saving, etc. Resyncs can cause stutter, lag spikes, stalls. -// Set to 0 for never. -// Affects OpenGL & D3D11 -#ifndef RMT_GPU_CPU_SYNC_SECONDS -#define RMT_GPU_CPU_SYNC_SECONDS 30 -#endif - -// Whether we should automatically resync if we detect a timer disjoint (e.g. -// changed from AC power to battery, GPU is overheating, or throttling up/down -// due to laptop savings events). Set it to 0 to avoid resync in such events. -// Useful if for some odd reason a driver reports a lot of disjoints. -// Affects D3D11 -#ifndef RMT_D3D11_RESYNC_ON_DISJOINT -#define RMT_D3D11_RESYNC_ON_DISJOINT 1 -#endif - -// If RMT_USE_INTERNAL_HASH_FUNCTION is defined to 1, the internal hash function for strings is used. -// This is the default setting. -// If you set RMT_USE_INTERNAL_HASH_FUNCTION to 0, you must implement rmt_HashString32 yourself. -#ifndef RMT_USE_INTERNAL_HASH_FUNCTION -#define RMT_USE_INTERNAL_HASH_FUNCTION 1 -#endif - -/*-------------------------------------------------------------------------------------------------------------------------------- - Compiler/Platform Detection and Preprocessor Utilities ----------------------------------------------------------------------------------------------------------------------------------*/ - - -// Platform identification -#if defined(_WINDOWS) || defined(_WIN32) - #define RMT_PLATFORM_WINDOWS -#elif defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) - #define RMT_PLATFORM_LINUX - #define RMT_PLATFORM_POSIX -#elif defined(__APPLE__) - #define RMT_PLATFORM_MACOS - #define RMT_PLATFORM_POSIX -#endif - -// Architecture identification -#ifdef RMT_PLATFORM_WINDOWS -#ifdef _M_AMD64 -#define RMT_ARCH_64BIT -#else -#define RMT_ARCH_32BIT -#endif -#endif - -#ifdef RMT_DLL - #if defined (RMT_PLATFORM_WINDOWS) - #if defined (RMT_IMPL) - #define RMT_API __declspec(dllexport) - #else - #define RMT_API __declspec(dllimport) - #endif - #elif defined (RMT_PLATFORM_POSIX) - #if defined (RMT_IMPL) - #define RMT_API __attribute__((visibility("default"))) - #else - #define RMT_API - #endif - #endif -#else - #define RMT_API -#endif - -// Allows macros to be written that can work around the inability to do: #define(x) #ifdef x -// with the C preprocessor. -#if RMT_ENABLED - #define IFDEF_RMT_ENABLED(t, f) t -#else - #define IFDEF_RMT_ENABLED(t, f) f -#endif -#if RMT_ENABLED && RMT_USE_CUDA - #define IFDEF_RMT_USE_CUDA(t, f) t -#else - #define IFDEF_RMT_USE_CUDA(t, f) f -#endif -#if RMT_ENABLED && RMT_USE_D3D11 - #define IFDEF_RMT_USE_D3D11(t, f) t -#else - #define IFDEF_RMT_USE_D3D11(t, f) f -#endif -#if RMT_ENABLED && RMT_USE_D3D12 - #define IFDEF_RMT_USE_D3D12(t, f) t -#else - #define IFDEF_RMT_USE_D3D12(t, f) f -#endif -#if RMT_ENABLED && RMT_USE_OPENGL - #define IFDEF_RMT_USE_OPENGL(t, f) t -#else - #define IFDEF_RMT_USE_OPENGL(t, f) f -#endif -#if RMT_ENABLED && RMT_USE_METAL - #define IFDEF_RMT_USE_METAL(t, f) t -#else - #define IFDEF_RMT_USE_METAL(t, f) f -#endif - - -// Public interface is written in terms of these macros to easily enable/disable itself -#define RMT_OPTIONAL(macro, x) IFDEF_ ## macro(x, ) -#define RMT_OPTIONAL_RET(macro, x, y) IFDEF_ ## macro(x, (y)) - - -/*-------------------------------------------------------------------------------------------------------------------------------- - Types ---------------------------------------------------------------------------------------------------------------------------------*/ - - -// Boolean -typedef unsigned int rmtBool; -#define RMT_TRUE ((rmtBool)1) -#define RMT_FALSE ((rmtBool)0) - -// Unsigned integer types -typedef unsigned char rmtU8; -typedef unsigned short rmtU16; -typedef unsigned int rmtU32; -typedef unsigned long long rmtU64; - -// Signed integer types -typedef char rmtS8; -typedef short rmtS16; -typedef int rmtS32; -typedef long long rmtS64; - -// Float types -typedef float rmtF32; -typedef double rmtF64; - -// Const, null-terminated string pointer -typedef const char* rmtPStr; - -// Opaque pointer for a sample graph tree -typedef struct Msg_SampleTree rmtSampleTree; - -// Opaque pointer to a node in the sample graph tree -typedef struct Sample rmtSample; - -// Handle to the main remotery instance -typedef struct Remotery Remotery; - -// Forward declaration -struct rmtProperty; - -typedef enum rmtSampleType -{ - RMT_SampleType_CPU, - RMT_SampleType_CUDA, - RMT_SampleType_D3D11, - RMT_SampleType_D3D12, - RMT_SampleType_OpenGL, - RMT_SampleType_Metal, - RMT_SampleType_Count, -} rmtSampleType; - -// All possible error codes -// clang-format off -typedef enum rmtError -{ - RMT_ERROR_NONE, - RMT_ERROR_RECURSIVE_SAMPLE, // Not an error but an internal message to calling code - RMT_ERROR_UNKNOWN, // An error with a message yet to be defined, only for internal error handling - RMT_ERROR_INVALID_INPUT, // An invalid input to a function call was provided - RMT_ERROR_RESOURCE_CREATE_FAIL, // Creation of an internal resource failed - RMT_ERROR_RESOURCE_ACCESS_FAIL, // Access of an internal resource failed - RMT_ERROR_TIMEOUT, // Internal system timeout - - // System errors - RMT_ERROR_MALLOC_FAIL, // Malloc call within remotery failed - RMT_ERROR_TLS_ALLOC_FAIL, // Attempt to allocate thread local storage failed - RMT_ERROR_VIRTUAL_MEMORY_BUFFER_FAIL, // Failed to create a virtual memory mirror buffer - RMT_ERROR_CREATE_THREAD_FAIL, // Failed to create a thread for the server - RMT_ERROR_OPEN_THREAD_HANDLE_FAIL, // Failed to open a thread handle, given a thread id - - // Network TCP/IP socket errors - RMT_ERROR_SOCKET_INVALID_POLL, // Poll attempt on an invalid socket - RMT_ERROR_SOCKET_SELECT_FAIL, // Server failed to call select on socket - RMT_ERROR_SOCKET_POLL_ERRORS, // Poll notified that the socket has errors - RMT_ERROR_SOCKET_SEND_FAIL, // Unrecoverable error occured while client/server tried to send data - RMT_ERROR_SOCKET_RECV_NO_DATA, // No data available when attempting a receive - RMT_ERROR_SOCKET_RECV_TIMEOUT, // Timed out trying to receive data - RMT_ERROR_SOCKET_RECV_FAILED, // Unrecoverable error occured while client/server tried to receive data - - // WebSocket errors - RMT_ERROR_WEBSOCKET_HANDSHAKE_NOT_GET, // WebSocket server handshake failed, not HTTP GET - RMT_ERROR_WEBSOCKET_HANDSHAKE_NO_VERSION, // WebSocket server handshake failed, can't locate WebSocket version - RMT_ERROR_WEBSOCKET_HANDSHAKE_BAD_VERSION, // WebSocket server handshake failed, unsupported WebSocket version - RMT_ERROR_WEBSOCKET_HANDSHAKE_NO_HOST, // WebSocket server handshake failed, can't locate host - RMT_ERROR_WEBSOCKET_HANDSHAKE_BAD_HOST, // WebSocket server handshake failed, host is not allowed to connect - RMT_ERROR_WEBSOCKET_HANDSHAKE_NO_KEY, // WebSocket server handshake failed, can't locate WebSocket key - RMT_ERROR_WEBSOCKET_HANDSHAKE_BAD_KEY, // WebSocket server handshake failed, WebSocket key is ill-formed - RMT_ERROR_WEBSOCKET_HANDSHAKE_STRING_FAIL, // WebSocket server handshake failed, internal error, bad string code - RMT_ERROR_WEBSOCKET_DISCONNECTED, // WebSocket server received a disconnect request and closed the socket - RMT_ERROR_WEBSOCKET_BAD_FRAME_HEADER, // Couldn't parse WebSocket frame header - RMT_ERROR_WEBSOCKET_BAD_FRAME_HEADER_SIZE, // Partially received wide frame header size - RMT_ERROR_WEBSOCKET_BAD_FRAME_HEADER_MASK, // Partially received frame header data mask - RMT_ERROR_WEBSOCKET_RECEIVE_TIMEOUT, // Timeout receiving frame header - - RMT_ERROR_REMOTERY_NOT_CREATED, // Remotery object has not been created - RMT_ERROR_SEND_ON_INCOMPLETE_PROFILE, // An attempt was made to send an incomplete profile tree to the client - - // CUDA error messages - RMT_ERROR_CUDA_DEINITIALIZED, // This indicates that the CUDA driver is in the process of shutting down - RMT_ERROR_CUDA_NOT_INITIALIZED, // This indicates that the CUDA driver has not been initialized with cuInit() or that initialization has failed - RMT_ERROR_CUDA_INVALID_CONTEXT, // This most frequently indicates that there is no context bound to the current thread - RMT_ERROR_CUDA_INVALID_VALUE, // This indicates that one or more of the parameters passed to the API call is not within an acceptable range of values - RMT_ERROR_CUDA_INVALID_HANDLE, // This indicates that a resource handle passed to the API call was not valid - RMT_ERROR_CUDA_OUT_OF_MEMORY, // The API call failed because it was unable to allocate enough memory to perform the requested operation - RMT_ERROR_ERROR_NOT_READY, // This indicates that a resource handle passed to the API call was not valid - - // Direct3D 11 error messages - RMT_ERROR_D3D11_FAILED_TO_CREATE_QUERY, // Failed to create query for sample - - // OpenGL error messages - RMT_ERROR_OPENGL_ERROR, // Generic OpenGL error, no need to expose detail since app will need an OpenGL error callback registered - - RMT_ERROR_CUDA_UNKNOWN, -} rmtError; -// clang-format on - -// Gets the last error message issued on the calling thread -RMT_API rmtPStr rmt_GetLastErrorMessage(); - - -/*-------------------------------------------------------------------------------------------------------------------------------- - Runtime Settings ---------------------------------------------------------------------------------------------------------------------------------*/ - - -// Callback function pointer types -typedef void* (*rmtMallocPtr)(void* mm_context, rmtU32 size); -typedef void* (*rmtReallocPtr)(void* mm_context, void* ptr, rmtU32 size); -typedef void (*rmtFreePtr)(void* mm_context, void* ptr); -typedef void (*rmtInputHandlerPtr)(const char* text, void* context); -typedef void (*rmtSampleTreeHandlerPtr)(void* cbk_context, rmtSampleTree* sample_tree); -typedef void (*rmtPropertyHandlerPtr)(void* cbk_context, struct rmtProperty* root); - -// Struture to fill in to modify Remotery default settings -typedef struct rmtSettings -{ - // Which port to listen for incoming connections on - rmtU16 port; - - // When this server exits it can leave the port open in TIME_WAIT state for a while. This forces - // subsequent server bind attempts to fail when restarting. If you find restarts fail repeatedly - // with bind attempts, set this to true to forcibly reuse the open port. - rmtBool reuse_open_port; - - // Only allow connections on localhost? - // For dev builds you may want to access your game from other devices but if - // you distribute a game to your players with Remotery active, probably best - // to limit connections to localhost. - rmtBool limit_connections_to_localhost; - - // Whether to enable runtime thread sampling that discovers which processors a thread is running - // on. This will suspend and resume threads from outside repeatdly and inject code into each - // thread that automatically instruments the processor. - // Default: Enabled - rmtBool enableThreadSampler; - - // How long to sleep between server updates, hopefully trying to give - // a little CPU back to other threads. - rmtU32 msSleepBetweenServerUpdates; - - // Size of the internal message queues Remotery uses - // Will be rounded to page granularity of 64k - rmtU32 messageQueueSizeInBytes; - - // If the user continuously pushes to the message queue, the server network - // code won't get a chance to update unless there's an upper-limit on how - // many messages can be consumed per loop. - rmtU32 maxNbMessagesPerUpdate; - - // Callback pointers for memory allocation - rmtMallocPtr malloc; - rmtReallocPtr realloc; - rmtFreePtr free; - void* mm_context; - - // Callback pointer for receiving input from the Remotery console - rmtInputHandlerPtr input_handler; - - // Callback pointer for traversing the sample tree graph - rmtSampleTreeHandlerPtr sampletree_handler; - void* sampletree_context; - - // Callback pointer for traversing the prpperty graph - rmtPropertyHandlerPtr snapshot_callback; - void* snapshot_context; - - // Context pointer that gets sent to Remotery console callback function - void* input_handler_context; - - rmtPStr logPath; -} rmtSettings; - -// Retrieve and configure the global rmtSettings object; returns `rmtSettings*`. -// This can be done before or after Remotery is initialised, however some fields are only referenced on initialisation. -#define rmt_Settings() \ - RMT_OPTIONAL_RET(RMT_ENABLED, _rmt_Settings(), NULL ) - - -/*-------------------------------------------------------------------------------------------------------------------------------- - Initialisation/Shutdown ---------------------------------------------------------------------------------------------------------------------------------*/ - - -// Can call remotery functions on a null pointer -// TODO: Can embed extern "C" in these macros? - -// Initialises Remotery and sets its internal global instance pointer. -// Parameter is `Remotery**`, returning you the pointer for further use. -#define rmt_CreateGlobalInstance(rmt) \ - RMT_OPTIONAL_RET(RMT_ENABLED, _rmt_CreateGlobalInstance(rmt), RMT_ERROR_NONE) - -// Shutsdown Remotery, requiring its pointer to be passed to ensure you are destroying the correct instance. -#define rmt_DestroyGlobalInstance(rmt) \ - RMT_OPTIONAL(RMT_ENABLED, _rmt_DestroyGlobalInstance(rmt)) - -// For use in the presence of DLLs/SOs if each of them are linking Remotery statically. -// If Remotery is hosted in its own DLL and linked dynamically then there is no need to use this. -// Otherwise, pass the result of `rmt_CreateGlobalInstance` from your main DLL to this in your other DLLs. -#define rmt_SetGlobalInstance(rmt) \ - RMT_OPTIONAL(RMT_ENABLED, _rmt_SetGlobalInstance(rmt)) - -// Get a pointer to the current global Remotery instance. -#define rmt_GetGlobalInstance() \ - RMT_OPTIONAL_RET(RMT_ENABLED, _rmt_GetGlobalInstance(), NULL) - - -/*-------------------------------------------------------------------------------------------------------------------------------- - CPU Sampling ---------------------------------------------------------------------------------------------------------------------------------*/ - - -#define rmt_SetCurrentThreadName(rmt) \ - RMT_OPTIONAL(RMT_ENABLED, _rmt_SetCurrentThreadName(rmt)) - -#define rmt_LogText(text) \ - RMT_OPTIONAL(RMT_ENABLED, _rmt_LogText(text)) - -#define rmt_BeginCPUSample(name, flags) \ - RMT_OPTIONAL(RMT_ENABLED, { \ - static rmtU32 rmt_sample_hash_##name = 0; \ - _rmt_BeginCPUSample(#name, flags, &rmt_sample_hash_##name); \ - }) - -#define rmt_BeginCPUSampleDynamic(namestr, flags) \ - RMT_OPTIONAL(RMT_ENABLED, _rmt_BeginCPUSample(namestr, flags, NULL)) - -#define rmt_EndCPUSample() \ - RMT_OPTIONAL(RMT_ENABLED, _rmt_EndCPUSample()) - -// Used for both CPU and GPU profiling -// Essential to call this every frame, ever since D3D12 support was added -// D3D12 Requirements: Don't sample any command lists that begin before this call and end after it -#define rmt_MarkFrame() \ - RMT_OPTIONAL_RET(RMT_ENABLED, _rmt_MarkFrame(), RMT_ERROR_NONE) - - -/*-------------------------------------------------------------------------------------------------------------------------------- - GPU Sampling ---------------------------------------------------------------------------------------------------------------------------------*/ - - -// Structure to fill in when binding CUDA to Remotery -typedef struct rmtCUDABind -{ - // The main context that all driver functions apply before each call - void* context; - - // Driver API function pointers that need to be pointed to - // Untyped so that the CUDA headers are not required in this file - // NOTE: These are named differently to the CUDA functions because the CUDA API has a habit of using - // macros to point function calls to different versions, e.g. cuEventDestroy is a macro for - // cuEventDestroy_v2. - void* CtxSetCurrent; - void* CtxGetCurrent; - void* EventCreate; - void* EventDestroy; - void* EventRecord; - void* EventQuery; - void* EventElapsedTime; - -} rmtCUDABind; - -// Call once after you've initialised CUDA to bind it to Remotery -#define rmt_BindCUDA(bind) \ - RMT_OPTIONAL(RMT_USE_CUDA, _rmt_BindCUDA(bind)) - -// Mark the beginning of a CUDA sample on the specified asynchronous stream -#define rmt_BeginCUDASample(name, stream) \ - RMT_OPTIONAL(RMT_USE_CUDA, { \ - static rmtU32 rmt_sample_hash_##name = 0; \ - _rmt_BeginCUDASample(#name, &rmt_sample_hash_##name, stream); \ - }) - -// Mark the end of a CUDA sample on the specified asynchronous stream -#define rmt_EndCUDASample(stream) \ - RMT_OPTIONAL(RMT_USE_CUDA, _rmt_EndCUDASample(stream)) - - -#define rmt_BindD3D11(device, context) \ - RMT_OPTIONAL(RMT_USE_D3D11, _rmt_BindD3D11(device, context)) - -#define rmt_UnbindD3D11() \ - RMT_OPTIONAL(RMT_USE_D3D11, _rmt_UnbindD3D11()) - -#define rmt_BeginD3D11Sample(name) \ - RMT_OPTIONAL(RMT_USE_D3D11, { \ - static rmtU32 rmt_sample_hash_##name = 0; \ - _rmt_BeginD3D11Sample(#name, &rmt_sample_hash_##name); \ - }) - -#define rmt_BeginD3D11SampleDynamic(namestr) \ - RMT_OPTIONAL(RMT_USE_D3D11, _rmt_BeginD3D11Sample(namestr, NULL)) - -#define rmt_EndD3D11Sample() \ - RMT_OPTIONAL(RMT_USE_D3D11, _rmt_EndD3D11Sample()) - - -typedef struct rmtD3D12Bind -{ - // The main device shared by all threads - void* device; - - // The queue command lists are executed on for profiling - void* queue; - -} rmtD3D12Bind; - -// Create a D3D12 binding for the given device/queue pair -#define rmt_BindD3D12(device, queue, out_bind) \ - RMT_OPTIONAL_RET(RMT_USE_D3D12, _rmt_BindD3D12(device, queue, out_bind), NULL) - -#define rmt_UnbindD3D12(bind) \ - RMT_OPTIONAL(RMT_USE_D3D12, _rmt_UnbindD3D12(bind)) - -#define rmt_BeginD3D12Sample(bind, command_list, name) \ - RMT_OPTIONAL(RMT_USE_D3D12, { \ - static rmtU32 rmt_sample_hash_##name = 0; \ - _rmt_BeginD3D12Sample(bind, command_list, #name, &rmt_sample_hash_##name); \ - }) - -#define rmt_BeginD3D12SampleDynamic(bind, command_list, namestr) \ - RMT_OPTIONAL(RMT_USE_D3D12, _rmt_BeginD3D12Sample(bind, command_list, namestr, NULL)) - -#define rmt_EndD3D12Sample() \ - RMT_OPTIONAL(RMT_USE_D3D12, _rmt_EndD3D12Sample()) - - -#define rmt_BindOpenGL() \ - RMT_OPTIONAL(RMT_USE_OPENGL, _rmt_BindOpenGL()) - -#define rmt_UnbindOpenGL() \ - RMT_OPTIONAL(RMT_USE_OPENGL, _rmt_UnbindOpenGL()) - -#define rmt_BeginOpenGLSample(name) \ - RMT_OPTIONAL(RMT_USE_OPENGL, { \ - static rmtU32 rmt_sample_hash_##name = 0; \ - _rmt_BeginOpenGLSample(#name, &rmt_sample_hash_##name); \ - }) - -#define rmt_BeginOpenGLSampleDynamic(namestr) \ - RMT_OPTIONAL(RMT_USE_OPENGL, _rmt_BeginOpenGLSample(namestr, NULL)) - -#define rmt_EndOpenGLSample() \ - RMT_OPTIONAL(RMT_USE_OPENGL, _rmt_EndOpenGLSample()) - - -#define rmt_BindMetal(command_buffer) \ - RMT_OPTIONAL(RMT_USE_METAL, _rmt_BindMetal(command_buffer)); - -#define rmt_UnbindMetal() \ - RMT_OPTIONAL(RMT_USE_METAL, _rmt_UnbindMetal()); - -#define rmt_BeginMetalSample(name) \ - RMT_OPTIONAL(RMT_USE_METAL, { \ - static rmtU32 rmt_sample_hash_##name = 0; \ - _rmt_BeginMetalSample(#name, &rmt_sample_hash_##name); \ - }) - -#define rmt_BeginMetalSampleDynamic(namestr) \ - RMT_OPTIONAL(RMT_USE_METAL, _rmt_BeginMetalSample(namestr, NULL)) - -#define rmt_EndMetalSample() \ - RMT_OPTIONAL(RMT_USE_METAL, _rmt_EndMetalSample()) - - -/*-------------------------------------------------------------------------------------------------------------------------------- - Runtime Properties ---------------------------------------------------------------------------------------------------------------------------------*/ - - -/* --- Public API --------------------------------------------------------------------------------------------------------------*/ - - -// Flags that control property behaviour -typedef enum -{ - RMT_PropertyFlags_NoFlags = 0, - - // Reset property back to its default value on each new frame - RMT_PropertyFlags_FrameReset = 1, -} rmtPropertyFlags; - -// All possible property types that can be recorded and sent to the viewer -typedef enum -{ - RMT_PropertyType_rmtGroup, - RMT_PropertyType_rmtBool, - RMT_PropertyType_rmtS32, - RMT_PropertyType_rmtU32, - RMT_PropertyType_rmtF32, - RMT_PropertyType_rmtS64, - RMT_PropertyType_rmtU64, - RMT_PropertyType_rmtF64, -} rmtPropertyType; - -// A property value as a union of all its possible types -typedef union rmtPropertyValue -{ - // C++ requires function-based construction of property values because it has no designated initialiser support until C++20 - #ifdef __cplusplus - // These are static Make calls, rather than overloaded constructors, because `rmtBool` is the same type as `rmtU32` - static rmtPropertyValue MakeBool(rmtBool v) { rmtPropertyValue pv; pv.Bool = v; return pv; } - static rmtPropertyValue MakeS32(rmtS32 v) { rmtPropertyValue pv; pv.S32 = v; return pv; } - static rmtPropertyValue MakeU32(rmtU32 v) { rmtPropertyValue pv; pv.U32 = v; return pv; } - static rmtPropertyValue MakeF32(rmtF32 v) { rmtPropertyValue pv; pv.F32 = v; return pv; } - static rmtPropertyValue MakeS64(rmtS64 v) { rmtPropertyValue pv; pv.S64 = v; return pv; } - static rmtPropertyValue MakeU64(rmtU64 v) { rmtPropertyValue pv; pv.U64 = v; return pv; } - static rmtPropertyValue MakeF64(rmtF64 v) { rmtPropertyValue pv; pv.F64 = v; return pv; } - #endif - - rmtBool Bool; - rmtS32 S32; - rmtU32 U32; - rmtF32 F32; - rmtS64 S64; - rmtU64 U64; - rmtF64 F64; -} rmtPropertyValue; - -// Definition of a property that should be stored globally -// Note: -// Use the callback api and the rmt_PropertyGetxxx accessors to traverse this structure -typedef struct rmtProperty -{ - // Gets set to RMT_TRUE after a property has been modified, when it gets initialised for the first time - rmtBool initialised; - - // Runtime description - rmtPropertyType type; - rmtPropertyFlags flags; - - // Current value - rmtPropertyValue value; - - // Last frame value to see if previous value needs to be updated - rmtPropertyValue lastFrameValue; - - // Previous value only if it's different from the current value, and when it changed - rmtPropertyValue prevValue; - rmtU32 prevValueFrame; - - // Text description - const char* name; - const char* description; - - // Default value for Reset calls - rmtPropertyValue defaultValue; - - // Parent link specifically placed after default value so that variadic macro can initialise it - struct rmtProperty* parent; - - // Links within the property tree - struct rmtProperty* firstChild; - struct rmtProperty* lastChild; - struct rmtProperty* nextSibling; - - // Hash for efficient sending of properties to the viewer - rmtU32 nameHash; - - // Unique, persistent ID among all properties - rmtU32 uniqueID; -} rmtProperty; - -// Define properties of different types at global scope: -// -// * Never define properties in a header file that gets included multiple times. -// * The property gets defined exactly as `name` in the global scope. -// * `flag` is specified without the `RMT_PropertyFlags_` prefix. -// * Property parents are optional and can be specified as the last parameter, referencing `&name`. -// -#define rmt_PropertyDefine_Group(name, desc, ...) _rmt_PropertyDefine(rmtGroup, name, _rmt_MakePropertyValue(Bool, 0), RMT_PropertyFlags_NoFlags, desc, __VA_ARGS__) -#define rmt_PropertyDefine_Bool(name, default_value, flag, desc, ...) _rmt_PropertyDefine(rmtBool, name, _rmt_MakePropertyValue(Bool, default_value), RMT_PropertyFlags_##flag, desc, __VA_ARGS__) -#define rmt_PropertyDefine_S32(name, default_value, flag, desc, ...) _rmt_PropertyDefine(rmtS32, name, _rmt_MakePropertyValue(S32, default_value), RMT_PropertyFlags_##flag, desc, __VA_ARGS__) -#define rmt_PropertyDefine_U32(name, default_value, flag, desc, ...) _rmt_PropertyDefine(rmtU32, name, _rmt_MakePropertyValue(U32, default_value), RMT_PropertyFlags_##flag, desc, __VA_ARGS__) -#define rmt_PropertyDefine_F32(name, default_value, flag, desc, ...) _rmt_PropertyDefine(rmtF32, name, _rmt_MakePropertyValue(F32, default_value), RMT_PropertyFlags_##flag, desc, __VA_ARGS__) -#define rmt_PropertyDefine_S64(name, default_value, flag, desc, ...) _rmt_PropertyDefine(rmtS64, name, _rmt_MakePropertyValue(S64, default_value), RMT_PropertyFlags_##flag, desc, __VA_ARGS__) -#define rmt_PropertyDefine_U64(name, default_value, flag, desc, ...) _rmt_PropertyDefine(rmtU64, name, _rmt_MakePropertyValue(U64, default_value), RMT_PropertyFlags_##flag, desc, __VA_ARGS__) -#define rmt_PropertyDefine_F64(name, default_value, flag, desc, ...) _rmt_PropertyDefine(rmtF64, name, _rmt_MakePropertyValue(F64, default_value), RMT_PropertyFlags_##flag, desc, __VA_ARGS__) - -// As properties need to be defined at global scope outside header files, use this to declare properties in header files to be -// modified in other translation units. -// -// If you don't want to include Remotery.h in your shared header you can forward declare the `rmtProperty` type and then forward -// declare the property name yourself. -#define rmt_PropertyExtern(name) extern rmtProperty name; - -// Set properties to the given value -#define rmt_PropertySet_Bool(name, set_value) _rmt_PropertySet(Bool, name, set_value) -#define rmt_PropertySet_S32(name, set_value) _rmt_PropertySet(S32, name, set_value) -#define rmt_PropertySet_U32(name, set_value) _rmt_PropertySet(U32, name, set_value) -#define rmt_PropertySet_F32(name, set_value) _rmt_PropertySet(F32, name, set_value) -#define rmt_PropertySet_S64(name, set_value) _rmt_PropertySet(S64, name, set_value) -#define rmt_PropertySet_U64(name, set_value) _rmt_PropertySet(U64, name, set_value) -#define rmt_PropertySet_F64(name, set_value) _rmt_PropertySet(F64, name, set_value) - -// Add the given value to properties -#define rmt_PropertyAdd_S32(name, add_value) _rmt_PropertyAdd(S32, name, add_value) -#define rmt_PropertyAdd_U32(name, add_value) _rmt_PropertyAdd(U32, name, add_value) -#define rmt_PropertyAdd_F32(name, add_value) _rmt_PropertyAdd(F32, name, add_value) -#define rmt_PropertyAdd_S64(name, add_value) _rmt_PropertyAdd(S64, name, add_value) -#define rmt_PropertyAdd_U64(name, add_value) _rmt_PropertyAdd(U64, name, add_value) -#define rmt_PropertyAdd_F64(name, add_value) _rmt_PropertyAdd(F64, name, add_value) - -// Reset properties to their default value -#define rmt_PropertyReset(name) \ - { \ - name.value = name.defaultValue; \ - _rmt_PropertySetValue(&name); \ - } - -// Send all properties and their values to the viewer and log to file -#define rmt_PropertySnapshotAll() _rmt_PropertySnapshotAll() - -// Reset all RMT_PropertyFlags_FrameReset properties to their default value -#define rmt_PropertyFrameResetAll() _rmt_PropertyFrameResetAll() - -/* --- Private Details ---------------------------------------------------------------------------------------------------------*/ - - -// Used to define properties from typed macro callers -#define _rmt_PropertyDefine(type, name, default_value, flags, desc, ...) \ - rmtProperty name = { RMT_FALSE, RMT_PropertyType_##type, flags, default_value, default_value, default_value, 0, #name, desc, default_value, __VA_ARGS__ }; - -// C++ doesn't support designated initialisers until C++20 -// Worth checking for C++ designated initialisers to remove the function call in debug builds -#ifdef __cplusplus -#define _rmt_MakePropertyValue(field, value) rmtPropertyValue::Make##field(value) -#else -#define _rmt_MakePropertyValue(field, value) { .field = value } -#endif - -// Used to set properties from typed macro callers -#define _rmt_PropertySet(field, name, set_value) \ - { \ - name.value.field = set_value; \ - _rmt_PropertySetValue(&name); \ - } - -// Used to add properties from typed macro callers -#define _rmt_PropertyAdd(field, name, add_value) \ - { \ - name.value.field += add_value; \ - rmtPropertyValue delta_value = _rmt_MakePropertyValue(field, add_value); \ - _rmt_PropertyAddValue(&name, delta_value); \ - } - - -#ifdef __cplusplus -extern "C" { -#endif - -RMT_API void _rmt_PropertySetValue(rmtProperty* property); -RMT_API void _rmt_PropertyAddValue(rmtProperty* property, rmtPropertyValue add_value); -RMT_API rmtError _rmt_PropertySnapshotAll(); -RMT_API void _rmt_PropertyFrameResetAll(); -RMT_API rmtU32 _rmt_HashString32(const char* s, int len, rmtU32 seed); - -#ifdef __cplusplus -} -#endif - - -/*-------------------------------------------------------------------------------------------------------------------------------- - Sample Tree API for walking `rmtSampleTree` Objects in the Sample Tree Handler. ---------------------------------------------------------------------------------------------------------------------------------*/ - - -typedef enum rmtSampleFlags -{ - // Default behaviour - RMTSF_None = 0, - - // Search parent for same-named samples and merge timing instead of adding a new sample - RMTSF_Aggregate = 1, - - // Merge sample with parent if it's the same sample - RMTSF_Recursive = 2, - - // Set this flag on any of your root samples so that Remotery will assert if it ends up *not* being the root sample. - // This will quickly allow you to detect Begin/End mismatches causing a sample tree imbalance. - RMTSF_Root = 4, - - // Mainly for platforms other than Windows that don't support the thread sampler and can't detect stalling samples. - // Where you have a non-root sample that stays open indefinitely and never sends its contents to log/viewer. - // Send this sample to log/viewer when it closes. - // You can not have more than one sample open with this flag on the same thread at a time. - // This flag will be removed in a future version when all platforms support stalling samples. - RMTSF_SendOnClose = 8, -} rmtSampleFlags; - -// Struct to hold iterator info -typedef struct rmtSampleIterator -{ -// public - rmtSample* sample; -// private - rmtSample* initial; -} rmtSampleIterator; - -#define rmt_IterateChildren(iter, sample) \ - RMT_OPTIONAL(RMT_ENABLED, _rmt_IterateChildren(iter, sample)) - -#define rmt_IterateNext(iter) \ - RMT_OPTIONAL_RET(RMT_ENABLED, _rmt_IterateNext(iter), RMT_FALSE) - -#define rmt_SampleTreeGetThreadName(sample_tree) \ - RMT_OPTIONAL_RET(RMT_ENABLED, _rmt_SampleTreeGetThreadName(sample_tree), NULL) - -#define rmt_SampleTreeGetRootSample(sample_tree) \ - RMT_OPTIONAL_RET(RMT_ENABLED, _rmt_SampleTreeGetRootSample(sample_tree), NULL) - -// Should only called from within the sample tree callback, -// when the internal string lookup table is valid (i.e. on the main Remotery thread) -#define rmt_SampleGetName(sample) \ - RMT_OPTIONAL_RET(RMT_ENABLED, _rmt_SampleGetName(sample), NULL) - -#define rmt_SampleGetNameHash(sample) \ - RMT_OPTIONAL_RET(RMT_ENABLED, _rmt_SampleGetNameHash(sample), 0U) - -#define rmt_SampleGetCallCount(sample) \ - RMT_OPTIONAL_RET(RMT_ENABLED, _rmt_SampleGetCallCount(sample), 0U) - -#define rmt_SampleGetStart(sample) \ - RMT_OPTIONAL_RET(RMT_ENABLED, _rmt_SampleGetStart(sample), 0LLU) - -#define rmt_SampleGetTime(sample) \ - RMT_OPTIONAL_RET(RMT_ENABLED, _rmt_SampleGetTime(sample), 0LLU) - -#define rmt_SampleGetSelfTime(sample) \ - RMT_OPTIONAL_RET(RMT_ENABLED, _rmt_SampleGetSelfTime(sample), 0LLU) - -#define rmt_SampleGetColour(sample, r, g, b) \ - RMT_OPTIONAL(RMT_ENABLED, _rmt_SampleGetColour(sample, r, g, b)) - -#define rmt_SampleGetType(sample) \ - RMT_OPTIONAL_RET(RMT_ENABLED, _rmt_SampleGetType(sample), RMT_SampleType_Count) - - -// Struct to hold iterator info -typedef struct rmtPropertyIterator -{ -// public - rmtProperty* property; -// private - rmtProperty* initial; -} rmtPropertyIterator; - -#define rmt_PropertyIterateChildren(iter, property) \ - RMT_OPTIONAL(RMT_ENABLED, _rmt_PropertyIterateChildren(iter, property)) - -#define rmt_PropertyIterateNext(iter) \ - RMT_OPTIONAL_RET(RMT_ENABLED, _rmt_PropertyIterateNext(iter), RMT_FALSE) - -// Should only called from within the property callback, -// when the internal string lookup table is valid (i.e. on the main Remotery thread) - -#define rmt_PropertyGetType(property) \ - RMT_OPTIONAL_RET(RMT_ENABLED, _rmt_PropertyGetType(property), RMT_PropertyType_Count) - -#define rmt_PropertyGetName(property) \ - RMT_OPTIONAL_RET(RMT_ENABLED, _rmt_PropertyGetName(property), NULL) - -#define rmt_PropertyGetDescription(property) \ - RMT_OPTIONAL_RET(RMT_ENABLED, _rmt_PropertyGetDescription(property), 0U) - -#define rmt_PropertyGetValue(property) \ - RMT_OPTIONAL_RET(RMT_ENABLED, _rmt_PropertyGetValue(property), 0U) - - - -/*-------------------------------------------------------------------------------------------------------------------------------- - C++ Public Interface Extensions ---------------------------------------------------------------------------------------------------------------------------------*/ - - -#ifdef __cplusplus - - -#if RMT_ENABLED - -// Types that end samples in their destructors -extern "C" RMT_API void _rmt_EndCPUSample(void); -struct rmt_EndCPUSampleOnScopeExit -{ - ~rmt_EndCPUSampleOnScopeExit() - { - _rmt_EndCPUSample(); - } -}; - -#if RMT_USE_CUDA -extern "C" RMT_API void _rmt_EndCUDASample(void* stream); -struct rmt_EndCUDASampleOnScopeExit -{ - rmt_EndCUDASampleOnScopeExit(void* stream) : stream(stream) - { - } - ~rmt_EndCUDASampleOnScopeExit() - { - _rmt_EndCUDASample(stream); - } - void* stream; -}; - -#endif -#if RMT_USE_D3D11 -extern "C" RMT_API void _rmt_EndD3D11Sample(void); -struct rmt_EndD3D11SampleOnScopeExit -{ - ~rmt_EndD3D11SampleOnScopeExit() - { - _rmt_EndD3D11Sample(); - } -}; -#endif - -#if RMT_USE_D3D12 -extern "C" RMT_API void _rmt_EndD3D12Sample(); -struct rmt_EndD3D12SampleOnScopeExit -{ - ~rmt_EndD3D12SampleOnScopeExit() - { - _rmt_EndD3D12Sample(); - } -}; -#endif - -#if RMT_USE_OPENGL -extern "C" RMT_API void _rmt_EndOpenGLSample(void); -struct rmt_EndOpenGLSampleOnScopeExit -{ - ~rmt_EndOpenGLSampleOnScopeExit() - { - _rmt_EndOpenGLSample(); - } -}; -#endif - -#if RMT_USE_METAL -extern "C" RMT_API void _rmt_EndMetalSample(void); -struct rmt_EndMetalSampleOnScopeExit -{ - ~rmt_EndMetalSampleOnScopeExit() - { - _rmt_EndMetalSample(); - } -}; -#endif - -#endif - - -// Pairs a call to rmt_BeginSample with its call to rmt_EndSample when leaving scope -#define rmt_ScopedCPUSample(name, flags) \ - RMT_OPTIONAL(RMT_ENABLED, rmt_BeginCPUSample(name, flags)); \ - RMT_OPTIONAL(RMT_ENABLED, rmt_EndCPUSampleOnScopeExit rmt_ScopedCPUSample##name); -#define rmt_ScopedCUDASample(name, stream) \ - RMT_OPTIONAL(RMT_USE_CUDA, rmt_BeginCUDASample(name, stream)); \ - RMT_OPTIONAL(RMT_USE_CUDA, rmt_EndCUDASampleOnScopeExit rmt_ScopedCUDASample##name(stream)); -#define rmt_ScopedD3D11Sample(name) \ - RMT_OPTIONAL(RMT_USE_D3D11, rmt_BeginD3D11Sample(name)); \ - RMT_OPTIONAL(RMT_USE_D3D11, rmt_EndD3D11SampleOnScopeExit rmt_ScopedD3D11Sample##name); -#define rmt_ScopedD3D12Sample(bind, command_list, name) \ - RMT_OPTIONAL(RMT_USE_D3D12, rmt_BeginD3D12Sample(bind, command_list, name)); \ - RMT_OPTIONAL(RMT_USE_D3D12, rmt_EndD3D12SampleOnScopeExit rmt_ScopedD3D12Sample##name()); -#define rmt_ScopedOpenGLSample(name) \ - RMT_OPTIONAL(RMT_USE_OPENGL, rmt_BeginOpenGLSample(name)); \ - RMT_OPTIONAL(RMT_USE_OPENGL, rmt_EndOpenGLSampleOnScopeExit rmt_ScopedOpenGLSample##name); -#define rmt_ScopedMetalSample(name) \ - RMT_OPTIONAL(RMT_USE_METAL, rmt_BeginMetalSample(name)); \ - RMT_OPTIONAL(RMT_USE_METAL, rmt_EndMetalSampleOnScopeExit rmt_ScopedMetalSample##name); - -#endif - - -/*-------------------------------------------------------------------------------------------------------------------------------- - Private Interface - don't directly call these ---------------------------------------------------------------------------------------------------------------------------------*/ - - -#if RMT_ENABLED - -#ifdef __cplusplus -extern "C" { -#endif - -RMT_API rmtSettings* _rmt_Settings( void ); -RMT_API enum rmtError _rmt_CreateGlobalInstance(Remotery** remotery); -RMT_API void _rmt_DestroyGlobalInstance(Remotery* remotery); -RMT_API void _rmt_SetGlobalInstance(Remotery* remotery); -RMT_API Remotery* _rmt_GetGlobalInstance(void); -RMT_API void _rmt_SetCurrentThreadName(rmtPStr thread_name); -RMT_API void _rmt_LogText(rmtPStr text); -RMT_API void _rmt_BeginCPUSample(rmtPStr name, rmtU32 flags, rmtU32* hash_cache); -RMT_API void _rmt_EndCPUSample(void); -RMT_API rmtError _rmt_MarkFrame(void); - -#if RMT_USE_CUDA -RMT_API void _rmt_BindCUDA(const rmtCUDABind* bind); -RMT_API void _rmt_BeginCUDASample(rmtPStr name, rmtU32* hash_cache, void* stream); -RMT_API void _rmt_EndCUDASample(void* stream); -#endif - -#if RMT_USE_D3D11 -RMT_API void _rmt_BindD3D11(void* device, void* context); -RMT_API void _rmt_UnbindD3D11(void); -RMT_API void _rmt_BeginD3D11Sample(rmtPStr name, rmtU32* hash_cache); -RMT_API void _rmt_EndD3D11Sample(void); -#endif - -#if RMT_USE_D3D12 -RMT_API rmtError _rmt_BindD3D12(void* device, void* queue, rmtD3D12Bind** out_bind); -RMT_API void _rmt_UnbindD3D12(rmtD3D12Bind* bind); -RMT_API void _rmt_BeginD3D12Sample(rmtD3D12Bind* bind, void* command_list, rmtPStr name, rmtU32* hash_cache); -RMT_API void _rmt_EndD3D12Sample(); -#endif - -#if RMT_USE_OPENGL -RMT_API void _rmt_BindOpenGL(); -RMT_API void _rmt_UnbindOpenGL(void); -RMT_API void _rmt_BeginOpenGLSample(rmtPStr name, rmtU32* hash_cache); -RMT_API void _rmt_EndOpenGLSample(void); -#endif - -#if RMT_USE_METAL -RMT_API void _rmt_BeginMetalSample(rmtPStr name, rmtU32* hash_cache); -RMT_API void _rmt_EndMetalSample(void); -#endif - -// Sample iterator -RMT_API void _rmt_IterateChildren(rmtSampleIterator* iter, rmtSample* sample); -RMT_API rmtBool _rmt_IterateNext(rmtSampleIterator* iter); - -// SampleTree accessors -RMT_API const char* _rmt_SampleTreeGetThreadName(rmtSampleTree* sample_tree); -RMT_API rmtSample* _rmt_SampleTreeGetRootSample(rmtSampleTree* sample_tree); - -// Sample accessors -RMT_API const char* _rmt_SampleGetName(rmtSample* sample); -RMT_API rmtU32 _rmt_SampleGetNameHash(rmtSample* sample); -RMT_API rmtU32 _rmt_SampleGetCallCount(rmtSample* sample); -RMT_API rmtU64 _rmt_SampleGetStart(rmtSample* sample); -RMT_API rmtU64 _rmt_SampleGetTime(rmtSample* sample); -RMT_API rmtU64 _rmt_SampleGetSelfTime(rmtSample* sample); -RMT_API void _rmt_SampleGetColour(rmtSample* sample, rmtU8* r, rmtU8* g, rmtU8* b); -RMT_API rmtSampleType _rmt_SampleGetType(rmtSample* sample); - -// Property iterator -RMT_API void _rmt_PropertyIterateChildren(rmtPropertyIterator* iter, rmtProperty* property); -RMT_API rmtBool _rmt_PropertyIterateNext(rmtPropertyIterator* iter); - -// Property accessors -RMT_API rmtPropertyType _rmt_PropertyGetType(rmtProperty* property); -RMT_API rmtU32 _rmt_PropertyGetNameHash(rmtProperty* property); -RMT_API const char* _rmt_PropertyGetName(rmtProperty* property); -RMT_API const char* _rmt_PropertyGetDescription(rmtProperty* property); -RMT_API rmtPropertyValue _rmt_PropertyGetValue(rmtProperty* property); - -#ifdef __cplusplus - -} -#endif - -#if RMT_USE_METAL -#ifdef __OBJC__ -RMT_API void _rmt_BindMetal(id command_buffer); -RMT_API void _rmt_UnbindMetal(); -#endif -#endif - -#endif // RMT_ENABLED - - -#endif diff --git a/profiler/lib/RemoteryMetal.mm b/profiler/lib/RemoteryMetal.mm deleted file mode 100644 index 437890b..0000000 --- a/profiler/lib/RemoteryMetal.mm +++ /dev/null @@ -1,59 +0,0 @@ -// -// Copyright 2014-2018 Celtoys Ltd -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -#include -#include -#include - -#import - -// Store command buffer in thread-local so that each thread can point to its own -static void SetCommandBuffer(id command_buffer) -{ - NSMutableDictionary* thread_data = [[NSThread currentThread] threadDictionary]; - thread_data[@"rmtMTLCommandBuffer"] = command_buffer; -} - -static id GetCommandBuffer() -{ - NSMutableDictionary* thread_data = [[NSThread currentThread] threadDictionary]; - return thread_data[@"rmtMTLCommandBuffer"]; -} - -extern "C" void _rmt_BindMetal(id command_buffer) -{ - SetCommandBuffer(command_buffer); -} - -extern "C" void _rmt_UnbindMetal() -{ - SetCommandBuffer(0); -} - -// Needs to be in the same lib for this to work -extern "C" unsigned long long rmtMetal_usGetTime(); - -static void SetTimestamp(void* data) -{ - *((unsigned long long*)data) = rmtMetal_usGetTime(); -} - -extern "C" void rmtMetal_MeasureCommandBuffer(unsigned long long* out_start, unsigned long long* out_end, unsigned int* out_ready) -{ - id command_buffer = GetCommandBuffer(); - [command_buffer addScheduledHandler:^(id ){ SetTimestamp(out_start); }]; - [command_buffer addCompletedHandler:^(id ){ SetTimestamp(out_end); *out_ready = 1; }]; -} diff --git a/profiler/readme.md b/profiler/readme.md deleted file mode 100644 index 35dbc9d..0000000 --- a/profiler/readme.md +++ /dev/null @@ -1,232 +0,0 @@ -Remotery --------- - -[![Build](https://github.com/Celtoys/Remotery/actions/workflows/build.yml/badge.svg?branch=main)](https://github.com/Celtoys/Remotery/actions/workflows/build.yml) - -A realtime CPU/GPU profiler hosted in a single C file with a viewer that runs in a web browser. - -![screenshot](screenshot.png?raw=true) - -Features: - -* Lightweight instrumentation of multiple threads running on the CPU. -* Web viewer that runs in Chrome, Firefox and Safari; on Desktops, Mobiles or Tablets. -* GPU UI rendering, bypassing the DOM completely, for real-time 60hz viewer updates at 10,000x the performance. -* Automatic thread sampler that tells you what processor cores your threads are running on without requiring Administrator privileges. -* Drop saved traces onto the Remotery window to load historical runs for inspection. -* Console output for logging text. -* Console input for sending commands to your game. -* A Property API for recording named/typed values over time, alongside samples. -* Profiles itself and shows how it's performing in the viewer. - -Supported Profiling Platforms: - -* Windows 7/8/10/11/UWP (Hololens), Linux, OSX, iOS, Android, Xbox One/Series, Free BSD. - -Supported GPU Profiling APIS: - -* D3D 11/12, OpenGL, CUDA, Metal. - -Compiling ---------- - -* Windows (MSVC) - add lib/Remotery.c and lib/Remotery.h to your program. Set include - directories to add Remotery/lib path. The required library ws2_32.lib should be picked - up through the use of the #pragma comment(lib, "ws2_32.lib") directive in Remotery.c. - -* Mac OS X (XCode) - simply add lib/Remotery.c, lib/Remotery.h and lib/Remotery.mm to your program. - -* Linux (GCC) - add the source in lib folder. Compilation of the code requires -pthreads for - library linkage. For example to compile the same run: cc lib/Remotery.c sample/sample.c - -I lib -pthread -lm - -* FreeBSD - the easiest way is to take a look at the official port - ([devel/remotery](https://www.freshports.org/devel/remotery/)) and modify the port's - Makefile if needed. There is also a package available via `pkg install remotery`. - -You can define some extra macros to modify what features are compiled into Remotery: - - Macro Default Description - - RMT_ENABLED 1 Disable this to not include any bits of Remotery in your build - RMT_USE_TINYCRT 0 Used by the Celtoys TinyCRT library (not released yet) - RMT_USE_CUDA 0 Assuming CUDA headers/libs are setup, allow CUDA profiling - RMT_USE_D3D11 0 Assuming Direct3D 11 headers/libs are setup, allow D3D11 GPU profiling - RMT_USE_D3D12 0 Allow D3D12 GPU profiling - RMT_USE_OPENGL 0 Allow OpenGL GPU profiling (dynamically links OpenGL libraries on available platforms) - RMT_USE_METAL 0 Allow Metal profiling of command buffers - - -Basic Use ---------- - -See the sample directory for further examples. A quick example: - - int main() - { - // Create the main instance of Remotery. - // You need only do this once per program. - Remotery* rmt; - rmt_CreateGlobalInstance(&rmt); - - // Explicit begin/end for C - { - rmt_BeginCPUSample(LogText, 0); - rmt_LogText("Time me, please!"); - rmt_EndCPUSample(); - } - - // Scoped begin/end for C++ - { - rmt_ScopedCPUSample(LogText, 0); - rmt_LogText("Time me, too!"); - } - - // Destroy the main instance of Remotery. - rmt_DestroyGlobalInstance(rmt); - } - - -Running the Viewer ------------------- - -Double-click or launch `vis/index.html` from the browser. - - -Sampling CUDA GPU activity --------------------------- - -Remotery allows for profiling multiple threads of CUDA execution using different asynchronous streams -that must all share the same context. After initialising both Remotery and CUDA you need to bind the -two together using the call: - - rmtCUDABind bind; - bind.context = m_Context; - bind.CtxSetCurrent = &cuCtxSetCurrent; - bind.CtxGetCurrent = &cuCtxGetCurrent; - bind.EventCreate = &cuEventCreate; - bind.EventDestroy = &cuEventDestroy; - bind.EventRecord = &cuEventRecord; - bind.EventQuery = &cuEventQuery; - bind.EventElapsedTime = &cuEventElapsedTime; - rmt_BindCUDA(&bind); - -Explicitly pointing to the CUDA interface allows Remotery to be included anywhere in your project without -need for you to link with the required CUDA libraries. After the bind completes you can safely sample any -CUDA activity: - - CUstream stream; - - // Explicit begin/end for C - { - rmt_BeginCUDASample(UnscopedSample, stream); - // ... CUDA code ... - rmt_EndCUDASample(stream); - } - - // Scoped begin/end for C++ - { - rmt_ScopedCUDASample(ScopedSample, stream); - // ... CUDA code ... - } - -Remotery supports only one context for all threads and will use cuCtxGetCurrent and cuCtxSetCurrent to -ensure the current thread has the context you specify in rmtCUDABind.context. - - -Sampling Direct3D 11 GPU activity ---------------------------------- - -Remotery allows sampling of D3D11 GPU activity on multiple devices on multiple threads. After initialising Remotery, you need to bind it to D3D11 with a single call from the thread that owns the device context: - - // Parameters are ID3D11Device* and ID3D11DeviceContext* - rmt_BindD3D11(d3d11_device, d3d11_context); - -Sampling is then a simple case of: - - // Explicit begin/end for C - { - rmt_BeginD3D11Sample(UnscopedSample); - // ... D3D code ... - rmt_EndD3D11Sample(); - } - - // Scoped begin/end for C++ - { - rmt_ScopedD3D11Sample(ScopedSample); - // ... D3D code ... - } - -Subsequent sampling calls from the same thread will use that device/context combination. When you shutdown your D3D11 device and context, ensure you notify Remotery before shutting down Remotery itself: - - rmt_UnbindD3D11(); - - -Sampling OpenGL GPU activity ----------------------------- - -Remotery allows sampling of GPU activity on your main OpenGL context. After initialising Remotery, you need -to bind it to OpenGL with the single call: - - rmt_BindOpenGL(); - -Sampling is then a simple case of: - - // Explicit begin/end for C - { - rmt_BeginOpenGLSample(UnscopedSample); - // ... OpenGL code ... - rmt_EndOpenGLSample(); - } - - // Scoped begin/end for C++ - { - rmt_ScopedOpenGLSample(ScopedSample); - // ... OpenGL code ... - } - -Support for multiple contexts can be added pretty easily if there is demand for the feature. When you shutdown -your OpenGL device and context, ensure you notify Remotery before shutting down Remotery itself: - - rmt_UnbindOpenGL(); - - -Sampling Metal GPU activity ---------------------------- - -Remotery can sample Metal command buffers issued to the GPU from multiple threads. As the Metal API does not -support finer grained profiling, samples will return only the timing of the bound command buffer, irrespective -of how many you issue. As such, make sure you bind and sample the command buffer for each call site: - - rmt_BindMetal(mtl_command_buffer); - rmt_ScopedMetalSample(command_buffer_name); - -The C API supports begin/end also: - - rmt_BindMetal(mtl_command_buffer); - rmt_BeginMetalSample(command_buffer_name); - ... - rmt_EndMetalSample(); - - -Applying Configuration Settings -------------------------------- - -Before creating your Remotery instance, you can configure its behaviour by retrieving its settings object: - - rmtSettings* settings = rmt_Settings(); - -Some important settings are: - - // Redirect any Remotery allocations to your own malloc/free, with an additional context pointer - // that gets passed to your callbacks. - settings->malloc; - settings->free; - settings->mm_context; - - // Specify an input pipelineStage that receives text input from the Remotery console, with an additional - // context pointer that gets passed to your callback. - // The pipelineStage will be called from the Remotery thread so synchronization with a mutex or atomics - // might be needed to avoid race conditions with your threads. - settings->input_handler; - settings->input_handler_context; diff --git a/profiler/sample/dump.c b/profiler/sample/dump.c deleted file mode 100644 index 3681b5c..0000000 --- a/profiler/sample/dump.c +++ /dev/null @@ -1,184 +0,0 @@ -#include -#include -#include -#include -#include -#include "../lib/Remotery.h" - -#include - -rmt_PropertyDefine_Group(Game, "Game Properties"); -rmt_PropertyDefine_Bool(WasUpdated, RMT_FALSE, FrameReset, "Was the game loop executed this frame?", &Game); -rmt_PropertyDefine_U32(RecursiveDepth, 0, FrameReset, "How deep did we go in recursiveFunction?", &Game); -rmt_PropertyDefine_F32(Accumulated, 0, FrameReset, "What was the latest value?", &Game); -rmt_PropertyDefine_U32(FrameCounter, 0, NoFlags, "What is the current frame number?", &Game); - - -void aggregateFunction() { - rmt_BeginCPUSample(aggregate, RMTSF_Aggregate); - rmt_EndCPUSample(); -} -void recursiveFunction(int depth) { - rmt_PropertySet_U32(RecursiveDepth, depth); - rmt_BeginCPUSample(recursive, RMTSF_Recursive); - if (depth < 5) { - recursiveFunction(depth + 1); - } - rmt_EndCPUSample(); -} - -double delay() { - int i, end; - double j = 0; - - rmt_BeginCPUSample(delay, 0); - for( i = 0, end = rand()/100; i < end; ++i ) { - double v = sin(i); - j += v; - - rmt_PropertyAdd_F32(Accumulated, v); - } - recursiveFunction(0); - aggregateFunction(); - aggregateFunction(); - aggregateFunction(); - rmt_EndCPUSample(); - return j; -} - -void printIndent(int indent) -{ - int i; - for (i = 0; i < indent; ++i) { - printf(" "); - } -} - -void printSample(rmtSample* sample, int indent) -{ - const char* name = rmt_SampleGetName(sample); - rmtU32 callcount = rmt_SampleGetCallCount(sample); - rmtU64 time = rmt_SampleGetTime(sample); - rmtU64 self_time = rmt_SampleGetSelfTime(sample); - rmtSampleType type = rmt_SampleGetType(sample); - rmtU8 r, g, b; - rmt_SampleGetColour(sample, &r, &g, &b); - - printIndent(indent); printf("%s %u time: %llu self: %llu type: %d color: 0x%02x%02x%02x\n", name, callcount, time, self_time, type, r, g, b); -} - -void printTree(rmtSample* sample, int indent) -{ - rmtSampleIterator iter; - - printSample(sample, indent); - - rmt_IterateChildren(&iter, sample); - while (rmt_IterateNext(&iter)) { - printTree(iter.sample, indent+1); - } -} - -void dumpTree(void* ctx, rmtSampleTree* sample_tree) -{ - rmtSample* root = rmt_SampleTreeGetRootSample(sample_tree); - const char* thread_name = rmt_SampleTreeGetThreadName(sample_tree); - if (strcmp("Remotery", thread_name) == 0) - { - return; // to minimize the verbosity in this example - } - - printf("// ******************** DUMP TREE: %s ************************\n", thread_name); - - printTree(root, 0); -} - -void printProperty(rmtProperty* property, int indent) -{ - rmtPropertyIterator iter; - - const char* name = rmt_PropertyGetName(property); - rmtPropertyType type = rmt_PropertyGetType(property); - rmtPropertyValue value = rmt_PropertyGetValue(property); - - printIndent(indent); printf("%s: ", name); - - switch(type) - { - case RMT_PropertyType_rmtBool: printf("%s\n", value.Bool ? "true":"false"); break; - case RMT_PropertyType_rmtS32: printf("%d\n", value.S32); break; - case RMT_PropertyType_rmtU32: printf("%u\n", value.U32); break; - case RMT_PropertyType_rmtF32: printf("%f\n", value.F32); break; - case RMT_PropertyType_rmtS64: printf("%lld\n", value.S64); break; - case RMT_PropertyType_rmtU64: printf("%llu\n", value.U64); break; - case RMT_PropertyType_rmtF64: printf("%g\n", value.F64); break; - case RMT_PropertyType_rmtGroup: printf("\n"); break; - default: break; - }; - - rmt_PropertyIterateChildren(&iter, property); - while (rmt_PropertyIterateNext(&iter)) { - printProperty(iter.property, indent + 1); - } -} - -void dumpProperties(void* ctx, rmtProperty* root) -{ - rmtPropertyIterator iter; - printf("// ******************** DUMP PROPERTIES: ************************\n"); - - rmt_PropertyIterateChildren(&iter, root); - while (rmt_PropertyIterateNext(&iter)) { - printProperty(iter.property, 0); - } -} - -int sig = 0; - -/// Allow to close cleanly with ctrl + c -void sigintHandler(int sig_num) { - sig = sig_num; - printf("Interrupted\n"); -} - -int main() { - Remotery* rmt; - rmtError error; - - signal(SIGINT, sigintHandler); - - rmtSettings* settings = rmt_Settings(); - if (settings) - { - settings->sampletree_handler = dumpTree; - settings->sampletree_context = 0; - - settings->snapshot_callback = dumpProperties; - settings->snapshot_context = 0; - } - - error = rmt_CreateGlobalInstance(&rmt); - - if( RMT_ERROR_NONE != error) { - printf("Error launching Remotery %d\n", error); - return -1; - } - - int max_count = 5; - - while (sig == 0 && --max_count > 0) { - rmt_LogText("start profiling"); - delay(); - rmt_LogText("end profiling"); - - rmt_PropertySet_Bool(WasUpdated, RMT_TRUE); - rmt_PropertyAdd_U32(FrameCounter, 1); - - rmt_PropertySnapshotAll(); - rmt_PropertyFrameResetAll(); - } - - rmt_DestroyGlobalInstance(rmt); - printf("Cleaned up and quit\n"); - return 0; -} diff --git a/profiler/sample/sample.c b/profiler/sample/sample.c deleted file mode 100644 index 3f56060..0000000 --- a/profiler/sample/sample.c +++ /dev/null @@ -1,64 +0,0 @@ -#include -#include -#include -#include -#include "../lib/Remotery.h" - -void aggregateFunction() { - rmt_BeginCPUSample(aggregate, RMTSF_Aggregate); - rmt_EndCPUSample(); -} -void recursiveFunction(int depth) { - rmt_BeginCPUSample(recursive, RMTSF_Recursive); - if (depth < 5) { - recursiveFunction(depth + 1); - } - rmt_EndCPUSample(); -} - -double delay() { - int i, end; - double j = 0; - - rmt_BeginCPUSample(delay, 0); - for( i = 0, end = rand()/100; i < end; ++i ) { - j += sin(i); - } - recursiveFunction(0); - aggregateFunction(); - aggregateFunction(); - aggregateFunction(); - rmt_EndCPUSample(); - return j; -} - -int sig = 0; - -/// Allow to close cleanly with ctrl + c -void sigintHandler(int sig_num) { - sig = sig_num; - printf("Interrupted\n"); -} - -int main( ) { - Remotery *rmt; - rmtError error; - - signal(SIGINT, sigintHandler); - - error = rmt_CreateGlobalInstance(&rmt); - if( RMT_ERROR_NONE != error) { - printf("Error launching Remotery %d\n", error); - return -1; - } - - while (sig == 0) { - rmt_LogText("start profiling"); - delay(); - rmt_LogText("end profiling"); - } - - rmt_DestroyGlobalInstance(rmt); - printf("Cleaned up and quit\n"); - return 0; -} diff --git a/profiler/screenshot.png b/profiler/screenshot.png deleted file mode 100644 index 11f4ce4..0000000 Binary files a/profiler/screenshot.png and /dev/null differ diff --git a/profiler/vis/Code/Console.js b/profiler/vis/Code/Console.js deleted file mode 100644 index 0eb5970..0000000 --- a/profiler/vis/Code/Console.js +++ /dev/null @@ -1,218 +0,0 @@ - -Console = (function() -{ - var BORDER = 10; - var HEIGHT = 200; - - - function Console(wm, server) - { - // Create the window and its controls - this.Window = wm.AddWindow("Console", 10, 10, 100, 100); - this.PageContainer = this.Window.AddControlNew(new WM.Container(10, 10, 400, 160)); - DOM.Node.AddClass(this.PageContainer.Node, "ConsoleText"); - this.AppContainer = this.Window.AddControlNew(new WM.Container(10, 10, 400, 160)); - DOM.Node.AddClass(this.AppContainer.Node, "ConsoleText"); - this.UserInput = this.Window.AddControlNew(new WM.EditBox(10, 5, 400, 30, "Input", "")); - this.UserInput.SetChangeHandler(Bind(ProcessInput, this)); - this.Window.ShowNoAnim(); - - // This accumulates log text as fast as is required - this.PageTextBuffer = ""; - this.PageTextUpdatePending = false; - this.AppTextBuffer = ""; - this.AppTextUpdatePending = false; - - // Setup command history control - this.CommandHistory = LocalStore.Get("App", "Global", "CommandHistory", [ ]); - this.CommandIndex = 0; - this.MaxNbCommands = 10000; - DOM.Event.AddHandler(this.UserInput.EditNode, "keydown", Bind(OnKeyPress, this)); - DOM.Event.AddHandler(this.UserInput.EditNode, "focus", Bind(OnFocus, this)); - - // At a much lower frequency this will update the console window - window.setInterval(Bind(UpdateHTML, this), 500); - - // Setup log requests from the server - this.Server = server; - server.SetConsole(this); - server.AddMessageHandler("LOGM", Bind(OnLog, this)); - - this.Window.SetOnResize(Bind(OnUserResize, this)); - } - - - Console.prototype.Log = function(text) - { - this.PageTextBuffer = LogText(this.PageTextBuffer, text); - this.PageTextUpdatePending = true; - } - - - Console.prototype.WindowResized = function(width, height) - { - // Place window - this.Window.SetPosition(BORDER, height - BORDER - 200); - this.Window.SetSize(width - 2 * BORDER, HEIGHT); - - ResizeInternals(this); - } - - - Console.prototype.TriggerUpdate = function() - { - this.AppTextUpdatePending = true; - } - - - function OnLog(self, socket, data_view_reader) - { - var text = data_view_reader.GetString(); - self.AppTextBuffer = LogText(self.AppTextBuffer, text); - - // Don't register text as updating if disconnected as this implies a trace is being loaded, which we want to speed up - if (self.Server.Connected()) - { - self.AppTextUpdatePending = true; - } - } - - - function LogText(existing_text, new_text) - { - // Filter the text a little to make it safer - if (new_text == null) - new_text = "NULL"; - - // Find and convert any HTML entities, ensuring the browser doesn't parse any embedded HTML code - // This also allows the log to contain arbitrary C++ code (e.g. assert comparison operators) - new_text = Convert.string_to_html_entities(new_text); - - // Prefix date and end with new line - var d = new Date(); - new_text = "[" + d.toLocaleTimeString() + "] " + new_text + "
"; - - // Append to local text buffer and ensure clip the oldest text to ensure a max size - existing_text = existing_text + new_text; - var max_len = 100 * 1024; - var len = existing_text.length; - if (len > max_len) - existing_text = existing_text.substr(len - max_len, max_len); - - return existing_text; - } - - function OnUserResize(self, evt) - { - ResizeInternals(self); - } - - function ResizeInternals(self) - { - // Place controls - var parent_size = self.Window.Size; - var mid_w = parent_size[0] / 3; - self.UserInput.SetPosition(BORDER, parent_size[1] - 2 * BORDER - 30); - self.UserInput.SetSize(parent_size[0] - 100, 18); - var output_height = self.UserInput.Position[1] - 2 * BORDER; - self.PageContainer.SetPosition(BORDER, BORDER); - self.PageContainer.SetSize(mid_w - 2 * BORDER, output_height); - self.AppContainer.SetPosition(mid_w, BORDER); - self.AppContainer.SetSize(parent_size[0] - mid_w - BORDER, output_height); - } - - - function UpdateHTML(self) - { - // Reset the current text buffer as html - - if (self.PageTextUpdatePending) - { - var page_node = self.PageContainer.Node; - page_node.innerHTML = self.PageTextBuffer; - page_node.scrollTop = page_node.scrollHeight; - self.PageTextUpdatePending = false; - } - - if (self.AppTextUpdatePending) - { - var app_node = self.AppContainer.Node; - app_node.innerHTML = self.AppTextBuffer; - app_node.scrollTop = app_node.scrollHeight; - self.AppTextUpdatePending = false; - } - } - - - function ProcessInput(self, node) - { - // Send the message exactly - var msg = node.value; - self.Server.Send("CONI" + msg); - - // Emit to console and clear - self.Log("> " + msg); - self.UserInput.SetValue(""); - - // Keep track of recently issued commands, with an upper bound - self.CommandHistory.push(msg); - var extra_commands = self.CommandHistory.length - self.MaxNbCommands; - if (extra_commands > 0) - self.CommandHistory.splice(0, extra_commands); - - // Set command history index to the most recent command - self.CommandIndex = self.CommandHistory.length; - - // Backup to local store - LocalStore.Set("App", "Global", "CommandHistory", self.CommandHistory); - - // Keep focus with the edit box - return true; - } - - - function OnKeyPress(self, evt) - { - evt = DOM.Event.Get(evt); - - if (evt.keyCode == Keyboard.Codes.UP) - { - if (self.CommandHistory.length > 0) - { - // Cycle backwards through the command history - self.CommandIndex--; - if (self.CommandIndex < 0) - self.CommandIndex = self.CommandHistory.length - 1; - var command = self.CommandHistory[self.CommandIndex]; - self.UserInput.SetValue(command); - } - - // Stops default behaviour of moving cursor to the beginning - DOM.Event.StopDefaultAction(evt); - } - - else if (evt.keyCode == Keyboard.Codes.DOWN) - { - if (self.CommandHistory.length > 0) - { - // Cycle fowards through the command history - self.CommandIndex = (self.CommandIndex + 1) % self.CommandHistory.length; - var command = self.CommandHistory[self.CommandIndex]; - self.UserInput.SetValue(command); - } - - // Stops default behaviour of moving cursor to the end - DOM.Event.StopDefaultAction(evt); - } - } - - - function OnFocus(self) - { - // Reset command index on focus - self.CommandIndex = self.CommandHistory.length; - } - - - return Console; -})(); diff --git a/profiler/vis/Code/DataViewReader.js b/profiler/vis/Code/DataViewReader.js deleted file mode 100644 index e2752b4..0000000 --- a/profiler/vis/Code/DataViewReader.js +++ /dev/null @@ -1,94 +0,0 @@ - -// -// Simple wrapper around DataView that auto-advances the read offset and provides -// a few common data type conversions specific to this app -// -DataViewReader = (function () -{ - function DataViewReader(data_view, offset) - { - this.DataView = data_view; - this.Offset = offset; - } - - DataViewReader.prototype.AtEnd = function() - { - return this.Offset >= this.DataView.byteLength; - } - - DataViewReader.prototype.GetBool = function () - { - let v = this.DataView.getUint8(this.Offset); - this.Offset++; - return v; - } - - DataViewReader.prototype.GetUInt8 = function () - { - let v = this.DataView.getUint8(this.Offset); - this.Offset++; - return v; - } - - DataViewReader.prototype.GetInt32 = function () - { - let v = this.DataView.getInt32(this.Offset, true); - this.Offset += 4; - return v; - } - - DataViewReader.prototype.GetUInt32 = function () - { - var v = this.DataView.getUint32(this.Offset, true); - this.Offset += 4; - return v; - } - - DataViewReader.prototype.GetFloat32 = function () - { - const v = this.DataView.getFloat32(this.Offset, true); - this.Offset += 4; - return v; - } - - DataViewReader.prototype.GetInt64 = function () - { - var v = this.DataView.getFloat64(this.Offset, true); - this.Offset += 8; - return v; - } - - DataViewReader.prototype.GetUInt64 = function () - { - var v = this.DataView.getFloat64(this.Offset, true); - this.Offset += 8; - return v; - } - - DataViewReader.prototype.GetFloat64 = function () - { - var v = this.DataView.getFloat64(this.Offset, true); - this.Offset += 8; - return v; - } - - DataViewReader.prototype.GetStringOfLength = function (string_length) - { - var string = ""; - for (var i = 0; i < string_length; i++) - { - string += String.fromCharCode(this.DataView.getInt8(this.Offset)); - this.Offset++; - } - - return string; - } - - DataViewReader.prototype.GetString = function () - { - var string_length = this.GetUInt32(); - return this.GetStringOfLength(string_length); - } - - return DataViewReader; -})(); diff --git a/profiler/vis/Code/GLCanvas.js b/profiler/vis/Code/GLCanvas.js deleted file mode 100644 index 23ce7db..0000000 --- a/profiler/vis/Code/GLCanvas.js +++ /dev/null @@ -1,123 +0,0 @@ - -class GLCanvas -{ - constructor(width, height) - { - this.width = width; - this.height = height; - - // Create a WebGL 2 canvas without premultiplied alpha - this.glCanvas = document.createElement("canvas"); - this.glCanvas.width = width; - this.glCanvas.height = height; - this.gl = this.glCanvas.getContext("webgl2", { premultipliedAlpha: false, antialias: false }); - - // Overlay the canvas on top of everything and make sure mouse events click-through - this.glCanvas.style.position = "fixed"; - this.glCanvas.style.pointerEvents = "none"; - this.glCanvas.style.zIndex = 1000; - document.body.appendChild(this.glCanvas); - - // Hook up resize event handler - DOM.Event.AddHandler(window, "resize", () => this.OnResizeWindow()); - this.OnResizeWindow(); - - // Compile needed shaders - this.timelineProgram = glCreateProgramFromSource(this.gl, "TimelineVShader", TimelineVShader, "TimelineFShader", TimelineFShader); - this.timelineHighlightProgram = glCreateProgramFromSource(this.gl, "TimelineHighlightVShader", TimelineHighlightVShader, "TimelineHighlightFShader", TimelineHighlightFShader); - this.timelineGpuToCpuProgram = glCreateProgramFromSource(this.gl, "TimelineGpuToCpuVShader", TimelineGpuToCpuVShader, "TimelineGpuToCpuFShader", TimelineGpuToCpuFShader); - this.timelineBackgroundProgram = glCreateProgramFromSource(this.gl, "TimelineBackgroundVShader", TimelineBackgroundVShader, "TimelineBackgroundFShader", TimelineBackgroundFShader); - this.gridProgram = glCreateProgramFromSource(this.gl, "GridVShader", GridVShader, "GridFShader", GridFShader); - this.gridNumberProgram = glCreateProgramFromSource(this.gl, "GridNumberVShader", GridNumberVShader, "GridNumberFShader", GridNumberFShader); - this.windowProgram = glCreateProgramFromSource(this.gl, "WindowVShader", WindowVShader, "WindowFShader", WindowFShader); - - // Create the shader font resources - this.font = new glFont(this.gl); - this.textBuffer = new glTextBuffer(this.gl, this.font); - this.nameMap = new NameMap(this.textBuffer); - - // Kick off the rendering refresh - this.OnDrawHandler = null; - this.Draw(performance.now()); - } - - SetOnDraw(handler) - { - this.OnDrawHandler = handler; - } - - ClearTextResources() - { - this.nameMap = new NameMap(this.textBuffer); - } - - OnResizeWindow() - { - // Resize to match the window - this.width = window.innerWidth; - this.height = window.innerHeight; - this.glCanvas.width = window.innerWidth; - this.glCanvas.height = window.innerHeight; - } - - SetFontUniforms(program) - { - // Font texture may not be loaded yet - if (this.font.atlasTexture != null) - { - const gl = this.gl; - glSetUniform(gl, program, "inFontAtlasTexture", this.font.atlasTexture, 0); - glSetUniform(gl, program, "inTextBufferDesc.fontWidth", this.font.fontWidth); - glSetUniform(gl, program, "inTextBufferDesc.fontHeight", this.font.fontHeight); - } - } - - SetTextUniforms(program) - { - const gl = this.gl; - this.SetFontUniforms(program); - this.textBuffer.SetAsUniform(gl, program, "inTextBuffer", 1); - } - - SetContainerUniforms(program, container) - { - const gl = this.gl; - const container_rect = container.getBoundingClientRect(); - glSetUniform(gl, program, "inContainer.x0", container_rect.left); - glSetUniform(gl, program, "inContainer.y0", container_rect.top); - glSetUniform(gl, program, "inContainer.x1", container_rect.left + container_rect.width); - glSetUniform(gl, program, "inContainer.y1", container_rect.top + container_rect.height); - } - - EnableBlendPremulAlpha() - { - const gl = this.gl; - gl.enable(gl.BLEND); - gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); - } - - DisableBlend() - { - const gl = this.gl; - gl.disable(gl.BLEND); - } - - Draw(timestamp) - { - // Setup the viewport and clear the screen - const gl = this.gl; - gl.viewport(0, 0, gl.canvas.width, gl.canvas.height); - gl.clearColor(0, 0, 0, 0); - gl.clear(gl.COLOR_BUFFER_BIT); - - // Chain to the Draw handler - const seconds = timestamp / 1000.0; - if (this.OnDrawHandler != null) - { - this.OnDrawHandler(gl, seconds); - } - - // Reschedule - window.requestAnimationFrame((timestamp) => this.Draw(timestamp)); - } -}; diff --git a/profiler/vis/Code/GridWindow.js b/profiler/vis/Code/GridWindow.js deleted file mode 100644 index 82ac2ec..0000000 --- a/profiler/vis/Code/GridWindow.js +++ /dev/null @@ -1,291 +0,0 @@ - -class GridConfigSamples -{ - constructor() - { - this.nbFloatsPerSample = g_nbFloatsPerSample; - - this.columns = []; - this.columns.push(new GridColumn("Sample Name", 196)); - this.columns.push(new GridColumn("Time (ms)", 56, g_sampleOffsetFloats_Length, 4)); - this.columns.push(new GridColumn("Self (ms)", 56, g_sampleOffsetFloats_Self, 4)); - this.columns.push(new GridColumn("Calls", 34, g_sampleOffsetFloats_Calls, 0)); - this.columns.push(new GridColumn("Depth", 34, g_sampleOffsetFloats_Recurse, 0)); - } -} - -class GridConfigProperties -{ - constructor() - { - this.nbFloatsPerSample = 10; - - this.columns = []; - this.columns.push(new GridColumn("Property Name", 196)); - this.columns.push(new GridColumn("Value", 90, 4, 4)); - this.columns.push(new GridColumn("Prev Value", 90, 6, 4)); - } -} - -class GridColumn -{ - static ColumnTemplate = `
`; - - constructor(name, width, number_offset, nb_float_chars) - { - // Description - this.name = name; - this.width = width; - this.numberOffset = number_offset; - this.nbFloatChars = nb_float_chars; - - // Constants - this.rowHeight = 15; - } - - Attach(parent_node) - { - // Generate HTML for the header and parent it - const column = DOM.Node.CreateHTML(GridColumn.ColumnTemplate); - column.innerHTML = this.name; - column.style.width = (this.width - 4) + "px"; - this.headerNode = parent_node.appendChild(column); - } - - Draw(gl_canvas, x, buffer, scroll_pos, clip, nb_entries, nb_floats_per_sample) - { - // If a number offset in the data stream is provided, we're rendering numbers and not names - if (this.numberOffset !== undefined) - { - this._DrawNumbers(gl_canvas, x, buffer, scroll_pos, clip, nb_entries, nb_floats_per_sample); - } - else - { - this._DrawNames(gl_canvas, x, buffer, scroll_pos, clip, nb_entries, nb_floats_per_sample); - } - } - - _DrawNames(gl_canvas, x, buffer, scroll_pos, clip, nb_entries, nb_floats_per_sample) - { - const gl = gl_canvas.gl; - const program = gl_canvas.gridProgram; - - gl.useProgram(program); - gl_canvas.SetTextUniforms(program); - - this._DrawAny(gl, program, x, buffer, scroll_pos, clip, nb_entries, nb_floats_per_sample); - } - - _DrawNumbers(gl_canvas, x, buffer, scroll_pos, clip, nb_entries, nb_floats_per_sample) - { - const gl = gl_canvas.gl; - const program = gl_canvas.gridNumberProgram; - - gl.useProgram(program); - gl_canvas.SetFontUniforms(program); - glSetUniform(gl, program, "inNumberOffset", this.numberOffset); - glSetUniform(gl, program, "inNbFloatChars", this.nbFloatChars); - - this._DrawAny(gl, program, x, buffer, scroll_pos, clip, nb_entries, nb_floats_per_sample); - } - - _DrawAny(gl, program, x, buffer, scroll_pos, clip, nb_entries, nb_floats_per_sample) - { - const clip_min_x = clip[0]; - const clip_min_y = clip[1]; - const clip_max_x = clip[2]; - const clip_max_y = clip[3]; - - // Scrolled position of the grid - const pos_x = clip_min_x + scroll_pos[0] + x; - const pos_y = clip_min_y + scroll_pos[1]; - - // Clip column to the window - const min_x = Math.min(Math.max(clip_min_x, pos_x), clip_max_x); - const min_y = Math.min(Math.max(clip_min_y, pos_y), clip_max_y); - const max_x = Math.max(Math.min(clip_max_x, pos_x + this.width), clip_min_x); - const max_y = Math.max(Math.min(clip_max_y, pos_y + nb_entries * this.rowHeight), clip_min_y); - - // Don't render if outside the bounds of the main window - if (min_x > gl.canvas.width || max_x < 0 || min_y > gl.canvas.height || max_y < 0) - { - return; - } - - const pixel_offset_x = Math.max(min_x - pos_x, 0); - const pixel_offset_y = Math.max(min_y - pos_y, 0); - - // Viewport constants - glSetUniform(gl, program, "inViewport.width", gl.canvas.width); - glSetUniform(gl, program, "inViewport.height", gl.canvas.height); - - // Grid constants - glSetUniform(gl, program, "inGrid.minX", min_x); - glSetUniform(gl, program, "inGrid.minY", min_y); - glSetUniform(gl, program, "inGrid.maxX", max_x); - glSetUniform(gl, program, "inGrid.maxY", max_y); - glSetUniform(gl, program, "inGrid.pixelOffsetX", pixel_offset_x); - glSetUniform(gl, program, "inGrid.pixelOffsetY", pixel_offset_y); - - // Source data set buffers - glSetUniform(gl, program, "inSamples", buffer.texture, 2); - glSetUniform(gl, program, "inSamplesLength", buffer.nbEntries); - glSetUniform(gl, program, "inFloatsPerSample", nb_floats_per_sample); - glSetUniform(gl, program, "inNbSamples", buffer.nbEntries / nb_floats_per_sample); - - gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); - } -} - -class GridWindow -{ - static GridTemplate = ` -
-
-
-
- `; - - constructor(wm, name, offset, gl_canvas, config) - { - this.nbEntries = 0; - this.scrollPos = [ 0, 0 ]; - - // Window setup - this.xPos = 10 + offset * 410; - this.window = wm.AddWindow(name, 100, 100, 100, 100, null, this); - this.window.ShowNoAnim(); - this.visible = true; - - // Cache how much internal padding the window has, for clipping - const style = getComputedStyle(this.window.BodyNode); - this.bodyPadding = parseFloat(style.padding); - - // Create the Grid host HTML - const grid_node = DOM.Node.CreateHTML(GridWindow.GridTemplate); - this.gridNode = this.window.BodyNode.appendChild(grid_node); - this.headerNode = this.gridNode.children[0]; - this.contentNode = this.gridNode.children[1]; - - // Build column data - this.nbFloatsPerSample = config.nbFloatsPerSample; - this.columns = config.columns; - for (let column of this.columns) - { - column.Attach(this.headerNode); - } - this._PositionHeaders(); - - // Header nodes have 1 pixel borders so the first column is required to have a width 1 less than everything else - // To counter that, shift the first header node one to the left (it will clip) so that it can have its full width - this.columns[0].headerNode.style.marginLeft = "-1px"; - - // Setup for pan/wheel scrolling - this.mouseInteraction = new MouseInteraction(this.window.BodyNode); - this.mouseInteraction.onMoveHandler = (mouse_state, mx, my) => this._OnMouseMove(mouse_state, mx, my); - this.mouseInteraction.onScrollHandler = (mouse_state) => this._OnMouseScroll(mouse_state); - - const gl = gl_canvas.gl; - this.glCanvas = gl_canvas; - this.sampleBuffer = new glDynamicBuffer(gl, glDynamicBufferType.Texture, gl.FLOAT, 1); - } - - Close() - { - this.window.Close(); - } - - static AnimatedMove(self, top_window, bottom_window, val) - { - self.xPos = val; - self.WindowResized(top_window, bottom_window); - } - - SetXPos(xpos, top_window, bottom_window) - { - Anim.Animate( - Bind(GridWindow.AnimatedMove, this, top_window, bottom_window), - this.xPos, 10 + xpos * 410, 0.25); - } - - SetVisible(visible) - { - if (visible != this.visible) - { - if (visible == true) - this.window.ShowNoAnim(); - else - this.window.HideNoAnim(); - - this.visible = visible; - } - } - - WindowResized(top_window, bottom_window) - { - const top = top_window.Position[1] + top_window.Size[1] + 10; - this.window.SetPosition(this.xPos, top_window.Position[1] + top_window.Size[1] + 10); - this.window.SetSize(400, bottom_window.Position[1] - 10 - top); - } - - UpdateEntries(nb_entries, samples) - { - // This tracks the latest actual entry count - this.nbEntries = nb_entries; - - // Resize buffers to match any new entry count - if (nb_entries * this.nbFloatsPerSample > this.sampleBuffer.nbEntries) - { - this.sampleBuffer.ResizeToFitNextPow2(nb_entries * this.nbFloatsPerSample); - } - - // Copy and upload the entry data - this.sampleBuffer.cpuArray.set(samples); - this.sampleBuffer.UploadData(); - } - - Draw() - { - // Establish content node clipping rectangle - const rect = this.contentNode.getBoundingClientRect(); - const clip = [ - rect.left, - rect.top, - rect.left + rect.width, - rect.top + rect.height, - ]; - - // Draw columns, left-to-right - let x = 0; - for (let column of this.columns) - { - column.Draw(this.glCanvas, x, this.sampleBuffer, this.scrollPos, clip, this.nbEntries, this.nbFloatsPerSample); - x += column.width + 1; - } - } - - _PositionHeaders() - { - let x = this.scrollPos[0]; - for (let i in this.columns) - { - const column = this.columns[i]; - column.headerNode.style.left = x + "px"; - x += column.width; - x += (i >= 1) ? 1 : 0; - } - } - - _OnMouseMove(mouse_state, mouse_offset_x, mouse_offset_y) - { - this.scrollPos[0] = Math.min(0, this.scrollPos[0] + mouse_offset_x); - this.scrollPos[1] = Math.min(0, this.scrollPos[1] + mouse_offset_y); - - this._PositionHeaders(); - } - - _OnMouseScroll(mouse_state) - { - this.scrollPos[1] = Math.min(0, this.scrollPos[1] + mouse_state.WheelDelta * 15); - } -} \ No newline at end of file diff --git a/profiler/vis/Code/MouseInteraction.js b/profiler/vis/Code/MouseInteraction.js deleted file mode 100644 index 4c5b3a0..0000000 --- a/profiler/vis/Code/MouseInteraction.js +++ /dev/null @@ -1,106 +0,0 @@ -class MouseInteraction -{ - constructor(node) - { - this.node = node; - - // Current interaction state - this.mouseDown = false; - this.lastMouseState = null; - this.mouseMoved = false; - - // Empty user handlers - this.onClickHandler = null; - this.onMoveHandler = null; - this.onHoverHandler = null; - this.onScrollHandler = null; - - // Setup DOM handlers - DOM.Event.AddHandler(this.node, "mousedown", (evt) => this._OnMouseDown(evt)); - DOM.Event.AddHandler(this.node, "mouseup", (evt) => this._OnMouseUp(evt)); - DOM.Event.AddHandler(this.node, "mousemove", (evt) => this._OnMouseMove(evt)); - DOM.Event.AddHandler(this.node, "mouseleave", (evt) => this._OnMouseLeave(evt)); - - // Mouse wheel is a little trickier - const mouse_wheel_event = (/Firefox/i.test(navigator.userAgent)) ? "DOMMouseScroll" : "mousewheel"; - DOM.Event.AddHandler(this.node, mouse_wheel_event, (evt) => this._OnMouseScroll(evt)); - } - - _OnMouseDown(evt) - { - this.mouseDown = true; - this.lastMouseState = new Mouse.State(evt); - this.mouseMoved = false; - DOM.Event.StopDefaultAction(evt); - } - - _OnMouseUp(evt) - { - const mouse_state = new Mouse.State(evt); - - this.mouseDown = false; - - // Chain to on click event when released without movement - if (!this.mouseMoved) - { - if (this.onClickHandler) - { - this.onClickHandler(mouse_state); - } - } - } - - _OnMouseMove(evt) - { - const mouse_state = new Mouse.State(evt); - - if (this.mouseDown) - { - // Has the mouse moved while being held down? - const move_offset_x = mouse_state.Position[0] - this.lastMouseState.Position[0]; - const move_offset_y = mouse_state.Position[1] - this.lastMouseState.Position[1]; - if (move_offset_x != 0 || move_offset_y != 0) - { - this.mouseMoved = true; - - // Chain to move handler - if (this.onMoveHandler) - { - this.onMoveHandler(mouse_state, move_offset_x, move_offset_y); - } - } - } - - // Chain to hover handler - else if (this.onHoverHandler) - { - this.onHoverHandler(mouse_state); - } - - this.lastMouseState = mouse_state; - } - - _OnMouseLeave(evt) - { - // Cancel panning - this.mouseDown = false; - - // Cancel hovering - if (this.onHoverHandler) - { - this.onHoverHandler(null); - } - } - - _OnMouseScroll(evt) - { - const mouse_state = new Mouse.State(evt); - if (this.onScrollHandler) - { - this.onScrollHandler(mouse_state); - - // Prevent vertical scrolling on mouse-wheel - DOM.Event.StopDefaultAction(evt); - } - } -}; diff --git a/profiler/vis/Code/NameMap.js b/profiler/vis/Code/NameMap.js deleted file mode 100644 index 689ebdd..0000000 --- a/profiler/vis/Code/NameMap.js +++ /dev/null @@ -1,53 +0,0 @@ -class NameMap -{ - constructor(text_buffer) - { - this.names = { }; - this.textBuffer = text_buffer; - } - - Get(name_hash) - { - // Return immediately if it's in the hash - let name = this.names[name_hash]; - if (name != undefined) - { - return [ true, name ]; - } - - // Create a temporary name that uses the hash - name = { - string: name_hash.toString(), - hash: name_hash - }; - this.names[name_hash] = name; - - // Add to the text buffer the first time this name is encountered - name.textEntry = this.textBuffer.AddText(name.string); - - return [ false, name ]; - } - - Set(name_hash, name_string) - { - // Create the name on-demand if its hash doesn't exist - let name = this.names[name_hash]; - if (name == undefined) - { - name = { - string: name_string, - hash: name_hash - }; - this.names[name_hash] = name; - } - else - { - name.string = name_string; - } - - // Apply the updated text to the buffer - name.textEntry = this.textBuffer.AddText(name_string); - - return name; - } -} \ No newline at end of file diff --git a/profiler/vis/Code/PixelTimeRange.js b/profiler/vis/Code/PixelTimeRange.js deleted file mode 100644 index 8c0ce81..0000000 --- a/profiler/vis/Code/PixelTimeRange.js +++ /dev/null @@ -1,61 +0,0 @@ - -class PixelTimeRange -{ - constructor(start_us, span_us, span_px) - { - this.Span_px = span_px; - this.Set(start_us, span_us); - } - - Set(start_us, span_us) - { - this.Start_us = start_us; - this.Span_us = span_us; - this.End_us = this.Start_us + span_us; - this.usPerPixel = this.Span_px / this.Span_us; - } - - SetStart(start_us) - { - this.Start_us = start_us; - this.End_us = start_us + this.Span_us; - } - - SetEnd(end_us) - { - this.End_us = end_us; - this.Start_us = end_us - this.Span_us; - } - - SetPixelSpan(span_px) - { - this.Span_px = span_px; - this.usPerPixel = this.Span_px / this.Span_us; - } - - PixelOffset(time_us) - { - return Math.floor((time_us - this.Start_us) * this.usPerPixel); - } - - PixelSize(time_us) - { - return Math.floor(time_us * this.usPerPixel); - } - - TimeAtPosition(position) - { - return this.Start_us + position / this.usPerPixel; - } - - Clone() - { - return new PixelTimeRange(this.Start_us, this.Span_us, this.Span_px); - } - - SetAsUniform(gl, program) - { - glSetUniform(gl, program, "inTimeRange.msStart", this.Start_us / 1000.0); - glSetUniform(gl, program, "inTimeRange.msPerPixel", this.usPerPixel * 1000.0); - } -} diff --git a/profiler/vis/Code/Remotery.js b/profiler/vis/Code/Remotery.js deleted file mode 100644 index b0490b7..0000000 --- a/profiler/vis/Code/Remotery.js +++ /dev/null @@ -1,756 +0,0 @@ - -// -// TODO: Window resizing needs finer-grain control -// TODO: Take into account where user has moved the windows -// TODO: Controls need automatic resizing within their parent windows -// - - -Settings = (function() -{ - function Settings() - { - this.IsPaused = false; - this.SyncTimelines = true; - } - - return Settings; - -})(); - - -Remotery = (function() -{ - // crack the url and get the parameter we want - var getUrlParameter = function getUrlParameter( search_param) - { - var page_url = decodeURIComponent( window.location.search.substring(1) ), - url_vars = page_url.split('&'), - param_name, - i; - - for (i = 0; i < url_vars.length; i++) - { - param_name = url_vars[i].split('='); - - if (param_name[0] === search_param) - { - return param_name[1] === undefined ? true : param_name[1]; - } - } - }; - - function Remotery() - { - this.WindowManager = new WM.WindowManager(); - this.Settings = new Settings(); - - // "addr" param is ip:port and will override the local store version if passed in the URL - var addr = getUrlParameter( "addr" ); - if ( addr != null ) - this.ConnectionAddress = "ws://" + addr + "/rmt"; - else - this.ConnectionAddress = LocalStore.Get("App", "Global", "ConnectionAddress", "ws://127.0.0.1:17815/rmt"); - - this.Server = new WebSocketConnection(); - this.Server.AddConnectHandler(Bind(OnConnect, this)); - this.Server.AddDisconnectHandler(Bind(OnDisconnect, this)); - - this.glCanvas = new GLCanvas(100, 100); - this.glCanvas.SetOnDraw((gl, seconds) => this.OnGLCanvasDraw(gl, seconds)); - - // Create the console up front as everything reports to it - this.Console = new Console(this.WindowManager, this.Server); - - // Create required windows - this.TitleWindow = new TitleWindow(this.WindowManager, this.Settings, this.Server, this.ConnectionAddress); - this.TitleWindow.SetConnectionAddressChanged(Bind(OnAddressChanged, this)); - this.SampleTimelineWindow = new TimelineWindow(this.WindowManager, "Sample Timeline", this.Settings, Bind(OnTimelineCheck, this), this.glCanvas); - this.SampleTimelineWindow.SetOnHover(Bind(OnSampleHover, this)); - this.SampleTimelineWindow.SetOnSelected(Bind(OnSampleSelected, this)); - this.ProcessorTimelineWindow = new TimelineWindow(this.WindowManager, "Processor Timeline", this.Settings, null, this.glCanvas); - - this.SampleTimelineWindow.SetOnMoved(Bind(OnTimelineMoved, this)); - this.ProcessorTimelineWindow.SetOnMoved(Bind(OnTimelineMoved, this)); - - this.TraceDrop = new TraceDrop(this); - - this.nbGridWindows = 0; - this.gridWindows = { }; - this.FrameHistory = { }; - this.ProcessorFrameHistory = { }; - this.PropertyFrameHistory = [ ]; - this.SelectedFrames = { }; - - this.Server.AddMessageHandler("SMPL", Bind(OnSamples, this)); - this.Server.AddMessageHandler("SSMP", Bind(OnSampleName, this)); - this.Server.AddMessageHandler("PRTH", Bind(OnProcessorThreads, this)); - this.Server.AddMessageHandler("PSNP", Bind(OnPropertySnapshots, this)); - - // Kick-off the auto-connect loop - AutoConnect(this); - - // Hook up resize event handler - DOM.Event.AddHandler(window, "resize", Bind(OnResizeWindow, this)); - OnResizeWindow(this); - } - - - Remotery.prototype.Clear = function() - { - // Clear timelines - this.SampleTimelineWindow.Clear(); - this.ProcessorTimelineWindow.Clear(); - - // Close and clear all sample windows - for (var i in this.gridWindows) - { - const grid_window = this.gridWindows[i]; - grid_window.Close(); - } - this.nbGridWindows = 0; - this.gridWindows = { }; - - this.propertyGridWindow = this.AddGridWindow("__rmt__global__properties__", "Global Properties", new GridConfigProperties()); - - // Clear runtime data - this.FrameHistory = { }; - this.ProcessorFrameHistory = { }; - this.PropertyFrameHistory = [ ]; - this.SelectedFrames = { }; - this.glCanvas.ClearTextResources(); - - // Resize everything to fit new layout - OnResizeWindow(this); - } - - function DrawWindowMask(gl, program, window_node) - { - gl.useProgram(program); - - // Using depth as a mask - gl.enable(gl.DEPTH_TEST); - gl.depthFunc(gl.LEQUAL); - - // Viewport constants - glSetUniform(gl, program, "inViewport.width", gl.canvas.width); - glSetUniform(gl, program, "inViewport.height", gl.canvas.height); - - // Window dimensions - const rect = window_node.getBoundingClientRect(); - glSetUniform(gl, program, "minX", rect.left); - glSetUniform(gl, program, "minY", rect.top); - glSetUniform(gl, program, "maxX", rect.left + rect.width); - glSetUniform(gl, program, "maxY", rect.top + rect.height); - - gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); - } - - Remotery.prototype.OnGLCanvasDraw = function(gl, seconds) - { - this.glCanvas.textBuffer.UploadData(); - - // Draw windows in their z-order, front-to-back - // Depth test is enabled, rejecting equal z, and windows render transparent - // Draw window content first, then draw the invisible window mask - // Any windows that come after another window will not draw where the previous window already masked out depth - for (let window of this.WindowManager.Windows) - { - // Some windows might not have WebGL drawing on them; they need to occlude as well - DrawWindowMask(gl, this.glCanvas.windowProgram, window.Node); - - if (window.userData != null) - { - window.userData.Draw(); - } - } - } - - function AutoConnect(self) - { - // Only attempt to connect if there isn't already a connection or an attempt to connect - if (!self.Server.Connected() && !self.Server.Connecting()) - { - self.Server.Connect(self.ConnectionAddress); - } - - // Always schedule another check - window.setTimeout(Bind(AutoConnect, self), 2000); - } - - - function OnConnect(self) - { - // Connection address has been validated - LocalStore.Set("App", "Global", "ConnectionAddress", self.ConnectionAddress); - - self.Clear(); - - // Ensure the viewer is ready for realtime updates - self.TitleWindow.Unpause(); - } - - function OnDisconnect(self) - { - // Pause so the user can inspect the trace - self.TitleWindow.Pause(); - } - - - function OnAddressChanged(self, node) - { - // Update and disconnect, relying on auto-connect to reconnect - self.ConnectionAddress = node.value; - self.Server.Disconnect(); - - // Give input focus away - return false; - } - - - Remotery.prototype.AddGridWindow = function(name, display_name, config) - { - const grid_window = new GridWindow(this.WindowManager, display_name, this.nbGridWindows, this.glCanvas, config); - this.gridWindows[name] = grid_window; - this.gridWindows[name].WindowResized(this.SampleTimelineWindow.Window, this.Console.Window); - this.nbGridWindows++; - MoveGridWindows(this); - return grid_window; - } - - - function DecodeSampleHeader(self, data_view_reader, length) - { - // Message-specific header - let message = { }; - message.messageStart = data_view_reader.Offset; - message.thread_name = data_view_reader.GetString(); - message.nb_samples = data_view_reader.GetUInt32(); - message.partial_tree = data_view_reader.GetUInt32(); - - // Align sample reading to 32-bit boundary - const align = ((4 - (data_view_reader.Offset & 3)) & 3); - data_view_reader.Offset += align; - message.samplesStart = data_view_reader.Offset; - message.samplesLength = length - (message.samplesStart - message.messageStart); - - return message; - } - - - function SetNanosecondsAsMilliseconds(samples_view, offset) - { - samples_view.setFloat32(offset, samples_view.getFloat64(offset, true) / 1000.0, true); - } - - - function SetUint32AsFloat32(samples_view, offset) - { - samples_view.setFloat32(offset, samples_view.getUint32(offset, true), true); - } - - - function ProcessSampleTree(self, sample_data, message) - { - const empty_text_entry = { - offset: 0, - length: 1, - }; - - const samples_length = message.nb_samples * g_nbBytesPerSample; - const samples_view = new DataView(sample_data, message.samplesStart, samples_length); - message.sampleDataView = samples_view; - - for (let offset = 0; offset < samples_length; offset += g_nbBytesPerSample) - { - // Get name hash and lookup in name map - const name_hash = samples_view.getUint32(offset, true); - const [ name_exists, name ] = self.glCanvas.nameMap.Get(name_hash); - - // If the name doesn't exist in the map yet, request it from the server - if (!name_exists) - { - if (self.Server.Connected()) - { - self.Server.Send("GSMP" + name_hash); - } - } - - // Add sample name text buffer location - const text_entry = name.textEntry != null ? name.textEntry : empty_text_entry; - samples_view.setFloat32(offset + g_sampleOffsetBytes_NameOffset, text_entry.offset, true); - samples_view.setFloat32(offset + g_sampleOffsetBytes_NameLength, text_entry.length, true); - - // Time in milliseconds - SetNanosecondsAsMilliseconds(samples_view, offset + g_sampleOffsetBytes_Start); - SetNanosecondsAsMilliseconds(samples_view, offset + g_sampleOffsetBytes_Length); - SetNanosecondsAsMilliseconds(samples_view, offset + g_sampleOffsetBytes_Self); - SetNanosecondsAsMilliseconds(samples_view, offset + g_sampleOffsetBytes_GpuToCpu); - - // Convert call count/recursion integers to float - SetUint32AsFloat32(samples_view, offset + g_sampleOffsetBytes_Calls); - SetUint32AsFloat32(samples_view, offset + g_sampleOffsetBytes_Recurse); - } - - // Convert to floats for GPU - message.sampleFloats = new Float32Array(sample_data, message.samplesStart, message.nb_samples * g_nbFloatsPerSample); - } - - function OnSamples(self, socket, data_view_reader, length) - { - // Discard any new samples while paused and connected - // Otherwise this stops a paused Remotery from loading new samples from disk - if (self.Settings.IsPaused && self.Server.Connected()) - return; - - // Binary decode incoming sample data - var message = DecodeSampleHeader(self, data_view_reader, length); - if (message.nb_samples == 0) - { - return; - } - var name = message.thread_name; - ProcessSampleTree(self, data_view_reader.DataView.buffer, message); - - // Add to frame history for this thread - var thread_frame = new ThreadFrame(message); - if (!(name in self.FrameHistory)) - { - self.FrameHistory[name] = [ ]; - } - var frame_history = self.FrameHistory[name]; - if (frame_history.length > 0 && frame_history[frame_history.length - 1].PartialTree) - { - // Always overwrite partial trees with new information - frame_history[frame_history.length - 1] = thread_frame; - } - else - { - frame_history.push(thread_frame); - } - - // Discard old frames to keep memory-use constant - var max_nb_frames = 10000; - var extra_frames = frame_history.length - max_nb_frames; - if (extra_frames > 0) - frame_history.splice(0, extra_frames); - - // Create sample windows on-demand - if (!(name in self.gridWindows)) - { - self.AddGridWindow(name, name, new GridConfigSamples()); - } - - // Set on the window and timeline if connected as this implies a trace is being loaded, which we want to speed up - if (self.Server.Connected()) - { - self.gridWindows[name].UpdateEntries(message.nb_samples, message.sampleFloats); - - self.SampleTimelineWindow.OnSamples(name, frame_history); - } - } - - - function OnSampleName(self, socket, data_view_reader) - { - // Add any names sent by the server to the local map - let name_hash = data_view_reader.GetUInt32(); - let name_string = data_view_reader.GetString(); - self.glCanvas.nameMap.Set(name_hash, name_string); - } - - - function OnProcessorThreads(self, socket, data_view_reader) - { - // Discard any new samples while paused and connected - // Otherwise this stops a paused Remotery from loading new samples from disk - if (self.Settings.IsPaused && self.Server.Connected()) - return; - - let nb_processors = data_view_reader.GetUInt32(); - let message_index = data_view_reader.GetUInt64(); - - const empty_text_entry = { - offset: 0, - length: 1, - }; - - // Decode each processor - for (let i = 0; i < nb_processors; i++) - { - let thread_id = data_view_reader.GetUInt32(); - let thread_name_hash = data_view_reader.GetUInt32(); - let sample_time = data_view_reader.GetUInt64(); - - // Add frame history for this processor - let processor_name = "Processor " + i.toString(); - if (!(processor_name in self.ProcessorFrameHistory)) - { - self.ProcessorFrameHistory[processor_name] = [ ]; - } - let frame_history = self.ProcessorFrameHistory[processor_name]; - - if (thread_id == 0xFFFFFFFF) - { - continue; - } - - // Try to merge this frame's samples with the previous frame if the are the same thread - if (frame_history.length > 0) - { - let last_thread_frame = frame_history[frame_history.length - 1]; - if (last_thread_frame.threadId == thread_id && last_thread_frame.messageIndex == message_index - 1) - { - // Update last frame message index so that the next frame can check for continuity - last_thread_frame.messageIndex = message_index; - - // Sum time elapsed on the previous frame - const us_length = sample_time - last_thread_frame.usLastStart; - last_thread_frame.usLastStart = sample_time; - last_thread_frame.EndTime_us += us_length; - const last_length = last_thread_frame.sampleDataView.getFloat32(g_sampleOffsetBytes_Length, true); - last_thread_frame.sampleDataView.setFloat32(g_sampleOffsetBytes_Length, last_length + us_length / 1000.0, true); - - continue; - } - } - - // Discard old frames to keep memory-use constant - var max_nb_frames = 10000; - var extra_frames = frame_history.length - max_nb_frames; - if (extra_frames > 0) - { - frame_history.splice(0, extra_frames); - } - - // Lookup the thread name - let [ name_exists, thread_name ] = self.glCanvas.nameMap.Get(thread_name_hash); - - // If the name doesn't exist in the map yet, request it from the server - if (!name_exists) - { - if (self.Server.Connected()) - { - self.Server.Send("GSMP" + thread_name_hash); - } - } - - // We are co-opting the sample rendering functionality of the timeline window to display processor threads as - // thread samples. Fabricate a thread frame message, packing the processor info into one root sample. - // TODO(don): Abstract the timeline window for pure range display as this is quite inefficient. - let thread_message = { }; - thread_message.nb_samples = 1; - thread_message.sampleData = new ArrayBuffer(g_nbBytesPerSample); - thread_message.sampleDataView = new DataView(thread_message.sampleData); - const sample_data_view = thread_message.sampleDataView; - - // Set the name - const text_entry = thread_name.textEntry != null ? thread_name.textEntry : empty_text_entry; - sample_data_view.setFloat32(g_sampleOffsetBytes_NameOffset, text_entry.offset, true); - sample_data_view.setFloat32(g_sampleOffsetBytes_NameLength, text_entry.length, true); - - // Make a pastel-y colour from the thread name hash - const hash = thread_name.hash; - sample_data_view.setUint8(g_sampleOffsetBytes_Colour + 0, 127 + (hash & 255) / 2); - sample_data_view.setUint8(g_sampleOffsetBytes_Colour + 1, 127 + ((hash >> 4) & 255) / 2); - sample_data_view.setUint8(g_sampleOffsetBytes_Colour + 2, 127 + ((hash >> 8) & 255) / 2); - - // Set the time - sample_data_view.setFloat32(g_sampleOffsetBytes_Start, sample_time / 1000.0, true); - sample_data_view.setFloat32(g_sampleOffsetBytes_Length, 0.25, true); - - thread_message.sampleFloats = new Float32Array(thread_message.sampleData, 0, thread_message.nb_samples * g_nbFloatsPerSample); - - // Create a thread frame and annotate with data required to merge processor samples - let thread_frame = new ThreadFrame(thread_message); - thread_frame.threadId = thread_id; - thread_frame.messageIndex = message_index; - thread_frame.usLastStart = sample_time; - frame_history.push(thread_frame); - - if (self.Server.Connected()) - { - self.ProcessorTimelineWindow.OnSamples(processor_name, frame_history); - } - } - } - - - function UInt64ToFloat32(view, offset) - { - // Unpack as two 32-bit integers so we have a vague attempt at reconstructing the value - const a = view.getUint32(offset + 0, true); - const b = view.getUint32(offset + 4, true); - - // Can't do bit arithmetic above 32-bits in JS so combine using power-of-two math - const v = a + (b * Math.pow(2, 32)); - - // TODO(don): Potentially massive data loss! - snapshots_view.setFloat32(offset, v); - } - - - function SInt64ToFloat32(view, offset) - { - // Unpack as two 32-bit integers so we have a vague attempt at reconstructing the value - const a = view.getUint32(offset + 0, true); - const b = view.getUint32(offset + 4, true); - - // Is this negative? - if (b & 0x80000000) - { - // Can only convert from twos-complement with 32-bit arithmetic so shave off the upper 32-bits - // TODO(don): Crazy data loss here - const v = -(~(a - 1)); - } - else - { - // Can't do bit arithmetic above 32-bits in JS so combine using power-of-two math - const v = a + (b * Math.pow(2, 32)); - } - - // TODO(don): Potentially massive data loss! - snapshots_view.setFloat32(offset, v); - } - - - function DecodeSnapshotHeader(self, data_view_reader, length) - { - // Message-specific header - let message = { }; - message.messageStart = data_view_reader.Offset; - message.nbSnapshots = data_view_reader.GetUInt32(); - message.propertyFrame = data_view_reader.GetUInt32(); - message.snapshotsStart = data_view_reader.Offset; - message.snapshotsLength = length - (message.snapshotsStart - message.messageStart); - return message; - } - - - function ProcessSnapshots(self, snapshot_data, message) - { - if (self.Settings.IsPaused) - { - return null; - } - - const empty_text_entry = { - offset: 0, - length: 1, - }; - - const snapshots_length = message.nbSnapshots * g_nbBytesPerSnapshot; - const snapshots_view = new DataView(snapshot_data, message.snapshotsStart, snapshots_length); - - for (let offset = 0; offset < snapshots_length; offset += g_nbBytesPerSnapshot) - { - // Get name hash and lookup in name map - const name_hash = snapshots_view.getUint32(offset, true); - const [ name_exists, name ] = self.glCanvas.nameMap.Get(name_hash); - - // If the name doesn't exist in the map yet, request it from the server - if (!name_exists) - { - if (self.Server.Connected()) - { - self.Server.Send("GSMP" + name_hash); - } - } - - // Add snapshot name text buffer location - const text_entry = name.textEntry != null ? name.textEntry : empty_text_entry; - snapshots_view.setFloat32(offset + 0, text_entry.offset, true); - snapshots_view.setFloat32(offset + 4, text_entry.length, true); - - // Heat colour style falloff to quickly identify modified properties - let r = 255, g = 255, b = 255; - const prev_value_frame = snapshots_view.getUint32(offset + 32, true); - const frame_delta = message.propertyFrame - prev_value_frame; - if (frame_delta < 64) - { - g = Math.min(Math.min(frame_delta, 32) * 8, 255); - b = Math.min(frame_delta * 4, 255); - } - snapshots_view.setUint8(offset + 8, r); - snapshots_view.setUint8(offset + 9, g); - snapshots_view.setUint8(offset + 10, b); - - const snapshot_type = snapshots_view.getUint32(offset + 12, true); - switch (snapshot_type) - { - case 1: - case 2: - case 3: - case 4: - case 7: - snapshots_view.setFloat32(offset + 16, snapshots_view.getFloat64(offset + 16, true), true); - snapshots_view.setFloat32(offset + 24, snapshots_view.getFloat64(offset + 24, true), true); - break; - - // Unpack 64-bit integers stored full precision in the logs and view them to the best of our current abilities - case 5: - SInt64ToFloat32(snapshots_view, offset + 16); - SInt64ToFloat32(snapshots_view, offset + 24); - case 6: - UInt64ToFloat32(snapshots_view, offset + 16); - UInt64ToFloat32(snapshots_view, offset + 24); - break; - } - } - - // Convert to floats for GPU - return new Float32Array(snapshot_data, message.snapshotsStart, message.nbSnapshots * g_nbFloatsPerSnapshot); - } - - - function OnPropertySnapshots(self, socket, data_view_reader, length) - { - // Discard any new snapshots while paused and connected - // Otherwise this stops a paused Remotery from loading new samples from disk - if (self.Settings.IsPaused && self.Server.Connected()) - return; - - // Binary decode incoming snapshot data - const message = DecodeSnapshotHeader(self, data_view_reader, length); - message.snapshotFloats = ProcessSnapshots(self, data_view_reader.DataView.buffer, message); - - // Add to frame history - const thread_frame = new PropertySnapshotFrame(message); - const frame_history = self.PropertyFrameHistory; - frame_history.push(thread_frame); - - // Discard old frames to keep memory-use constant - var max_nb_frames = 10000; - var extra_frames = frame_history.length - max_nb_frames; - if (extra_frames > 0) - frame_history.splice(0, extra_frames); - - // Set on the window if connected as this implies a trace is being loaded, which we want to speed up - if (self.Server.Connected()) - { - self.propertyGridWindow.UpdateEntries(message.nbSnapshots, message.snapshotFloats); - } - } - - function OnTimelineCheck(self, name, evt) - { - // Show/hide the equivalent sample window and move all the others to occupy any left-over space - var target = DOM.Event.GetNode(evt); - self.gridWindows[name].SetVisible(target.checked); - MoveGridWindows(self); - } - - - function MoveGridWindows(self) - { - // Stack all windows next to each other - let xpos = 0; - for (let i in self.gridWindows) - { - const grid_window = self.gridWindows[i]; - if (grid_window.visible) - { - grid_window.SetXPos(xpos++, self.SampleTimelineWindow.Window, self.Console.Window); - } - } - } - - - function OnSampleHover(self, thread_name, hover) - { - if (!self.Settings.IsPaused) - { - return; - } - - // Search for the grid window for the thread being hovered over - for (let window_thread_name in self.gridWindows) - { - if (window_thread_name == thread_name) - { - const grid_window = self.gridWindows[thread_name]; - - // Populate with the sample under hover - if (hover != null) - { - const frame = hover[0]; - grid_window.UpdateEntries(frame.NbSamples, frame.sampleFloats); - } - - // When there's no hover, go back to the selected frame - else if (self.SelectedFrames[thread_name]) - { - const frame = self.SelectedFrames[thread_name]; - grid_window.UpdateEntries(frame.NbSamples, frame.sampleFloats); - } - - // Otherwise display the last sample in the frame - else - { - const frames = self.FrameHistory[thread_name]; - const frame = frames[frames.length - 1]; - grid_window.UpdateEntries(frame.NbSamples, frame.sampleFloats); - } - - break; - } - } - } - - - function OnSampleSelected(self, thread_name, select) - { - // Lookup sample window - if (thread_name in self.gridWindows) - { - const grid_window = self.gridWindows[thread_name]; - - // Set the grid window to the selected frame if valid - if (select) - { - const frame = select[0]; - self.SelectedFrames[thread_name] = frame; - grid_window.UpdateEntries(frame.NbSamples, frame.sampleFloats); - } - - // Otherwise deselect - else - { - const frames = self.FrameHistory[thread_name]; - const frame = frames[frames.length - 1]; - self.SelectedFrames[thread_name] = null; - grid_window.UpdateEntries(frame.NbSamples, frame.sampleFloats); - self.SampleTimelineWindow.Deselect(thread_name); - } - } - } - - - function OnResizeWindow(self) - { - var w = window.innerWidth; - var h = window.innerHeight; - - // Resize windows - self.Console.WindowResized(w, h); - self.TitleWindow.WindowResized(w, h); - self.SampleTimelineWindow.WindowResized(10, w / 2 - 5, self.TitleWindow.Window); - self.ProcessorTimelineWindow.WindowResized(w / 2 + 5, w / 2 - 5, self.TitleWindow.Window); - for (var i in self.gridWindows) - { - self.gridWindows[i].WindowResized(self.SampleTimelineWindow.Window, self.Console.Window); - } - } - - - function OnTimelineMoved(self, timeline) - { - if (self.Settings.SyncTimelines) - { - let other_timeline = timeline == self.ProcessorTimelineWindow ? self.SampleTimelineWindow : self.ProcessorTimelineWindow; - other_timeline.SetTimeRange(timeline.TimeRange.Start_us, timeline.TimeRange.Span_us); - } - } - - return Remotery; -})(); \ No newline at end of file diff --git a/profiler/vis/Code/SampleGlobals.js b/profiler/vis/Code/SampleGlobals.js deleted file mode 100644 index d74c88b..0000000 --- a/profiler/vis/Code/SampleGlobals.js +++ /dev/null @@ -1,28 +0,0 @@ - -// Sample properties when viewed as an array of floats -const g_nbFloatsPerSample = 14; -const g_sampleOffsetFloats_NameOffset = 0; -const g_sampleOffsetFloats_NameLength = 1; -const g_sampleOffsetFloats_Start = 3; -const g_sampleOffsetFloats_Length = 5; -const g_sampleOffsetFloats_Self = 7; -const g_sampleOffsetFloats_GpuToCpu = 9; -const g_sampleOffsetFloats_Calls = 11; -const g_sampleOffsetFloats_Recurse = 12; - -// Sample properties when viewed as bytes -const g_nbBytesPerSample = g_nbFloatsPerSample * 4; -const g_sampleOffsetBytes_NameOffset = g_sampleOffsetFloats_NameOffset * 4; -const g_sampleOffsetBytes_NameLength = g_sampleOffsetFloats_NameLength * 4; -const g_sampleOffsetBytes_Colour = 8; -const g_sampleOffsetBytes_Depth = 11; -const g_sampleOffsetBytes_Start = g_sampleOffsetFloats_Start * 4; -const g_sampleOffsetBytes_Length = g_sampleOffsetFloats_Length * 4; -const g_sampleOffsetBytes_Self = g_sampleOffsetFloats_Self * 4; -const g_sampleOffsetBytes_GpuToCpu = g_sampleOffsetFloats_GpuToCpu * 4; -const g_sampleOffsetBytes_Calls = g_sampleOffsetFloats_Calls * 4; -const g_sampleOffsetBytes_Recurse = g_sampleOffsetFloats_Recurse * 4; - -// Snapshot properties -const g_nbFloatsPerSnapshot = 10; -const g_nbBytesPerSnapshot = g_nbFloatsPerSnapshot * 4; \ No newline at end of file diff --git a/profiler/vis/Code/Shaders/Grid.glsl b/profiler/vis/Code/Shaders/Grid.glsl deleted file mode 100644 index eb1e1ff..0000000 --- a/profiler/vis/Code/Shaders/Grid.glsl +++ /dev/null @@ -1,162 +0,0 @@ -const GridShaderShared = ShaderShared + ` - -#define RowHeight 15.0 - -struct Grid -{ - float minX; - float minY; - float maxX; - float maxY; - float pixelOffsetX; - float pixelOffsetY; -}; - -uniform Viewport inViewport; -uniform Grid inGrid; - -float Row(vec2 pixel_position) -{ - return floor(pixel_position.y / RowHeight); -} - -vec3 RowColour(float row) -{ - float row_grey = (int(row) & 1) == 0 ? 0.25 : 0.23; - return vec3(row_grey); -} - -`; - -// ------------------------------------------------------------------------------------------------------------------------------- -// Vertex Shader -// ------------------------------------------------------------------------------------------------------------------------------- - -const GridVShader = GridShaderShared + ` - -out vec2 varPixelPosition; - -void main() -{ - vec2 position = QuadPosition(gl_VertexID, inGrid.minX, inGrid.minY, inGrid.maxX, inGrid.maxY); - vec4 ndc_pos = UVToNDC(inViewport, position); - - gl_Position = ndc_pos; - varPixelPosition = position - vec2(inGrid.minX, inGrid.minY) + vec2(inGrid.pixelOffsetX, inGrid.pixelOffsetY); -} -`; - -const GridNumberVShader = GridShaderShared + ` - -out vec2 varPixelPosition; - -void main() -{ - vec2 position = QuadPosition(gl_VertexID, inGrid.minX, inGrid.minY, inGrid.maxX, inGrid.maxY); - vec4 ndc_pos = UVToNDC(inViewport, position); - - gl_Position = ndc_pos; - varPixelPosition = position - vec2(inGrid.minX, inGrid.minY) + vec2(inGrid.pixelOffsetX, inGrid.pixelOffsetY); -} -`; - -// ------------------------------------------------------------------------------------------------------------------------------- -// Fragment Shader -// ------------------------------------------------------------------------------------------------------------------------------- - -const GridFShader = GridShaderShared + ` - -// Array of samples -uniform sampler2D inSamples; -uniform float inSamplesLength; -uniform float inFloatsPerSample; -uniform float inNbSamples; - -in vec2 varPixelPosition; - -out vec4 outColour; - -void main() -{ - // Font description - float font_width_px = inTextBufferDesc.fontWidth; - float font_height_px = inTextBufferDesc.fontHeight; - float text_buffer_length = inTextBufferDesc.textBufferLength; - - // Which row are we on? - float row = Row(varPixelPosition); - vec3 row_colour = RowColour(row); - - float text_weight = 0.0; - vec3 text_colour = vec3(0.0); - if (row < inNbSamples) - { - // Unpack colour and depth - int colour_depth = floatBitsToInt(TextureBufferLookup(inSamples, row * inFloatsPerSample + 2.0, inSamplesLength).r); - text_colour.r = float(colour_depth & 255) / 255.0; - text_colour.g = float((colour_depth >> 8) & 255) / 255.0; - text_colour.b = float((colour_depth >> 16) & 255) / 255.0; - float depth = float(colour_depth >> 24); - - float text_buffer_offset = TextureBufferLookup(inSamples, row * inFloatsPerSample + 0.0, inSamplesLength).r; - float text_length_chars = TextureBufferLookup(inSamples, row * inFloatsPerSample + 1.0, inSamplesLength).r; - float text_length_px = text_length_chars * font_width_px; - - // Pixel position within the row - vec2 pos_in_box_px; - pos_in_box_px.x = varPixelPosition.x; - pos_in_box_px.y = varPixelPosition.y - row * RowHeight; - - // Get text at this position - vec2 text_start_px = vec2(4.0 + depth * 10.0, 3.0); - text_weight = LookupText(pos_in_box_px - text_start_px, text_buffer_offset, text_length_chars); - } - - outColour = vec4(mix(row_colour, text_colour, text_weight), 1.0); -} -`; - -const GridNumberFShader = GridShaderShared + ` - -// Array of samples -uniform sampler2D inSamples; -uniform float inSamplesLength; -uniform float inFloatsPerSample; -uniform float inNbSamples; - -// Offset within the sample -uniform float inNumberOffset; - -uniform float inNbFloatChars; - -in vec2 varPixelPosition; - -out vec4 outColour; - -void main() -{ - // Font description - float font_width_px = inTextBufferDesc.fontWidth; - float font_height_px = inTextBufferDesc.fontHeight; - float text_buffer_length = inTextBufferDesc.textBufferLength; - - // Which row are we on? - float row = Row(varPixelPosition); - vec3 row_colour = RowColour(row); - float text_weight = 0.0; - if (row < inNbSamples) - { - // Pixel position within the row - vec2 pos_in_box_px; - pos_in_box_px.x = varPixelPosition.x; - pos_in_box_px.y = varPixelPosition.y - row * RowHeight; - - // Get the number at this pixel - const vec2 text_start_px = vec2(4.0, 3.0); - float number = TextureBufferLookup(inSamples, row * inFloatsPerSample + inNumberOffset, inSamplesLength).r; - text_weight = LookupNumber(pos_in_box_px - text_start_px, number, inNbFloatChars); - } - - outColour = vec4(mix(row_colour, vec3(1.0), text_weight), 1.0); -} -`; diff --git a/profiler/vis/Code/Shaders/Shared.glsl b/profiler/vis/Code/Shaders/Shared.glsl deleted file mode 100644 index 94daf96..0000000 --- a/profiler/vis/Code/Shaders/Shared.glsl +++ /dev/null @@ -1,154 +0,0 @@ -const ShaderShared = `#version 300 es - -precision mediump float; - -struct Viewport -{ - float width; - float height; -}; - -vec2 QuadPosition(int vertex_id, float min_x, float min_y, float max_x, float max_y) -{ - // Quad indices are: - // - // 2 3 - // +----+ - // | | - // +----+ - // 0 1 - // - vec2 position; - position.x = (vertex_id & 1) == 0 ? min_x : max_x; - position.y = (vertex_id & 2) == 0 ? min_y : max_y; - return position; -} - -vec4 UVToNDC(Viewport viewport, vec2 uv) -{ - // - // NDC is: - // -1 to 1, left to right - // -1 to 1, bottom to top - // - vec4 ndc_pos; - ndc_pos.x = (uv.x / viewport.width) * 2.0 - 1.0; - ndc_pos.y = 1.0 - (uv.y / viewport.height) * 2.0; - ndc_pos.z = 0.0; - ndc_pos.w = 1.0; - return ndc_pos; -} - -vec4 TextureBufferLookup(sampler2D sampler, float index, float length) -{ - vec2 uv = vec2((index + 0.5) / length, 0.5); - return texture(sampler, uv); -} - -struct TextBufferDesc -{ - float fontWidth; - float fontHeight; - float textBufferLength; -}; - -uniform sampler2D inFontAtlasTexture; -uniform sampler2D inTextBuffer; -uniform TextBufferDesc inTextBufferDesc; - -float LookupCharacter(float char_ascii, float pos_x, float pos_y, float font_width_px, float font_height_px) -{ - // 2D index of the ASCII character in the font atlas - float char_index_y = floor(char_ascii / 16.0); - float char_index_x = char_ascii - char_index_y * 16.0; - - // Start UV of the character in the font atlas - float char_base_uv_x = char_index_x / 16.0; - float char_base_uv_y = char_index_y / 16.0; - - // UV within the character itself, scaled to the font atlas - float char_uv_x = pos_x / (font_width_px * 16.0); - float char_uv_y = pos_y / (font_height_px * 16.0); - - vec2 uv; - uv.x = char_base_uv_x + char_uv_x; - uv.y = char_base_uv_y + char_uv_y; - - // Strip colour and return alpha only - return texture(inFontAtlasTexture, uv).a; -} - -float LookupText(vec2 render_pos_px, float text_buffer_offset, float text_length_chars) -{ - // Font description - float font_width_px = inTextBufferDesc.fontWidth; - float font_height_px = inTextBufferDesc.fontHeight; - float text_buffer_length = inTextBufferDesc.textBufferLength; - float text_length_px = text_length_chars * font_width_px; - - // Text pixel position clamped to the bounds of the full word, allowing leakage to neighbouring NULL characters to pad zeroes - vec2 text_pixel_pos; - text_pixel_pos.x = max(min(render_pos_px.x, text_length_px), -1.0); - text_pixel_pos.y = max(min(render_pos_px.y, font_height_px - 1.0), 0.0); - - // Index of the current character in the text buffer - float text_index = text_buffer_offset + floor(text_pixel_pos.x / font_width_px); - - // Sample the 1D text buffer to get the ASCII character index - float char_ascii = TextureBufferLookup(inTextBuffer, text_index, text_buffer_length).a * 255.0; - - return LookupCharacter(char_ascii, - text_pixel_pos.x - (text_index - text_buffer_offset) * font_width_px, - text_pixel_pos.y, - font_width_px, font_height_px); -} - -float NbIntegerCharsForNumber(float number) -{ - float number_int = floor(number); - return number_int == 0.0 ? 1.0 : floor(log(number_int) / 2.302585092994046) + 1.0; -} - -// Base-10 lookup table for shifting digits of a float to the range 0-9 where they can be rendered -const float g_Multipliers[14] = float[14]( - // Decimal part multipliers - 1000.0, 100.0, 10.0, - // Zero entry for maintaining the ASCII "." base when rendering the period - 0.0, - // Integer part multipliers - 1.0, 0.1, 0.01, 0.001, 0.0001, 0.00001, 0.000001, 0.0000001, 0.00000001, 0.000000001 ); - -float LookupNumber(vec2 render_pos_px, float number, float nb_float_chars) -{ - // Font description - float font_width_px = inTextBufferDesc.fontWidth; - float font_height_px = inTextBufferDesc.fontHeight; - float text_buffer_length = inTextBufferDesc.textBufferLength; - - float number_integer_chars = NbIntegerCharsForNumber(number); - - // Clip - render_pos_px.y = max(min(render_pos_px.y, font_height_px - 1.0), 0.0); - - float number_index = floor(render_pos_px.x / font_width_px); - - if (number_index >= 0.0 && number_index < number_integer_chars + nb_float_chars) - { - // When we are indexing the period separating integer and decimal, set the base to ASCII "." - // The lookup table stores zero for this entry, multipying with the addend to produce no shift from this base - float base = (number_index == number_integer_chars) ? 46.0 : 48.0; - - // Calculate digit using the current number index base-10 shift - float multiplier = g_Multipliers[int(number_integer_chars - number_index) + 3]; - float number_shifted_int = floor(number * multiplier); - float number_digit = floor(mod(number_shifted_int, 10.0)); - - return LookupCharacter(base + number_digit, - render_pos_px.x - number_index * font_width_px, - render_pos_px.y, - font_width_px, font_height_px); - } - - return 0.0; -} -`; diff --git a/profiler/vis/Code/Shaders/Timeline.glsl b/profiler/vis/Code/Shaders/Timeline.glsl deleted file mode 100644 index afb464f..0000000 --- a/profiler/vis/Code/Shaders/Timeline.glsl +++ /dev/null @@ -1,337 +0,0 @@ -const TimelineShaderShared = ShaderShared + ` - -#define SAMPLE_HEIGHT 16.0 -#define SAMPLE_BORDER 2.0 -#define SAMPLE_Y_SPACING (SAMPLE_HEIGHT + SAMPLE_BORDER * 2.0) - -#define PIXEL_ROUNDED_OFFSETS - -struct Container -{ - float x0; - float y0; - float x1; - float y1; -}; - -struct TimeRange -{ - float msStart; - float msPerPixel; -}; - -struct Row -{ - float yOffset; -}; - -uniform Viewport inViewport; -uniform TimeRange inTimeRange; -uniform Container inContainer; -uniform Row inRow; - -float PixelOffset(float time_ms) -{ - float offset = (time_ms - inTimeRange.msStart) * inTimeRange.msPerPixel; - #ifdef PIXEL_ROUNDED_OFFSETS - return floor(offset); - #else - return offset; - #endif -} - -float PixelSize(float time_ms) -{ - float size = time_ms * inTimeRange.msPerPixel; - #ifdef PIXEL_ROUNDED_OFFSETS - return floor(size); - #else - return size; - #endif -} - -vec4 SampleQuad(int vertex_id, vec4 in_sample_textoffset, float padding, out vec4 out_quad_pos_size_px) -{ - // Unpack input data - float ms_start = in_sample_textoffset.x; - float ms_length = in_sample_textoffset.y; - float depth = in_sample_textoffset.z; - - // Determine pixel range of the sample - float x0 = PixelOffset(ms_start); - float x1 = x0 + PixelSize(ms_length); - - // Calculate box to render - // Ensure no sample is less than one pixel in length and so is always visible - float offset_x = inContainer.x0 + x0 - padding; - float offset_y = inRow.yOffset + (depth - 1.0) * SAMPLE_Y_SPACING + SAMPLE_BORDER - padding; - float size_x = max(x1 - x0, 1.0) + padding * 2.0; - float size_y = SAMPLE_HEIGHT + padding * 2.0; - - // Box range clipped to container bounds - float min_x = min(max(offset_x, inContainer.x0), inContainer.x1); - float min_y = min(max(offset_y, inContainer.y0), inContainer.y1); - float max_x = min(max(offset_x + size_x, inContainer.x0), inContainer.x1); - float max_y = min(max(offset_y + size_y, inContainer.y0), inContainer.y1); - - // Box quad position in NDC - vec2 position = QuadPosition(vertex_id, min_x, min_y, max_x, max_y); - vec4 ndc_pos = UVToNDC(inViewport, position); - - out_quad_pos_size_px.xy = vec2(position.x - offset_x, position.y - offset_y); - out_quad_pos_size_px.zw = vec2(max_x - min_x, max_y - min_y); - - return ndc_pos; -} - -`; - -// ------------------------------------------------------------------------------------------------------------------------------- -// Sample Rendering -// ------------------------------------------------------------------------------------------------------------------------------- - -const TimelineVShader = TimelineShaderShared + ` - -in vec4 inSample_TextOffset; -in vec4 inColour_TextLength; - -out vec4 varColour_TimeMs; -out vec4 varPosInBoxPx_TextEntry; -out float varTimeChars; - -void main() -{ - // Unpack input data - float ms_length = inSample_TextOffset.y; - float text_buffer_offset = inSample_TextOffset.w; - vec3 box_colour = inColour_TextLength.rgb; - float text_length_chars = inColour_TextLength.w; - - // Calculate number of characters required to display the millisecond time - float time_chars = NbIntegerCharsForNumber(ms_length); - - // Calculate sample quad vertex positions - vec4 quad_pos_size_px; - gl_Position = SampleQuad(gl_VertexID, inSample_TextOffset, 0.0, quad_pos_size_px); - - // Pack for fragment shader - varColour_TimeMs = vec4(box_colour / 255.0, ms_length); - varPosInBoxPx_TextEntry = vec4(quad_pos_size_px.x, quad_pos_size_px.y, text_buffer_offset, text_length_chars); - varTimeChars = time_chars; -} -`; - -const TimelineFShader = TimelineShaderShared + ` - -in vec4 varColour_TimeMs; -in vec4 varPosInBoxPx_TextEntry; -in float varTimeChars; - -out vec4 outColour; - -void main() -{ - // Font description - float font_width_px = inTextBufferDesc.fontWidth; - float font_height_px = inTextBufferDesc.fontHeight; - - // Text range in the text buffer - vec2 pos_in_box_px = varPosInBoxPx_TextEntry.xy; - float text_buffer_offset = varPosInBoxPx_TextEntry.z; - float text_length_chars = varPosInBoxPx_TextEntry.w; - float text_length_px = text_length_chars * font_width_px; - - // Text placement offset within the box - const vec2 text_start_px = vec2(10.0, 3.0); - - vec3 box_colour = varColour_TimeMs.rgb; - - // Add a subtle border to the box so that you can visually separate samples when they are next to each other - vec2 top_left = min(pos_in_box_px.xy, 2.0); - float both = min(top_left.x, top_left.y); - box_colour *= (0.8 + both * 0.1); - - float text_weight = 0.0; - - // Are we over the time number or the text? - float text_end_px = text_start_px.x + text_length_px; - float number_start_px = text_end_px + font_width_px * 2.0; - if (pos_in_box_px.x > number_start_px) - { - vec2 time_pixel_pos; - time_pixel_pos.x = pos_in_box_px.x - number_start_px; - time_pixel_pos.y = max(min(pos_in_box_px.y - text_start_px.y, font_height_px - 1.0), 0.0); - - // Time number - float time_ms = varColour_TimeMs.w; - float time_index = floor(time_pixel_pos.x / font_width_px); - if (time_index < varTimeChars + 4.0) - { - text_weight = LookupNumber(time_pixel_pos, time_ms, 4.0); - } - - // " ms" label at the end of the time - else if (time_index < varTimeChars + 7.0) - { - const float ms[3] = float[3] ( 32.0, 109.0, 115.0 ); - float char = ms[int(time_index - (varTimeChars + 4.0))]; - text_weight = LookupCharacter(char, time_pixel_pos.x - time_index * font_width_px, time_pixel_pos.y, font_width_px, font_height_px); - } - } - else - { - text_weight = LookupText(pos_in_box_px - text_start_px, text_buffer_offset, text_length_chars); - } - - // Blend text onto the box - vec3 text_colour = vec3(0.0, 0.0, 0.0); - outColour = vec4(mix(box_colour, text_colour, text_weight), 1.0); -} -`; - -// ------------------------------------------------------------------------------------------------------------------------------- -// Sample Highlights -// ------------------------------------------------------------------------------------------------------------------------------- - -const TimelineHighlightVShader = TimelineShaderShared + ` - -uniform float inStartMs; -uniform float inLengthMs; -uniform float inDepth; - -out vec4 varPosSize; - -void main() -{ - // Calculate sample quad vertex positions - gl_Position = SampleQuad(gl_VertexID, vec4(inStartMs, inLengthMs, inDepth, 0.0), 1.0, varPosSize); -} -`; - -const TimelineHighlightFShader = TimelineShaderShared + ` - -// TODO(don): Vector uniforms, please! -uniform float inColourR; -uniform float inColourG; -uniform float inColourB; - -in vec4 varPosSize; - -out vec4 outColour; - -void main() -{ - // Rounded pixel co-ordinates interpolating across the sample - vec2 pos = floor(varPosSize.xy); - - // Sample size in pixel co-ordinates - vec2 size = floor(varPosSize.zw); - - // Highlight thickness - float t = 2.0; - - // Distance along axes to highlight edges - vec2 dmin = abs(pos - 0.0); - vec2 dmax = abs(pos - (size - 1.0)); - - // Take the closest distance - float dx = min(dmin.x, dmax.x); - float dy = min(dmin.y, dmax.y); - float d = min(dx, dy); - - // Invert the distance and clamp to thickness - d = (t + 1.0) - min(d, t + 1.0); - - // Scale with thickness for uniform intensity - d = d / (t + 1.0); - outColour = vec4(inColourR * d, inColourG * d, inColourB * d, d); -} -`; - -// ------------------------------------------------------------------------------------------------------------------------------- -// GPU->CPU Sample Sources -// ------------------------------------------------------------------------------------------------------------------------------- - -const TimelineGpuToCpuVShader = TimelineShaderShared + ` - -uniform float inStartMs; -uniform float inLengthMs; -uniform float inDepth; - -out vec4 varPosSize; - -void main() -{ - // Calculate sample quad vertex positions - gl_Position = SampleQuad(gl_VertexID, vec4(inStartMs, inLengthMs, inDepth, 0.0), 1.0, varPosSize); -} -`; - - -const TimelineGpuToCpuFShader = TimelineShaderShared + ` - -in vec4 varPosSize; - -out vec4 outColour; - -void main() -{ - // Rounded pixel co-ordinates interpolating across the sample - vec2 pos = floor(varPosSize.xy); - - // Sample size in pixel co-ordinates - vec2 size = floor(varPosSize.zw); - - // Distance to centre line, bumped out every period to create a dash - float dc = abs(pos.y - size.y / 2.0); - dc += (int(pos.x / 3.0) & 1) == 0 ? 100.0 : 0.0; - - // Min with the start line - float ds = abs(pos.x - 0.0); - float d = min(dc, ds); - - // Invert the distance for highlight - d = 1.0 - min(d, 1.0); - - outColour = vec4(d, d, d, d); -} - -`; - -// ------------------------------------------------------------------------------------------------------------------------------- -// Background -// ------------------------------------------------------------------------------------------------------------------------------- - -const TimelineBackgroundVShader = TimelineShaderShared + ` - -uniform float inYOffset; - -out vec2 varPosition; - -void main() -{ - - // Container quad position in NDC - vec2 position = QuadPosition(gl_VertexID, inContainer.x0, inContainer.y0, inContainer.x1, inContainer.y1); - gl_Position = UVToNDC(inViewport, position); - - // Offset Y with scroll position - varPosition = vec2(position.x, position.y - inYOffset); -} - -`; - -const TimelineBackgroundFShader = TimelineShaderShared + ` - -in vec2 varPosition; - -out vec4 outColour; - -void main() -{ - vec2 pos = floor(varPosition); - float f = round(fract(pos.y / SAMPLE_Y_SPACING) * SAMPLE_Y_SPACING); - float g = f >= 1.0 && f <= (SAMPLE_Y_SPACING - 2.0) ? 0.30 : 0.23; - outColour = vec4(g, g, g, 1.0); -} -`; \ No newline at end of file diff --git a/profiler/vis/Code/Shaders/Window.glsl b/profiler/vis/Code/Shaders/Window.glsl deleted file mode 100644 index 362772e..0000000 --- a/profiler/vis/Code/Shaders/Window.glsl +++ /dev/null @@ -1,33 +0,0 @@ -// ------------------------------------------------------------------------------------------------------------------------------- -// Vertex Shader -// ------------------------------------------------------------------------------------------------------------------------------- - -const WindowVShader = ShaderShared + ` - -uniform Viewport inViewport; - -uniform float minX; -uniform float minY; -uniform float maxX; -uniform float maxY; - -void main() -{ - vec2 position = QuadPosition(gl_VertexID, minX, minY, maxX, maxY); - gl_Position = UVToNDC(inViewport, position); -} -`; - -// ------------------------------------------------------------------------------------------------------------------------------- -// Fragment Shader -// ------------------------------------------------------------------------------------------------------------------------------- - -const WindowFShader = ShaderShared + ` - -out vec4 outColour; - -void main() -{ - outColour = vec4(1.0, 1.0, 1.0, 0.0); -} -`; diff --git a/profiler/vis/Code/ThreadFrame.js b/profiler/vis/Code/ThreadFrame.js deleted file mode 100644 index 3826205..0000000 --- a/profiler/vis/Code/ThreadFrame.js +++ /dev/null @@ -1,34 +0,0 @@ - - -class ThreadFrame -{ - constructor(message) - { - // Persist the required message data - this.NbSamples = message.nb_samples; - this.sampleDataView = message.sampleDataView; - this.sampleFloats = message.sampleFloats; - this.PartialTree = message.partial_tree > 0 ? true : false; - - // Point to the first/last samples - const first_sample_start_us = this.sampleFloats[g_sampleOffsetFloats_Start] * 1000.0; - const last_sample_offset = (this.NbSamples - 1) * g_nbFloatsPerSample; - const last_sample_start_us = this.sampleFloats[last_sample_offset + g_sampleOffsetFloats_Start] * 1000.0; - const last_sample_length_us = this.sampleFloats[last_sample_offset + g_sampleOffsetFloats_Length] * 1000.0; - - // Calculate the frame start/end times - this.StartTime_us = first_sample_start_us; - this.EndTime_us = last_sample_start_us + last_sample_length_us; - this.Length_us = this.EndTime_us - this.StartTime_us; - } -} - -class PropertySnapshotFrame -{ - constructor(message) - { - this.nbSnapshots = message.nbSnapshots; - this.snapshots = message.snapshots; - this.snapshotFloats = message.snapshotFloats; - } -} \ No newline at end of file diff --git a/profiler/vis/Code/TimelineMarkers.js b/profiler/vis/Code/TimelineMarkers.js deleted file mode 100644 index 435f4fb..0000000 --- a/profiler/vis/Code/TimelineMarkers.js +++ /dev/null @@ -1,186 +0,0 @@ - -function GetTimeText(seconds) -{ - if (seconds < 0) - { - return ""; - } - - var text = ""; - - // Add any contributing hours - var h = Math.floor(seconds / 3600); - seconds -= h * 3600; - if (h) - { - text += h + "h "; - } - - // Add any contributing minutes - var m = Math.floor(seconds / 60); - seconds -= m * 60; - if (m) - { - text += m + "m "; - } - - // Add any contributing seconds or always add seconds when hours or minutes have no contribution - // This ensures the 0s marker displays - var s = Math.floor(seconds); - seconds -= s; - if (s || text == "") - { - text += s + "s "; - } - - // Add remaining milliseconds - var ms = Math.floor(seconds * 1000); - if (ms) - { - text += ms + "ms"; - } - - return text; -} - - -class TimelineMarkers -{ - constructor(timeline) - { - this.timeline = timeline; - - // Need a 2D drawing context - this.markerContainer = timeline.Window.AddControlNew(new WM.Container(10, 10, 10, 10)); - this.markerCanvas = document.createElement("canvas"); - this.markerContainer.Node.appendChild(this.markerCanvas); - this.markerContext = this.markerCanvas.getContext("2d"); - } - - Draw(time_range) - { - let ctx = this.markerContext; - - ctx.clearRect(0, 0, this.markerCanvas.width, this.markerCanvas.height); - - // Setup render state for the time line markers - ctx.strokeStyle = "#BBB"; - ctx.fillStyle = "#BBB"; - ctx.lineWidth = 1; - ctx.font = "9px LocalFiraCode"; - - // A list of all supported units of time (measured in seconds) that require markers - let units = [ 0.001, 0.01, 0.1, 1, 10, 60, 60 * 5, 60 * 60, 60 * 60 * 24 ]; - - // Given the current pixel size of a second, calculate the spacing for each unit marker - let second_pixel_size = time_range.PixelSize(1000 * 1000); - let sizeof_units = [ ]; - for (let unit of units) - { - sizeof_units.push(unit * second_pixel_size); - } - - // Calculate whether each unit marker is visible at the current zoom level - var show_unit = [ ]; - for (let sizeof_unit of sizeof_units) - { - show_unit.push(Math.max(Math.min((sizeof_unit - 4) * 0.25, 1), 0)); - } - - // Find the first visible unit - for (let i = 0; i < units.length; i++) - { - if (show_unit[i] > 0) - { - // Cut out unit information for the first set of units not visible - units = units.slice(i); - sizeof_units = sizeof_units.slice(i); - show_unit = show_unit.slice(i); - break; - } - } - - let timeline_end = this.markerCanvas.width; - for (let i = 0; i < 3; i++) - { - // Round the start time up to the next visible unit - let time_start = time_range.Start_us / (1000 * 1000); - let unit_time_start = Math.ceil(time_start / units[i]) * units[i]; - - // Calculate the canvas offset required to step to the first visible unit - let pre_step_x = time_range.PixelOffset(unit_time_start * (1000 * 1000)); - - // Draw lines for every unit at this level, keeping tracking of the seconds - var seconds = unit_time_start; - for (let x = pre_step_x; x <= timeline_end; x += sizeof_units[i]) - { - // For the first two units, don't draw the units above it to prevent - // overdraw and the visual errors that causes - // The last unit always draws - if (i > 1 || (seconds % units[i + 1])) - { - // Only the first two units scale with unit visibility - // The last unit maintains its size - let height = Math.min(i * 4 + 4 * show_unit[i], 16); - - // Draw the line on an integer boundary, shifted by 0.5 to get an un-anti-aliased 1px line - let ix = Math.floor(x); - ctx.beginPath(); - ctx.moveTo(ix + 0.5, 1); - ctx.lineTo(ix + 0.5, 1 + height); - ctx.stroke(); - } - - seconds += units[i]; - } - - if (i == 1) - { - // Draw text labels for the second unit, fading them out as they slowly - // become the first unit - ctx.globalAlpha = show_unit[0]; - var seconds = unit_time_start; - for (let x = pre_step_x; x <= timeline_end; x += sizeof_units[i]) - { - if (seconds % units[2]) - { - this.DrawTimeText(seconds, x, 16); - } - seconds += units[i]; - } - - // Restore alpha - ctx.globalAlpha = 1; - } - - else if (i == 2) - { - // Draw text labels for the third unit with no fade - var seconds = unit_time_start; - for (let x = pre_step_x; x <= timeline_end; x += sizeof_units[i]) - { - this.DrawTimeText(seconds, x, 16); - seconds += units[i]; - } - } - } - } - - DrawTimeText(seconds, x, y) - { - // Use text measuring to centre the text horizontally on the input x - var text = GetTimeText(seconds); - var width = this.markerContext.measureText(text).width; - this.markerContext.fillText(text, Math.floor(x) - width / 2, y); - } - - Resize(x, y, w, h) - { - this.markerContainer.SetPosition(x, y); - this.markerContainer.SetSize(w, h); - - // Match canvas size to container - this.markerCanvas.width = this.markerContainer.Node.clientWidth; - this.markerCanvas.height = this.markerContainer.Node.clientHeight; - } -} \ No newline at end of file diff --git a/profiler/vis/Code/TimelineRow.js b/profiler/vis/Code/TimelineRow.js deleted file mode 100644 index 60a54a7..0000000 --- a/profiler/vis/Code/TimelineRow.js +++ /dev/null @@ -1,400 +0,0 @@ - - -TimelineRow = (function() -{ - const RowLabelTemplate = ` -
-
- -
-
-
+
-
-
-
-
-
-
-
-
` - - - var SAMPLE_HEIGHT = 16; - var SAMPLE_BORDER = 2; - var SAMPLE_Y_SPACING = SAMPLE_HEIGHT + SAMPLE_BORDER * 2; - - - function TimelineRow(gl, name, timeline, frame_history, check_handler) - { - this.Name = name; - this.timeline = timeline; - - // Create the row HTML and add to the parent - this.LabelContainerNode = DOM.Node.CreateHTML(RowLabelTemplate); - const label_node = DOM.Node.FindWithClass(this.LabelContainerNode, "TimelineRowLabel"); - label_node.innerHTML = name; - timeline.TimelineLabels.Node.appendChild(this.LabelContainerNode); - - // All sample view windows visible by default - const checkbox_node = DOM.Node.FindWithClass(this.LabelContainerNode, "TimelineRowCheckbox"); - checkbox_node.checked = true; - checkbox_node.addEventListener("change", (e) => check_handler(name, e)); - - // Manually hook-up events to simulate div:active - // I can't get the equivalent CSS to work in Firefox, so... - const expand_node_0 = DOM.Node.FindWithClass(this.LabelContainerNode, "TimelineRowExpand", 0); - const expand_node_1 = DOM.Node.FindWithClass(this.LabelContainerNode, "TimelineRowExpand", 1); - const inc_node = DOM.Node.FindWithClass(expand_node_0, "TimelineRowExpandButton"); - const dec_node = DOM.Node.FindWithClass(expand_node_1, "TimelineRowExpandButton"); - inc_node.addEventListener("mousedown", ExpandButtonDown); - inc_node.addEventListener("mouseup", ExpandButtonUp); - inc_node.addEventListener("mouseleave", ExpandButtonUp); - dec_node.addEventListener("mousedown", ExpandButtonDown); - dec_node.addEventListener("mouseup", ExpandButtonUp); - dec_node.addEventListener("mouseleave", ExpandButtonUp); - - // Pressing +/i increases/decreases depth - inc_node.addEventListener("click", () => this.IncDepth()); - dec_node.addEventListener("click", () => this.DecDepth()); - - // Frame index to start at when looking for first visible sample - this.StartFrameIndex = 0; - - this.FrameHistory = frame_history; - this.VisibleFrames = [ ]; - this.VisibleTimeRange = null; - this.Depth = 1; - - // Currently selected sample - this.SelectSampleInfo = null; - - // Create WebGL sample buffers - this.sampleBuffer = new glDynamicBuffer(gl, glDynamicBufferType.Buffer, gl.FLOAT, 4); - this.colourBuffer = new glDynamicBuffer(gl, glDynamicBufferType.Buffer, gl.UNSIGNED_BYTE, 4); - - // An initial SetSize call to restore containers to their original size after traces were loaded prior to this - this.SetSize(); - } - - - TimelineRow.prototype.SetSize = function() - { - this.LabelContainerNode.style.height = SAMPLE_Y_SPACING * this.Depth; - } - - - TimelineRow.prototype.SetVisibleFrames = function(time_range) - { - // Clear previous visible list - this.VisibleFrames = [ ]; - if (this.FrameHistory.length == 0) - return; - - // Store a copy of the visible time range rather than referencing it - // This prevents external modifications to the time range from affecting rendering/selection - time_range = time_range.Clone(); - this.VisibleTimeRange = time_range; - - // The frame history can be reset outside this class - // This also catches the overflow to the end of the frame list below when a thread stops sending samples - var max_frame = Math.max(this.FrameHistory.length - 1, 0); - var start_frame_index = Math.min(this.StartFrameIndex, max_frame); - - // First do a back-track in case the time range moves negatively - while (start_frame_index > 0) - { - var frame = this.FrameHistory[start_frame_index]; - if (time_range.Start_us > frame.StartTime_us) - break; - start_frame_index--; - } - - // Then search from this point for the first visible frame - while (start_frame_index < this.FrameHistory.length) - { - var frame = this.FrameHistory[start_frame_index]; - if (frame.EndTime_us > time_range.Start_us) - break; - start_frame_index++; - } - - // Gather all frames up to the end point - this.StartFrameIndex = start_frame_index; - for (var i = start_frame_index; i < this.FrameHistory.length; i++) - { - var frame = this.FrameHistory[i]; - if (frame.StartTime_us > time_range.End_us) - break; - this.VisibleFrames.push(frame); - } - } - - - TimelineRow.prototype.DrawSampleHighlight = function(gl_canvas, container, frame, offset, depth, selected) - { - if (depth <= this.Depth) - { - const gl = gl_canvas.gl; - const program = gl_canvas.timelineHighlightProgram; - - gl_canvas.SetContainerUniforms(program, container); - - // Set row parameters - const row_rect = this.LabelContainerNode.getBoundingClientRect(); - glSetUniform(gl, program, "inRow.yOffset", row_rect.top); - - // Set sample parameters - const float_offset = offset / 4; - glSetUniform(gl, program, "inStartMs", frame.sampleFloats[float_offset + g_sampleOffsetFloats_Start]); - glSetUniform(gl, program, "inLengthMs", frame.sampleFloats[float_offset + g_sampleOffsetFloats_Length]); - glSetUniform(gl, program, "inDepth", depth); - - // Set colour - glSetUniform(gl, program, "inColourR", 1.0); - glSetUniform(gl, program, "inColourG", selected ? 0.0 : 1.0); - glSetUniform(gl, program, "inColourB", selected ? 0.0 : 1.0); - - gl_canvas.EnableBlendPremulAlpha(); - gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); - gl_canvas.DisableBlend(); - } - } - - - TimelineRow.prototype.DrawSampleGpuToCpu = function(gl_canvas, container, frame, offset, depth) - { - // Is this a GPU sample? - const float_offset = offset / 4; - const start_ms = frame.sampleFloats[float_offset + g_sampleOffsetFloats_GpuToCpu]; - if (start_ms > 0) - { - const gl = gl_canvas.gl; - const program = gl_canvas.timelineGpuToCpuProgram; - - gl_canvas.SetContainerUniforms(program, container); - - // Set row parameters - const row_rect = this.LabelContainerNode.getBoundingClientRect(); - glSetUniform(gl, program, "inRow.yOffset", row_rect.top); - - // Set sample parameters - const length_ms = frame.sampleFloats[float_offset + g_sampleOffsetFloats_Start] - start_ms; - glSetUniform(gl, program, "inStartMs", start_ms); - glSetUniform(gl, program, "inLengthMs", length_ms); - glSetUniform(gl, program, "inDepth", depth); - - gl_canvas.EnableBlendPremulAlpha(); - gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); - gl_canvas.DisableBlend(); - } - } - - - TimelineRow.prototype.DisplayHeight = function() - { - return this.LabelContainerNode.clientHeight; - } - - - TimelineRow.prototype.YOffset = function() - { - return this.LabelContainerNode.offsetTop; - } - - - function GatherSamples(self, frame, samples_per_depth) - { - const sample_data_view = frame.sampleDataView; - - for (let offset = 0; offset < sample_data_view.byteLength; offset += g_nbBytesPerSample) - { - depth = sample_data_view.getUint8(offset + g_sampleOffsetBytes_Depth) + 1; - if (depth > self.Depth) - { - continue; - } - - // Ensure there's enough entries for each depth - while (depth >= samples_per_depth.length) - { - samples_per_depth.push([]); - } - - let samples_this_depth = samples_per_depth[depth]; - samples_this_depth.push([frame, offset]); - } - } - - - TimelineRow.prototype.Draw = function(gl_canvas, container) - { - let samples_per_depth = []; - - // Gather all sample data in the visible frame set - for (var i in this.VisibleFrames) - { - var frame = this.VisibleFrames[i]; - GatherSamples(this, frame, samples_per_depth); - } - - // Count number of samples required - let nb_samples = 0; - for (const samples_this_depth of samples_per_depth) - { - nb_samples += samples_this_depth.length; - } - - // Resize buffers to match any new count of samples - const gl = gl_canvas.gl; - const program = gl_canvas.timelineProgram; - if (nb_samples > this.sampleBuffer.nbEntries) - { - this.sampleBuffer.ResizeToFitNextPow2(nb_samples); - this.colourBuffer.ResizeToFitNextPow2(nb_samples); - - // Have to create a new VAO for these buffers - this.vertexArrayObject = gl.createVertexArray(); - gl.bindVertexArray(this.vertexArrayObject); - this.sampleBuffer.BindAsInstanceAttribute(program, "inSample_TextOffset"); - this.colourBuffer.BindAsInstanceAttribute(program, "inColour_TextLength"); - } - - // CPU write destination for samples - let cpu_samples = this.sampleBuffer.cpuArray; - let cpu_colours = this.colourBuffer.cpuArray; - let sample_pos = 0; - - // TODO(don): Pack offsets into the sample buffer, instead? - // Puts all samples together into one growing buffer (will need ring buffer management). - // Offset points into that. - // Remains to be seen how much of this can be done given the limitations of WebGL2... - - // Copy samples to the CPU buffer - // TODO(don): Use a ring buffer instead and take advantage of timeline scrolling adding new samples at the beginning/end - for (let depth = 0; depth < samples_per_depth.length; depth++) - { - let samples_this_depth = samples_per_depth[depth]; - for (const [frame, offset] of samples_this_depth) - { - const float_offset = offset / 4; - - cpu_samples[sample_pos + 0] = frame.sampleFloats[float_offset + g_sampleOffsetFloats_Start]; - cpu_samples[sample_pos + 1] = frame.sampleFloats[float_offset + g_sampleOffsetFloats_Length]; - cpu_samples[sample_pos + 2] = depth; - cpu_samples[sample_pos + 3] = frame.sampleFloats[float_offset + g_sampleOffsetFloats_NameOffset]; - - cpu_colours[sample_pos + 0] = frame.sampleDataView.getUint8(offset + g_sampleOffsetBytes_Colour + 0); - cpu_colours[sample_pos + 1] = frame.sampleDataView.getUint8(offset + g_sampleOffsetBytes_Colour + 1); - cpu_colours[sample_pos + 2] = frame.sampleDataView.getUint8(offset + g_sampleOffsetBytes_Colour + 2); - cpu_colours[sample_pos + 3] = frame.sampleFloats[float_offset + g_sampleOffsetFloats_NameLength]; - - sample_pos += 4; - } - } - - // Upload to GPU - this.sampleBuffer.UploadData(); - this.colourBuffer.UploadData(); - - gl_canvas.SetContainerUniforms(program, container); - - // Set row parameters - const row_rect = this.LabelContainerNode.getBoundingClientRect(); - glSetUniform(gl, program, "inRow.yOffset", row_rect.top); - - gl.bindVertexArray(this.vertexArrayObject); - gl.drawArraysInstanced(gl.TRIANGLE_STRIP, 0, 4, nb_samples); - } - - - TimelineRow.prototype.SetSelectSample = function(sample_info) - { - this.SelectSampleInfo = sample_info; - } - - - function ExpandButtonDown(evt) - { - var node = DOM.Event.GetNode(evt); - DOM.Node.AddClass(node, "TimelineRowExpandButtonActive"); - } - - - function ExpandButtonUp(evt) - { - var node = DOM.Event.GetNode(evt); - DOM.Node.RemoveClass(node, "TimelineRowExpandButtonActive"); - } - - - TimelineRow.prototype.IncDepth = function() - { - this.Depth++; - this.SetSize(); - } - - - TimelineRow.prototype.DecDepth = function() - { - if (this.Depth > 1) - { - this.Depth--; - this.SetSize(); - - // Trigger scroll handling to ensure reducing the depth reduces the display height - this.timeline.MoveVertically(0); - } - } - - - TimelineRow.prototype.GetSampleAtPosition = function(time_us, mouse_y) - { - // Calculate depth of the mouse cursor - const depth = Math.min(Math.floor(mouse_y / SAMPLE_Y_SPACING) + 1, this.Depth); - - // Search for the first frame to intersect this time - for (let i in this.VisibleFrames) - { - // Use the sample's closed interval to detect hits. - // Rendering of samples ensures a sample is never smaller than one pixel so that all samples always draw, irrespective - // of zoom level. If a half-open interval is used then some visible samples will be unselectable due to them being - // smaller than a pixel. This feels pretty odd and the closed interval fixes this feeling well. - // TODO(don): There are still inconsistencies, need to shift to pixel range checking to match exactly. - const frame = this.VisibleFrames[i]; - if (time_us >= frame.StartTime_us && time_us <= frame.EndTime_us) - { - const found_sample = FindSample(this, frame, time_us, depth, 1); - if (found_sample != null) - { - return [ frame, found_sample[0], found_sample[1], this ]; - } - } - } - - return null; - } - - - function FindSample(self, frame, time_us, target_depth, depth) - { - // Search entire frame of samples looking for a depth and time range that contains the input time - const sample_data_view = frame.sampleDataView; - for (let offset = 0; offset < sample_data_view.byteLength; offset += g_nbBytesPerSample) - { - depth = sample_data_view.getUint8(offset + g_sampleOffsetBytes_Depth) + 1; - if (depth == target_depth) - { - const us_start = sample_data_view.getFloat32(offset + g_sampleOffsetBytes_Start, true) * 1000.0; - const us_length = sample_data_view.getFloat32(offset + g_sampleOffsetBytes_Length, true) * 1000.0; - if (time_us >= us_start && time_us < us_start + us_length) - { - return [ offset, depth ]; - } - } - } - - return null; - } - - - return TimelineRow; -})(); diff --git a/profiler/vis/Code/TimelineWindow.js b/profiler/vis/Code/TimelineWindow.js deleted file mode 100644 index 7a15f6b..0000000 --- a/profiler/vis/Code/TimelineWindow.js +++ /dev/null @@ -1,496 +0,0 @@ - -// TODO(don): Separate all knowledge of threads from this timeline - -TimelineWindow = (function() -{ - var BORDER = 10; - - function TimelineWindow(wm, name, settings, check_handler, gl_canvas) - { - this.Settings = settings; - this.glCanvas = gl_canvas; - - // Create timeline window - this.Window = wm.AddWindow("Timeline", 10, 20, 100, 100, null, this); - this.Window.SetTitle(name); - this.Window.ShowNoAnim(); - - this.timelineMarkers = new TimelineMarkers(this); - - // DO THESE need to be containers... can they just be divs? - // divs need a retrieval function - this.TimelineLabelScrollClipper = this.Window.AddControlNew(new WM.Container(10, 10, 10, 10)); - DOM.Node.AddClass(this.TimelineLabelScrollClipper.Node, "TimelineLabelScrollClipper"); - this.TimelineLabels = this.TimelineLabelScrollClipper.AddControlNew(new WM.Container(0, 0, 10, 10)); - DOM.Node.AddClass(this.TimelineLabels.Node, "TimelineLabels"); - - // Ordered list of thread rows on the timeline - this.ThreadRows = [ ]; - - // Create timeline container - this.TimelineContainer = this.Window.AddControlNew(new WM.Container(10, 10, 800, 160)); - DOM.Node.AddClass(this.TimelineContainer.Node, "TimelineContainer"); - - // Setup mouse interaction - this.mouseInteraction = new MouseInteraction(this.TimelineContainer.Node); - this.mouseInteraction.onClickHandler = (mouse_state) => OnMouseClick(this, mouse_state); - this.mouseInteraction.onMoveHandler = (mouse_state, mx, my) => OnMouseMove(this, mouse_state, mx, my); - this.mouseInteraction.onHoverHandler = (mouse_state) => OnMouseHover(this, mouse_state); - this.mouseInteraction.onScrollHandler = (mouse_state) => OnMouseScroll(this, mouse_state); - - // Allow user to click on the thread name to deselect any threads as finding empty space may be difficult - DOM.Event.AddHandler(this.TimelineLabels.Node, "mousedown", (evt) => OnLabelMouseDown(this, evt)); - - this.Window.SetOnResize(Bind(OnUserResize, this)); - - this.Clear(); - - this.OnHoverHandler = null; - this.OnSelectedHandler = null; - this.OnMovedHandler = null; - this.CheckHandler = check_handler; - - this.yScrollOffset = 0; - - this.HoverSampleInfo = null; - this.lastHoverThreadName = null; - } - - - TimelineWindow.prototype.Clear = function() - { - // Clear out labels - this.TimelineLabels.ClearControls(); - - this.ThreadRows = [ ]; - this.TimeRange = new PixelTimeRange(0, 200 * 1000, this.TimelineContainer.Node.clientWidth); - } - - - TimelineWindow.prototype.SetOnHover = function(handler) - { - this.OnHoverHandler = handler; - } - - - TimelineWindow.prototype.SetOnSelected = function(handler) - { - this.OnSelectedHandler = handler; - } - - - TimelineWindow.prototype.SetOnMoved = function(handler) - { - this.OnMovedHandler = handler; - } - - - TimelineWindow.prototype.WindowResized = function(x, width, top_window) - { - // Resize window - var top = top_window.Position[1] + top_window.Size[1] + 10; - this.Window.SetPosition(x, top); - this.Window.SetSize(width - 2 * 10, 260); - - ResizeInternals(this); - } - - - TimelineWindow.prototype.OnSamples = function(thread_name, frame_history) - { - // Shift the timeline to the last entry on this thread - var last_frame = frame_history[frame_history.length - 1]; - this.TimeRange.SetEnd(last_frame.EndTime_us); - - // Search for the index of this thread - var thread_index = -1; - for (var i in this.ThreadRows) - { - if (this.ThreadRows[i].Name == thread_name) - { - thread_index = i; - break; - } - } - - // If this thread has not been seen before, add a new row to the list - if (thread_index == -1) - { - var row = new TimelineRow(this.glCanvas.gl, thread_name, this, frame_history, this.CheckHandler); - this.ThreadRows.push(row); - - // Sort thread rows in the collection by name - this.ThreadRows.sort((a, b) => a.Name.localeCompare(b.Name)); - - // Resort the view by removing timeline row nodes from their DOM parents and re-adding - const thread_rows = new Array(); - for (let thread_row of this.ThreadRows) - { - this.TimelineLabels.Node.removeChild(thread_row.LabelContainerNode); - thread_rows.push(thread_row); - } - for (let thread_row of thread_rows) - { - this.TimelineLabels.Node.appendChild(thread_row.LabelContainerNode); - } - } - } - - - TimelineWindow.prototype.DrawBackground = function() - { - const gl = this.glCanvas.gl; - const program = this.glCanvas.timelineBackgroundProgram; - gl.useProgram(program); - - // Set viewport parameters - glSetUniform(gl, program, "inViewport.width", gl.canvas.width); - glSetUniform(gl, program, "inViewport.height", gl.canvas.height); - - this.glCanvas.SetContainerUniforms(program, this.TimelineContainer.Node); - - // Set row parameters - const row_rect = this.TimelineLabels.Node.getBoundingClientRect(); - glSetUniform(gl, program, "inYOffset", row_rect.top); - - gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); - - this.timelineMarkers.Draw(this.TimeRange); - } - - - TimelineWindow.prototype.Deselect = function(thread_name) - { - for (let thread_row of this.ThreadRows) - { - if (thread_name == thread_row.Name) - { - thread_row.SelectSampleInfo = null; - } - } - } - - - TimelineWindow.prototype.DrawSampleHighlights = function() - { - const gl = this.glCanvas.gl; - const program = this.glCanvas.timelineHighlightProgram; - gl.useProgram(program); - - // Set viewport parameters - glSetUniform(gl, program, "inViewport.width", gl.canvas.width); - glSetUniform(gl, program, "inViewport.height", gl.canvas.height); - - // Set time range parameters - const time_range = this.TimeRange; - time_range.SetAsUniform(gl, program); - - for (let thread_row of this.ThreadRows) - { - // Draw highlight for hover row - if (this.HoverSampleInfo != null && this.HoverSampleInfo[3] == thread_row) - { - const frame = this.HoverSampleInfo[0]; - const offset = this.HoverSampleInfo[1]; - const depth = this.HoverSampleInfo[2]; - thread_row.DrawSampleHighlight(this.glCanvas, this.TimelineContainer.Node, frame, offset, depth, false); - } - - // Draw highlight for any samples selected on this row - if (thread_row.SelectSampleInfo != null) - { - const frame = thread_row.SelectSampleInfo[0]; - const offset = thread_row.SelectSampleInfo[1]; - const depth = thread_row.SelectSampleInfo[2]; - thread_row.DrawSampleHighlight(this.glCanvas, this.TimelineContainer.Node, frame, offset, depth, true); - } - } - } - - - TimelineWindow.prototype.DrawSampleGpuToCpu = function() - { - const gl = this.glCanvas.gl; - const program = this.glCanvas.timelineGpuToCpuProgram; - gl.useProgram(program); - - // Set viewport parameters - glSetUniform(gl, program, "inViewport.width", gl.canvas.width); - glSetUniform(gl, program, "inViewport.height", gl.canvas.height); - - // Set time range parameters - const time_range = this.TimeRange; - time_range.SetAsUniform(gl, program); - - // Draw pointer for hover rows - for (let thread_row of this.ThreadRows) - { - if (this.HoverSampleInfo != null && this.HoverSampleInfo[3] == thread_row) - { - const frame = this.HoverSampleInfo[0]; - const offset = this.HoverSampleInfo[1]; - const depth = this.HoverSampleInfo[2]; - thread_row.DrawSampleGpuToCpu(this.glCanvas, this.TimelineContainer.Node, frame, offset, depth); - } - } - } - - - TimelineWindow.prototype.Draw = function() - { - this.DrawBackground(); - - const gl = this.glCanvas.gl; - const program = this.glCanvas.timelineProgram; - gl.useProgram(program); - - // Set viewport parameters - glSetUniform(gl, program, "inViewport.width", gl.canvas.width); - glSetUniform(gl, program, "inViewport.height", gl.canvas.height); - - // Set time range parameters - const time_range = this.TimeRange; - time_range.SetAsUniform(gl, program); - - this.glCanvas.SetTextUniforms(program); - - for (let i in this.ThreadRows) - { - var thread_row = this.ThreadRows[i]; - thread_row.SetVisibleFrames(time_range); - thread_row.Draw(this.glCanvas, this.TimelineContainer.Node); - } - - this.DrawSampleHighlights(); - this.DrawSampleGpuToCpu(); - } - - - function OnUserResize(self, evt) - { - ResizeInternals(self); - } - - function ResizeInternals(self) - { - // .TimelineRowLabel - // .TimelineRowExpand - // .TimelineRowExpand - // .TimelineRowCheck - // Window padding - let offset_x = 145+19+19+19+10; - - let MarkersHeight = 18; - - var parent_size = self.Window.Size; - - self.timelineMarkers.Resize(BORDER + offset_x, 10, parent_size[0] - 2* BORDER - offset_x, MarkersHeight); - - // Resize controls - self.TimelineContainer.SetPosition(BORDER + offset_x, 10 + MarkersHeight); - self.TimelineContainer.SetSize(parent_size[0] - 2 * BORDER - offset_x, parent_size[1] - MarkersHeight - 40); - - self.TimelineLabelScrollClipper.SetPosition(10, 10 + MarkersHeight); - self.TimelineLabelScrollClipper.SetSize(offset_x, parent_size[1] - MarkersHeight - 40); - self.TimelineLabels.SetSize(offset_x, parent_size[1] - MarkersHeight - 40); - - // Adjust time range to new width - const width = self.TimelineContainer.Node.clientWidth; - self.TimeRange.SetPixelSpan(width); - } - - - function OnMouseScroll(self, mouse_state) - { - let scale = 1.11; - if (mouse_state.WheelDelta > 0) - scale = 1 / scale; - - // What time is the mouse hovering over? - let mouse_pos = self.TimelineMousePosition(mouse_state); - let time_us = self.TimeRange.TimeAtPosition(mouse_pos[0]); - - // Calculate start time relative to the mouse hover position - var time_start_us = self.TimeRange.Start_us - time_us; - - // Scale and offset back to the hover time - self.TimeRange.Set(time_start_us * scale + time_us, self.TimeRange.Span_us * scale); - - if (self.OnMovedHandler) - { - self.OnMovedHandler(self); - } - } - - - TimelineWindow.prototype.SetTimeRange = function(start_us, span_us) - { - this.TimeRange.Set(start_us, span_us); - } - - - TimelineWindow.prototype.DisplayHeight = function() - { - // Sum height of each thread row - let height = 0; - for (thread_row of this.ThreadRows) - { - height += thread_row.DisplayHeight(); - } - - return height; - } - - - TimelineWindow.prototype.MoveVertically = function(y_scroll) - { - // Calculate the minimum negative value the position of the labels can be to account for scrolling to the bottom - // of the label/depth list - let display_height = this.DisplayHeight(); - let container_height = this.TimelineLabelScrollClipper.Node.clientHeight; - let minimum_y = Math.min(container_height - display_height, 0.0); - - // Resize the label container to match the display height - this.TimelineLabels.Node.style.height = Math.max(display_height, container_height); - - // Increment the y-scroll using just-calculated limits - let old_y_scroll_offset = this.yScrollOffset; - this.yScrollOffset = Math.min(Math.max(this.yScrollOffset + y_scroll, minimum_y), 0); - - // Calculate how much the labels should actually scroll after limiting and apply - let y_scroll_px = this.yScrollOffset - old_y_scroll_offset; - this.TimelineLabels.Node.style.top = this.TimelineLabels.Node.offsetTop + y_scroll_px; - } - - - TimelineWindow.prototype.TimelineMousePosition = function(mouse_state) - { - // Position of the mouse relative to the timeline container - let node_offset = DOM.Node.GetPosition(this.TimelineContainer.Node); - let mouse_x = mouse_state.Position[0] - node_offset[0]; - let mouse_y = mouse_state.Position[1] - node_offset[1]; - - // Offset by the amount of scroll - mouse_y -= this.yScrollOffset; - - return [ mouse_x, mouse_y ]; - } - - - TimelineWindow.prototype.GetHoverThreadRow = function(mouse_pos) - { - // Search for the thread row the mouse intersects - let height = 0; - for (let thread_row of this.ThreadRows) - { - let row_height = thread_row.DisplayHeight(); - if (mouse_pos[1] >= height && mouse_pos[1] < height + row_height) - { - // Mouse y relative to row start - mouse_pos[1] -= height; - return thread_row; - } - height += row_height; - } - - return null; - } - - - function OnMouseClick(self, mouse_state) - { - // Are we hovering over a thread row? - const mouse_pos = self.TimelineMousePosition(mouse_state); - const hover_thread_row = self.GetHoverThreadRow(mouse_pos); - if (hover_thread_row != null) - { - // Are we hovering over a sample? - const time_us = self.TimeRange.TimeAtPosition(mouse_pos[0]); - const sample_info = hover_thread_row.GetSampleAtPosition(time_us, mouse_pos[1]); - if (sample_info != null) - { - // Toggle deselect if this sample is already selected - if (hover_thread_row.SelectSampleInfo != null && - sample_info[0] == hover_thread_row.SelectSampleInfo[0] && sample_info[1] == hover_thread_row.SelectSampleInfo[1] && - sample_info[2] == hover_thread_row.SelectSampleInfo[2] && sample_info[3] == hover_thread_row.SelectSampleInfo[3]) - { - hover_thread_row.SetSelectSample(null); - self.OnSelectedHandler?.(hover_thread_row.Name, null); - } - - // Otherwise select - else - { - hover_thread_row.SetSelectSample(sample_info); - self.OnSelectedHandler?.(hover_thread_row.Name, sample_info); - } - } - - // Deselect if not hovering over a sample - else - { - self.OnSelectedHandler?.(hover_thread_row.Name, null); - } - } - } - - - function OnLabelMouseDown(self, evt) - { - // Deselect sample on this thread - const mouse_state = new Mouse.State(evt); - let mouse_pos = self.TimelineMousePosition(mouse_state); - const thread_row = self.GetHoverThreadRow(mouse_pos); - self.OnSelectedHandler?.(thread_row.Name, null); - } - - - function OnMouseMove(self, mouse_state, move_offset_x, move_offset_y) - { - // Shift the visible time range with mouse movement - const time_offset_us = move_offset_x / self.TimeRange.usPerPixel; - self.TimeRange.SetStart(self.TimeRange.Start_us - time_offset_us); - - // Control vertical movement - self.MoveVertically(move_offset_y); - - // Notify - self.OnMovedHandler?.(self); - } - - - function OnMouseHover(self, mouse_state) - { - // Check for hover ending - if (mouse_state == null) - { - self.OnHoverHandler?.(self.lastHoverThreadName, null); - return; - } - - // Are we hovering over a thread row? - const mouse_pos = self.TimelineMousePosition(mouse_state); - const hover_thread_row = self.GetHoverThreadRow(mouse_pos); - if (hover_thread_row != null) - { - // Are we hovering over a sample? - const time_us = self.TimeRange.TimeAtPosition(mouse_pos[0]); - self.HoverSampleInfo = hover_thread_row.GetSampleAtPosition(time_us, mouse_pos[1]); - - // Exit hover for the last hover row - self.OnHoverHandler?.(self.lastHoverThreadName, null); - self.lastHoverThreadName = hover_thread_row.Name; - - // Tell listeners which sample we're hovering over - self.OnHoverHandler?.(hover_thread_row.Name, self.HoverSampleInfo); - } - else - { - self.HoverSampleInfo = null; - } - } - - - return TimelineWindow; -})(); - diff --git a/profiler/vis/Code/TitleWindow.js b/profiler/vis/Code/TitleWindow.js deleted file mode 100644 index 01ac721..0000000 --- a/profiler/vis/Code/TitleWindow.js +++ /dev/null @@ -1,105 +0,0 @@ - -TitleWindow = (function() -{ - function TitleWindow(wm, settings, server, connection_address) - { - this.Settings = settings; - - this.Window = wm.AddWindow("     Remotery", 10, 10, 100, 100); - this.Window.ShowNoAnim(); - - this.PingContainer = this.Window.AddControlNew(new WM.Container(4, -13, 10, 10)); - DOM.Node.AddClass(this.PingContainer.Node, "PingContainer"); - - this.EditBox = this.Window.AddControlNew(new WM.EditBox(10, 5, 300, 18, "Connection Address", connection_address)); - - // Setup pause button - this.PauseButton = this.Window.AddControlNew(new WM.Button("Pause", 5, 5, { toggle: true })); - this.PauseButton.SetOnClick(Bind(OnPausePressed, this)); - - this.SyncButton = this.Window.AddControlNew(new WM.Button("Sync Timelines", 5, 5, { toggle: true})); - this.SyncButton.SetOnClick(Bind(OnSyncPressed, this)); - this.SyncButton.SetState(this.Settings.SyncTimelines); - - server.AddMessageHandler("PING", Bind(OnPing, this)); - - this.Window.SetOnResize(Bind(OnUserResize, this)); - } - - - TitleWindow.prototype.SetConnectionAddressChanged = function(handler) - { - this.EditBox.SetChangeHandler(handler); - } - - - TitleWindow.prototype.WindowResized = function(width, height) - { - this.Window.SetSize(width - 2 * 10, 50); - ResizeInternals(this); - } - - TitleWindow.prototype.Pause = function() - { - if (!this.Settings.IsPaused) - { - this.PauseButton.SetText("Paused"); - this.PauseButton.SetState(true); - this.Settings.IsPaused = true; - } - } - - TitleWindow.prototype.Unpause = function() - { - if (this.Settings.IsPaused) - { - this.PauseButton.SetText("Pause"); - this.PauseButton.SetState(false); - this.Settings.IsPaused = false; - } - } - - function OnUserResize(self, evt) - { - ResizeInternals(self); - } - - function ResizeInternals(self) - { - self.PauseButton.SetPosition(self.Window.Size[0] - 60, 5); - self.SyncButton.SetPosition(self.Window.Size[0] - 155, 5); - } - - - function OnPausePressed(self) - { - if (self.PauseButton.IsPressed()) - { - self.Pause(); - } - else - { - self.Unpause(); - } - } - - - function OnSyncPressed(self) - { - self.Settings.SyncTimelines = self.SyncButton.IsPressed(); - } - - - function OnPing(self, server) - { - // Set the ping container as active and take it off half a second later - DOM.Node.AddClass(self.PingContainer.Node, "PingContainerActive"); - window.setTimeout(Bind(function(self) - { - DOM.Node.RemoveClass(self.PingContainer.Node, "PingContainerActive"); - }, self), 500); - } - - - return TitleWindow; -})(); \ No newline at end of file diff --git a/profiler/vis/Code/TraceDrop.js b/profiler/vis/Code/TraceDrop.js deleted file mode 100644 index d33c323..0000000 --- a/profiler/vis/Code/TraceDrop.js +++ /dev/null @@ -1,147 +0,0 @@ - -class TraceDrop -{ - constructor(remotery) - { - this.Remotery = remotery; - - // Create a full-page overlay div for dropping files onto - this.DropNode = DOM.Node.CreateHTML("
Load Remotery Trace
"); - document.body.appendChild(this.DropNode); - - // Attach drop handlers - window.addEventListener("dragenter", () => this.ShowDropZone()); - this.DropNode.addEventListener("dragenter", (e) => this.AllowDrag(e)); - this.DropNode.addEventListener("dragover", (e) => this.AllowDrag(e)); - this.DropNode.addEventListener("dragleave", () => this.HideDropZone()); - this.DropNode.addEventListener("drop", (e) => this.OnDrop(e)); - } - - ShowDropZone() - { - this.DropNode.style.display = "flex"; - } - - HideDropZone() - { - this.DropNode.style.display = "none"; - } - - AllowDrag(evt) - { - // Prevent the default drag handler kicking in - evt.preventDefault(); - - evt.dataTransfer.dropEffect = "copy"; - } - - OnDrop(evt) - { - // Prevent the default drop handler kicking in - evt.preventDefault(); - - this.HideDropZone(evt); - - // Get the file that was dropped - let files = DOM.Event.GetDropFiles(evt); - if (files.length == 0) - { - alert("No files dropped"); - return; - } - if (files.length > 1) - { - alert("Too many files dropped"); - return; - } - - // Check file type - let file = files[0]; - if (!file.name.endsWith(".rbin")) - { - alert("Not the correct .rbin file type"); - return; - } - - // Background-load the file - var remotery = this.Remotery; - let file_reader = new FileReader(); - file_reader.onload = function() - { - // Create the data reader and verify the header - let data_view = new DataView(this.result); - let data_view_reader = new DataViewReader(data_view, 0); - let header = data_view_reader.GetStringOfLength(8); - if (header != "RMTBLOGF") - { - alert("Not a valid Remotery Log File"); - return; - } - - remotery.Clear(); - - try - { - // Forward all recorded events to message handlers - while (!data_view_reader.AtEnd()) - { - const start_offset = data_view_reader.Offset; - const [id, length ] = remotery.Server.CallMessageHandlers(data_view_reader, this.Result); - data_view_reader.Offset = start_offset + length; - } - } - catch (e) - { - // The last message may be partially written due to process exit - // Catch this safely as it's a valid state for the file to be in - if (e instanceof RangeError) - { - console.log("Aborted reading last message"); - } - } - - // After loading completes, populate the UI which wasn't updated during loading - - remotery.Console.TriggerUpdate(); - - // Set frame history for each timeline thread - for (let name in remotery.FrameHistory) - { - let frame_history = remotery.FrameHistory[name]; - remotery.SampleTimelineWindow.OnSamples(name, frame_history); - } - - // Set frame history for each processor - for (let name in remotery.ProcessorFrameHistory) - { - let frame_history = remotery.ProcessorFrameHistory[name]; - remotery.ProcessorTimelineWindow.OnSamples(name, frame_history); - } - - // Set the last frame values for each grid window - for (let name in remotery.gridWindows) - { - const grid_window = remotery.gridWindows[name]; - - const frame_history = remotery.FrameHistory[name]; - if (frame_history) - { - // This is a sample window - const frame = frame_history[frame_history.length - 1]; - grid_window.UpdateEntries(frame.NbSamples, frame.sampleFloats); - } - else - { - // This is a property window - const frame_history = remotery.PropertyFrameHistory; - const frame = frame_history[frame_history.length - 1]; - grid_window.UpdateEntries(frame.nbSnapshots, frame.snapshotFloats); - } - } - - // Pause for viewing - remotery.TitleWindow.Pause(); - }; - file_reader.readAsArrayBuffer(file); - } -} \ No newline at end of file diff --git a/profiler/vis/Code/WebGL.js b/profiler/vis/Code/WebGL.js deleted file mode 100644 index 1d90b6f..0000000 --- a/profiler/vis/Code/WebGL.js +++ /dev/null @@ -1,252 +0,0 @@ - -function assert(condition, message) -{ - if (!condition) - { - throw new Error(message || "Assertion failed"); - } -} - -function glCompileShader(gl, type, name, source) -{ - console.log("Compiling " + name); - - // Compile the shader - let shader = gl.createShader(type); - gl.shaderSource(shader, source); - gl.compileShader(shader); - - // Report any errors - if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) - { - console.log("Error compiling " + name); - console.log(gl.getShaderInfoLog(shader)); - console.trace(); - } - - return shader; -} - -function glCreateProgram(gl, vshader, fshader) -{ - // Attach shaders and link - let program = gl.createProgram(); - gl.attachShader(program, vshader); - gl.attachShader(program, fshader); - gl.linkProgram(program); - - // Report any errors - if (!gl.getProgramParameter(program, gl.LINK_STATUS)) - { - console.log("Failed to link program"); - console.trace(); - } - - return program; -} - -function glCreateProgramFromSource(gl, vshader_name, vshader_source, fshader_name, fshader_source) -{ - const vshader = glCompileShader(gl, gl.VERTEX_SHADER, vshader_name, vshader_source); - const fshader = glCompileShader(gl, gl.FRAGMENT_SHADER, fshader_name, fshader_source); - return glCreateProgram(gl, vshader, fshader); -} - -function glSetUniform(gl, program, name, value, index) -{ - // Get location - const location = gl.getUniformLocation(program, name); - assert(location != null, "Can't find uniform " + name); - - // Dispatch to uniform function by type - assert(value != null, "Value is null"); - const type = Object.prototype.toString.call(value).slice(8, -1); - switch (type) - { - case "Number": - gl.uniform1f(location, value); - break; - - case "WebGLTexture": - gl.activeTexture(gl.TEXTURE0 + index); - gl.bindTexture(gl.TEXTURE_2D, value); - gl.uniform1i(location, index); - break; - - default: - assert(false, "Unhandled type " + type); - break; - } -} - -function glCreateTexture(gl, width, height, data) -{ - const texture = gl.createTexture(); - - // Set filtering/wrapping to nearest/clamp - gl.bindTexture(gl.TEXTURE_2D, texture); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); - - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, data); - - return texture; -} - -const glDynamicBufferType = Object.freeze({ - Buffer: 1, - Texture: 2 -}); - -class glDynamicBuffer -{ - constructor(gl, buffer_type, element_type, nb_elements, nb_entries = 1) - { - this.gl = gl; - this.elementType = element_type; - this.nbElements = nb_elements; - this.bufferType = buffer_type; - this.dirty = false; - - this.Resize(nb_entries); - } - - BindAsInstanceAttribute(program, attrib_name) - { - assert(this.bufferType == glDynamicBufferType.Buffer, "Can only call BindAsInstanceAttribute with Buffer types"); - - let gl = this.gl; - - gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer); - - // The attribute referenced in the program - const attrib_location = gl.getAttribLocation(program, attrib_name); - - gl.enableVertexAttribArray(attrib_location); - gl.vertexAttribPointer(attrib_location, this.nbElements, this.elementType, false, 0, 0); - - // One per instance - gl.vertexAttribDivisor(attrib_location, 1); - } - - UploadData() - { - let gl = this.gl; - - switch (this.bufferType) - { - case glDynamicBufferType.Buffer: - gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer); - gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.cpuArray); - break; - - case glDynamicBufferType.Texture: - assert(this.elementType == gl.UNSIGNED_BYTE || this.elementType == gl.FLOAT); - gl.bindTexture(gl.TEXTURE_2D, this.texture); - - // Very limited map from internal type to texture type - let internal_format, format, type; - if (this.elementType == gl.UNSIGNED_BYTE) - { - internal_format = this.nbElements == 1 ? gl.ALPHA : gl.RGBA8; - format = this.nbElements == 1 ? gl.ALPHA : gl.RGBA; - type = gl.UNSIGNED_BYTE; - } - else if (this.elementType == gl.FLOAT) - { - internal_format = this.nbElements == 1 ? gl.R32F : RGBA32F; - format = this.nbElements == 1 ? gl.RED : gl.RGBA; - type = gl.FLOAT; - } - - gl.texImage2D(gl.TEXTURE_2D, 0, internal_format, this.nbEntries, 1, 0, format, type, this.cpuArray); - break; - } - } - - UploadDirtyData() - { - if (this.dirty) - { - this.UploadData(); - this.dirty = false; - } - } - - ResizeToFitNextPow2(target_count) - { - let nb_entries = this.nbEntries; - while (target_count > nb_entries) - { - nb_entries <<= 1; - } - - if (nb_entries > this.nbEntries) - { - this.Resize(nb_entries); - } - } - - Resize(nb_entries) - { - this.nbEntries = nb_entries; - - let gl = this.gl; - - // Create the CPU array - const old_array = this.cpuArray; - switch (this.elementType) - { - case gl.FLOAT: - this.nbElementBytes = 4; - this.cpuArray = new Float32Array(this.nbElements * this.nbEntries); - break; - - case gl.UNSIGNED_BYTE: - this.nbElementBytes = 1; - this.cpuArray = new Uint8Array(this.nbElements * this.nbEntries); - break; - - default: - assert(false, "Unsupported dynamic buffer element type"); - } - - // Calculate byte size of the buffer - this.nbBytes = this.nbElementBytes * this.nbElements * this.nbEntries; - - if (old_array != undefined) - { - // Copy the values of the previous array over - this.cpuArray.set(old_array); - } - - // Create the GPU buffer - switch (this.bufferType) - { - case glDynamicBufferType.Buffer: - this.buffer = gl.createBuffer(); - gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer); - gl.bufferData(gl.ARRAY_BUFFER, this.nbBytes, gl.DYNAMIC_DRAW); - break; - - case glDynamicBufferType.Texture: - this.texture = gl.createTexture(); - - // Point sampling with clamp for indexing - gl.bindTexture(gl.TEXTURE_2D, this.texture); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); - break; - - default: - assert(false, "Unsupported dynamic buffer type"); - } - - this.UploadData(); - } -}; - diff --git a/profiler/vis/Code/WebGLFont.js b/profiler/vis/Code/WebGLFont.js deleted file mode 100644 index be4b897..0000000 --- a/profiler/vis/Code/WebGLFont.js +++ /dev/null @@ -1,125 +0,0 @@ - -class glFont -{ - constructor(gl) - { - // Offscreen canvas for rendering individual characters - this.charCanvas = document.createElement("canvas"); - this.charContext = this.charCanvas.getContext("2d"); - - // Describe the font - const font_size = 9; - this.fontWidth = 5; - this.fontHeight = 13; - const font_face = "LocalFiraCode"; - const font_desc = font_size + "px " + font_face; - - // Ensure the CSS font is loaded before we do any work with it - const self = this; - document.fonts.load(font_desc).then(function (){ - - // Create a canvas atlas for all characters in the font - const atlas_canvas = document.createElement("canvas"); - const atlas_context = atlas_canvas.getContext("2d"); - atlas_canvas.width = 16 * self.fontWidth; - atlas_canvas.height = 16 * self.fontHeight; - - // Add each character to the atlas - const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-+=[]{};\'~#,./<>?!\"£$%%^&*()"; - for (let char of chars) - { - // Render this character to the canvas on its own - self.RenderTextToCanvas(char, font_desc, self.fontWidth, self.fontHeight); - - // Calculate a location for it in the atlas using its ASCII code - const ascii_code = char.charCodeAt(0); - assert(ascii_code < 256); - const y_index = Math.floor(ascii_code / 16); - const x_index = ascii_code - y_index * 16 - assert(x_index < 16); - assert(y_index < 16); - - // Copy into the atlas - atlas_context.drawImage(self.charCanvas, x_index * self.fontWidth, y_index * self.fontHeight); - } - - // Create the atlas texture and store it in the destination object - self.atlasTexture = glCreateTexture(gl, atlas_canvas.width, atlas_canvas.height, atlas_canvas); - }); - } - - RenderTextToCanvas(text, font, width, height) - { - // Resize canvas to match - this.charCanvas.width = width; - this.charCanvas.height = height; - - // Clear the background - this.charContext.fillStyle = "black"; - this.charContext.clearRect(0, 0, width, height); - - // TODO(don): I don't know why this results in the crispest text! - // Every pattern I've checked so far has thrown up no ideas... but it works, so it will do for now - let offset = 0.25; - if ("AFILMTWijmw4+{};\'#,.?!\"£*()".includes(text)) - { - offset = 0.0; - } - - // Render the text - this.charContext.font = font; - this.charContext.textAlign = "left"; - this.charContext.textBaseline = "top"; - this.charContext.fillText(text, offset, 2.5); - } -} - -class glTextBuffer -{ - constructor(gl, font) - { - this.font = font; - this.textMap = {}; - this.textBuffer = new glDynamicBuffer(gl, glDynamicBufferType.Texture, gl.UNSIGNED_BYTE, 1, 8); - this.textBufferPos = 0; - this.textEncoder = new TextEncoder(); - } - - AddText(text) - { - // Return if it already exists - const existing_entry = this.textMap[text]; - if (existing_entry != undefined) - { - return existing_entry; - } - - // Add to the map - // Note we're leaving an extra NULL character before every piece of text so that the shader can sample into it on text - // boundaries and sample a zero colour for clamp. - let entry = { - offset: this.textBufferPos + 1, - length: text.length, - }; - this.textMap[text] = entry; - - // Ensure there's always enough space in the text buffer before adding - this.textBuffer.ResizeToFitNextPow2(entry.offset + entry.length + 1); - this.textBuffer.cpuArray.set(this.textEncoder.encode(text), entry.offset, entry.length); - this.textBuffer.dirty = true; - this.textBufferPos = entry.offset + entry.length; - - return entry; - } - - UploadData() - { - this.textBuffer.UploadDirtyData(); - } - - SetAsUniform(gl, program, name, index) - { - glSetUniform(gl, program, name, this.textBuffer.texture, index); - glSetUniform(gl, program, "inTextBufferDesc.textBufferLength", this.textBuffer.nbEntries); - } -} \ No newline at end of file diff --git a/profiler/vis/Code/WebSocketConnection.js b/profiler/vis/Code/WebSocketConnection.js deleted file mode 100644 index 261fb17..0000000 --- a/profiler/vis/Code/WebSocketConnection.js +++ /dev/null @@ -1,149 +0,0 @@ - -WebSocketConnection = (function() -{ - function WebSocketConnection() - { - this.MessageHandlers = { }; - this.Socket = null; - this.Console = null; - } - - - WebSocketConnection.prototype.SetConsole = function(console) - { - this.Console = console; - } - - - WebSocketConnection.prototype.Connecting = function() - { - return this.Socket != null && this.Socket.readyState == WebSocket.CONNECTING; - } - - WebSocketConnection.prototype.Connected = function() - { - return this.Socket != null && this.Socket.readyState == WebSocket.OPEN; - } - - - WebSocketConnection.prototype.AddConnectHandler = function(handler) - { - this.AddMessageHandler("__OnConnect__", handler); - } - - - WebSocketConnection.prototype.AddDisconnectHandler = function(handler) - { - this.AddMessageHandler("__OnDisconnect__", handler); - } - - - WebSocketConnection.prototype.AddMessageHandler = function(message_name, handler) - { - // Create the message handler array on-demand - if (!(message_name in this.MessageHandlers)) - this.MessageHandlers[message_name] = [ ]; - this.MessageHandlers[message_name].push(handler); - } - - - WebSocketConnection.prototype.Connect = function(address) - { - // Abandon previous connection attempt - this.Disconnect(); - - Log(this, "Connecting to " + address); - - this.Socket = new WebSocket(address); - this.Socket.binaryType = "arraybuffer"; - this.Socket.onopen = Bind(OnOpen, this); - this.Socket.onmessage = Bind(OnMessage, this); - this.Socket.onclose = Bind(OnClose, this); - this.Socket.onerror = Bind(OnError, this); - } - - - WebSocketConnection.prototype.Disconnect = function() - { - Log(this, "Disconnecting"); - if (this.Socket != null) - { - this.Socket.close(); - this.Socket = null; - } - } - - - WebSocketConnection.prototype.Send = function(msg) - { - if (this.Connected()) - this.Socket.send(msg); - } - - - function Log(self, message) - { - self.Console.Log(message); - } - - - function CallMessageHandlers(self, message_name, data_view, length) - { - if (message_name in self.MessageHandlers) - { - var handlers = self.MessageHandlers[message_name]; - for (var i in handlers) - handlers[i](self, data_view, length); - } - } - - - function OnOpen(self, event) - { - Log(self, "Connected"); - CallMessageHandlers(self, "__OnConnect__"); - } - - - function OnClose(self, event) - { - // Clear all references - self.Socket.onopen = null; - self.Socket.onmessage = null; - self.Socket.onclose = null; - self.Socket.onerror = null; - self.Socket = null; - - Log(self, "Disconnected"); - CallMessageHandlers(self, "__OnDisconnect__"); - } - - - function OnError(self, event) - { - Log(self, "Connection Error "); - } - - - function OnMessage(self, event) - { - let data_view = new DataView(event.data); - let data_view_reader = new DataViewReader(data_view, 0); - self.CallMessageHandlers(data_view_reader); - } - - WebSocketConnection.prototype.CallMessageHandlers = function(data_view_reader) - { - // Decode standard message header - const id = data_view_reader.GetStringOfLength(4); - const length = data_view_reader.GetUInt32(); - - // Pass the length of the message left to parse - CallMessageHandlers(this, id, data_view_reader, length - 8); - - return [ id, length ]; - } - - - return WebSocketConnection; -})(); diff --git a/profiler/vis/Code/tsconfig.json b/profiler/vis/Code/tsconfig.json deleted file mode 100644 index 029304c..0000000 --- a/profiler/vis/Code/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "compilerOptions": { - "outFile": "out.js", - "allowJs": true, - "sourceMap": true - }, - "include": ["ThreadFrame.js"] -} \ No newline at end of file diff --git a/profiler/vis/Styles/Fonts/FiraCode/LICENSE b/profiler/vis/Styles/Fonts/FiraCode/LICENSE deleted file mode 100644 index 8e9c277..0000000 --- a/profiler/vis/Styles/Fonts/FiraCode/LICENSE +++ /dev/null @@ -1,93 +0,0 @@ -Copyright (c) 2014, The Fira Code Project Authors (https://github.com/tonsky/FiraCode) - -This Font Software is licensed under the SIL Open Font License, Version 1.1. -This license is copied below, and is also available with a FAQ at: -http://scripts.sil.org/OFL - - ------------------------------------------------------------ -SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 ------------------------------------------------------------ - -PREAMBLE -The goals of the Open Font License (OFL) are to stimulate worldwide -development of collaborative font projects, to support the font creation -efforts of academic and linguistic communities, and to provide a free and -open framework in which fonts may be shared and improved in partnership -with others. - -The OFL allows the licensed fonts to be used, studied, modified and -redistributed freely as long as they are not sold by themselves. The -fonts, including any derivative works, can be bundled, embedded, -redistributed and/or sold with any software provided that any reserved -names are not used by derivative works. The fonts and derivatives, -however, cannot be released under any other type of license. The -requirement for fonts to remain under this license does not apply -to any document created using the fonts or their derivatives. - -DEFINITIONS -"Font Software" refers to the set of files released by the Copyright -Holder(s) under this license and clearly marked as such. This may -include source files, build scripts and documentation. - -"Reserved Font Name" refers to any names specified as such after the -copyright statement(s). - -"Original Version" refers to the collection of Font Software components as -distributed by the Copyright Holder(s). - -"Modified Version" refers to any derivative made by adding to, deleting, -or substituting -- in part or in whole -- any of the components of the -Original Version, by changing formats or by porting the Font Software to a -new environment. - -"Author" refers to any designer, engineer, programmer, technical -writer or other person who contributed to the Font Software. - -PERMISSION & CONDITIONS -Permission is hereby granted, free of charge, to any person obtaining -a copy of the Font Software, to use, study, copy, merge, embed, modify, -redistribute, and sell modified and unmodified copies of the Font -Software, subject to the following conditions: - -1) Neither the Font Software nor any of its individual components, -in Original or Modified Versions, may be sold by itself. - -2) Original or Modified Versions of the Font Software may be bundled, -redistributed and/or sold with any software, provided that each copy -contains the above copyright notice and this license. These can be -included either as stand-alone text files, human-readable headers or -in the appropriate machine-readable metadata fields within text or -binary files as long as those fields can be easily viewed by the user. - -3) No Modified Version of the Font Software may use the Reserved Font -Name(s) unless explicit written permission is granted by the corresponding -Copyright Holder. This restriction only applies to the primary font name as -presented to the users. - -4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font -Software shall not be used to promote, endorse or advertise any -Modified Version, except to acknowledge the contribution(s) of the -Copyright Holder(s) and the Author(s) or with their explicit written -permission. - -5) The Font Software, modified or unmodified, in part or in whole, -must be distributed entirely under this license, and must not be -distributed under any other license. The requirement for fonts to -remain under this license does not apply to any document created -using the Font Software. - -TERMINATION -This license becomes null and void if any of the above conditions are -not met. - -DISCLAIMER -THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT -OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE -COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL -DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM -OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/profiler/vis/Styles/Remotery.css b/profiler/vis/Styles/Remotery.css deleted file mode 100644 index 79df692..0000000 --- a/profiler/vis/Styles/Remotery.css +++ /dev/null @@ -1,237 +0,0 @@ - -body -{ - /* Take up the full page */ - width: 100%; - height: 100%; - margin: 0px; - - background-color: #999; - - touch-action: none; -} - - -/* Override default container style to remove 3D effect */ -.Container -{ - border: none; - box-shadow: none; -} - - -/* Override default edit box style to remove 3D effect */ -.EditBox -{ - border: none; - box-shadow: none; - width:200; -} - - -@font-face -{ - font-family: "LocalFiraCode"; - src:url("Fonts/FiraCode/FiraCode-Regular.ttf"); -} - -.ConsoleText -{ - overflow:auto; - color: #BBB; - font: 10px LocalFiraCode; - margin: 3px; - white-space: pre; - line-height:14px; -} - - -.PingContainer -{ - background-color: #F55; - border-radius: 2px; - - /* Transition from green is gradual */ - transition: background-color 0.25s ease-in; -} - - -.PingContainerActive -{ - background-color: #5F5; - - /* Transition to green is instant */ - transition: none; -} - - -.GridNameHeader -{ - position: absolute; - display: inline-block; - white-space: nowrap; - overflow: hidden; - text-align: center; - - background:rgb(48, 48, 48); - - color: #BBB; - font: 9px Verdana; - - padding: 1px 1px 1px 2px; - border: 1px solid; - - border-top-color:#555; - border-left-color:#555; - border-bottom-color:#111; - border-right-color:#111; -} - -.TimelineBox -{ - /* Following style generally copies GridRowCell.GridGroup from BrowserLib */ - - padding: 1px 1px 1px 2px; - margin: 1px; - - border: 1px solid; - border-radius: 2px; - border-top-color:#555; - border-left-color:#555; - border-bottom-color:#111; - border-right-color:#111; - - background: #222; - - font: 9px Verdana; - color: #BBB; -} -.TimelineRow -{ - width: 100%; -} -.TimelineRowCheckbox -{ - width: 12px; - height: 12px; - margin: 0px; -} -.TimelineRowCheck -{ - /* Pull .TimelineRowExpand to the right of the checkbox */ - float:left; - - width: 14px; - height: 14px; -} -.TimelineRowExpand -{ - /* Pull .TimelineRowLabel to the right of +/- buttons */ - float:left; - - width: 14px; - height: 14px; -} -.TimelineRowExpandButton -{ - width: 11px; - height: 12px; - - color: #333; - - border: 1px solid; - - border-top-color:#F4F4F4; - border-left-color:#F4F4F4; - border-bottom-color:#8E8F8F; - border-right-color:#8E8F8F; - - /* Top-right to bottom-left grey background gradient */ - background: #f6f6f6; /* Old browsers */ - background: -moz-linear-gradient(-45deg, #f6f6f6 0%, #abaeb2 100%); /* FF3.6+ */ - background: -webkit-gradient(linear, left top, right bottom, color-stop(0%,#f6f6f6), color-stop(100%,#abaeb2)); /* Chrome,Safari4+ */ - background: -webkit-linear-gradient(-45deg, #f6f6f6 0%,#abaeb2 100%); /* Chrome10+,Safari5.1+ */ - background: -o-linear-gradient(-45deg, #f6f6f6 0%,#abaeb2 100%); /* Opera 11.10+ */ - background: -ms-linear-gradient(-45deg, #f6f6f6 0%,#abaeb2 100%); /* IE10+ */ - background: linear-gradient(135deg, #f6f6f6 0%,#abaeb2 100%); /* W3C */ - filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#f6f6f6', endColorstr='#abaeb2',GradientType=1 ); /* IE6-9 fallback on horizontal gradient */ - - text-align: center; - vertical-align: center; -} -.TimelineRowExpandButton:hover -{ - border-top-color:#79C6F9; - border-left-color:#79C6F9; - border-bottom-color:#385D72; - border-right-color:#385D72; - - /* Top-right to bottom-left blue background gradient, matching border */ - background: #f3f3f3; /* Old browsers */ - background: -moz-linear-gradient(-45deg, #f3f3f3 0%, #79c6f9 100%); /* FF3.6+ */ - background: -webkit-gradient(linear, left top, right bottom, color-stop(0%,#f3f3f3), color-stop(100%,#79c6f9)); /* Chrome,Safari4+ */ - background: -webkit-linear-gradient(-45deg, #f3f3f3 0%,#79c6f9 100%); /* Chrome10+,Safari5.1+ */ - background: -o-linear-gradient(-45deg, #f3f3f3 0%,#79c6f9 100%); /* Opera 11.10+ */ - background: -ms-linear-gradient(-45deg, #f3f3f3 0%,#79c6f9 100%); /* IE10+ */ - background: linear-gradient(135deg, #f3f3f3 0%,#79c6f9 100%); /* W3C */ - filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#f3f3f3', endColorstr='#79c6f9',GradientType=1 ); /* IE6-9 fallback on horizontal gradient */ -} -.TimelineRowExpandButtonActive -{ - /* Simple means of shifting text within a div to the bottom-right */ - padding-left:1px; - padding-top:1px; - width:10px; - height:11px; -} -.TimelineRowLabel -{ - float:left; - - width: 140px; - height: 14px; -} - -.TimelineContainer -{ -} -.TimelineLabels -{ - padding: 0; - margin: 0; - border: 0; - overflow-y: hidden; -} -.TimelineLabelScrollClipper -{ - padding: 0; - margin: 0; - border: 0; - overflow-y: hidden; -} - -.DropZone -{ - /* Covers the whole page, initially hidden */ - box-sizing: border-box; - display: none; - position: fixed; - width: 100%; - height: 100%; - left: 0; - top: 0; - - /* On top of everything possible */ - z-index: 99999; - - /* Styling for when visible */ - background: rgba(32, 4, 136, 0.25); - border: 3px dashed white; - - /* Styling for text when visible */ - color: white; - font-family: Arial, Helvetica, sans-serif; - font-size: xx-large; - align-items: center; - justify-content: center; -} diff --git a/profiler/vis/extern/BrowserLib/Core/Code/Animation.js b/profiler/vis/extern/BrowserLib/Core/Code/Animation.js deleted file mode 100644 index 67e9332..0000000 --- a/profiler/vis/extern/BrowserLib/Core/Code/Animation.js +++ /dev/null @@ -1,65 +0,0 @@ - -// -// Very basic linear value animation system, for now. -// - - -namespace("Anim"); - - -Anim.Animation = (function() -{ - var anim_hz = 60; - - - function Animation(anim_func, start_value, end_value, time, end_callback) - { - // Setup initial parameters - this.StartValue = start_value; - this.EndValue = end_value; - this.ValueInc = (end_value - start_value) / (time * anim_hz); - this.Value = start_value; - this.Complete = false; - this.EndCallback = end_callback; - - // Cache the update function to prevent recreating the closure - var self = this; - this.AnimFunc = anim_func; - this.AnimUpdate = function() { Update(self); } - - // Call for the start value - this.AnimUpdate(); - } - - - function Update(self) - { - // Queue up the next frame immediately - var id = window.setTimeout(self.AnimUpdate, 1000 / anim_hz); - - // Linear step the value and check for completion - self.Value += self.ValueInc; - if (Math.abs(self.Value - self.EndValue) < 0.01) - { - self.Value = self.EndValue; - self.Complete = true; - - if (self.EndCallback) - self.EndCallback(); - - window.clearTimeout(id); - } - - // Pass to the animation function - self.AnimFunc(self.Value); - } - - - return Animation; -})(); - - -Anim.Animate = function(anim_func, start_value, end_value, time, end_callback) -{ - return new Anim.Animation(anim_func, start_value, end_value, time, end_callback); -} diff --git a/profiler/vis/extern/BrowserLib/Core/Code/Bind.js b/profiler/vis/extern/BrowserLib/Core/Code/Bind.js deleted file mode 100644 index 23c15e9..0000000 --- a/profiler/vis/extern/BrowserLib/Core/Code/Bind.js +++ /dev/null @@ -1,92 +0,0 @@ -// -// This will generate a closure for the given function and optionally bind an arbitrary number of -// its initial arguments to specific values. -// -// Parameters: -// -// 0: Either the function scope or the function. -// 1: If 0 is the function scope, this is the function. -// Otherwise it's the start of the optional bound argument list. -// 2: Start of the optional bound argument list if 1 is the function. -// -// Examples: -// -// function GlobalFunction(p0, p1, p2) { } -// function ThisFunction(p0, p1, p2) { } -// -// var a = Bind("GlobalFunction"); -// var b = Bind(this, "ThisFunction"); -// var c = Bind("GlobalFunction", BoundParam0, BoundParam1); -// var d = Bind(this, "ThisFunction", BoundParam0, BoundParam1); -// var e = Bind(GlobalFunction); -// var f = Bind(this, ThisFunction); -// var g = Bind(GlobalFunction, BoundParam0, BoundParam1); -// var h = Bind(this, ThisFunction, BoundParam0, BoundParam1); -// -// a(0, 1, 2); -// b(0, 1, 2); -// c(2); -// d(2); -// e(0, 1, 2); -// f(0, 1, 2); -// g(2); -// h(2); -// -function Bind() -{ - // No closure to define? - if (arguments.length == 0) - return null; - - // Figure out which of the 4 call types is being used to bind - // Locate scope, function and bound parameter start index - - if (typeof(arguments[0]) == "string") - { - var scope = window; - var func = window[arguments[0]]; - var start = 1; - } - - else if (typeof(arguments[0]) == "function") - { - var scope = window; - var func = arguments[0]; - var start = 1; - } - - else if (typeof(arguments[1]) == "string") - { - var scope = arguments[0]; - var func = scope[arguments[1]]; - var start = 2; - } - - else if (typeof(arguments[1]) == "function") - { - var scope = arguments[0]; - var func = arguments[1]; - var start = 2; - } - - else - { - // unknown - console.log("Bind() ERROR: Unknown bind parameter configuration"); - return; - } - - // Convert the arguments list to an array - var arg_array = Array.prototype.slice.call(arguments, start); - start = arg_array.length; - - return function() - { - // Concatenate incoming arguments - for (var i = 0; i < arguments.length; i++) - arg_array[start + i] = arguments[i]; - - // Call the function in the given scope with the new arguments - return func.apply(scope, arg_array); - } -} diff --git a/profiler/vis/extern/BrowserLib/Core/Code/Convert.js b/profiler/vis/extern/BrowserLib/Core/Code/Convert.js deleted file mode 100644 index 0fa7ff4..0000000 --- a/profiler/vis/extern/BrowserLib/Core/Code/Convert.js +++ /dev/null @@ -1,218 +0,0 @@ - -namespace("Convert"); - - -// -// Convert between utf8 and b64 without raising character out of range exceptions with unicode strings -// Technique described here: http://monsur.hossa.in/2012/07/20/utf-8-in-javascript.html -// -Convert.utf8string_to_b64string = function(str) -{ - return btoa(unescape(encodeURIComponent(str))); -} -Convert.b64string_to_utf8string = function(str) -{ - return decodeURIComponent(escape(atob(str))); -} - - -// -// More general approach, converting between byte arrays and b64 -// Info here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Base64_encoding_and_decoding -// -Convert.b64string_to_Uint8Array = function(sBase64, nBlocksSize) -{ - function b64ToUint6 (nChr) - { - return nChr > 64 && nChr < 91 ? - nChr - 65 - : nChr > 96 && nChr < 123 ? - nChr - 71 - : nChr > 47 && nChr < 58 ? - nChr + 4 - : nChr === 43 ? - 62 - : nChr === 47 ? - 63 - : - 0; - } - - var - sB64Enc = sBase64.replace(/[^A-Za-z0-9\+\/]/g, ""), - nInLen = sB64Enc.length, - nOutLen = nBlocksSize ? Math.ceil((nInLen * 3 + 1 >> 2) / nBlocksSize) * nBlocksSize : nInLen * 3 + 1 >> 2, - taBytes = new Uint8Array(nOutLen); - - for (var nMod3, nMod4, nUint24 = 0, nOutIdx = 0, nInIdx = 0; nInIdx < nInLen; nInIdx++) - { - nMod4 = nInIdx & 3; - nUint24 |= b64ToUint6(sB64Enc.charCodeAt(nInIdx)) << 18 - 6 * nMod4; - if (nMod4 === 3 || nInLen - nInIdx === 1) - { - for (nMod3 = 0; nMod3 < 3 && nOutIdx < nOutLen; nMod3++, nOutIdx++) - taBytes[nOutIdx] = nUint24 >>> (16 >>> nMod3 & 24) & 255; - nUint24 = 0; - } - } - - return taBytes; -} -Convert.Uint8Array_to_b64string = function(aBytes) -{ - function uint6ToB64 (nUint6) - { - return nUint6 < 26 ? - nUint6 + 65 - : nUint6 < 52 ? - nUint6 + 71 - : nUint6 < 62 ? - nUint6 - 4 - : nUint6 === 62 ? - 43 - : nUint6 === 63 ? - 47 - : - 65; - } - - var nMod3, sB64Enc = ""; - - for (var nLen = aBytes.length, nUint24 = 0, nIdx = 0; nIdx < nLen; nIdx++) - { - nMod3 = nIdx % 3; - if (nIdx > 0 && (nIdx * 4 / 3) % 76 === 0) - sB64Enc += "\r\n"; - nUint24 |= aBytes[nIdx] << (16 >>> nMod3 & 24); - if (nMod3 === 2 || aBytes.length - nIdx === 1) - { - sB64Enc += String.fromCharCode(uint6ToB64(nUint24 >>> 18 & 63), uint6ToB64(nUint24 >>> 12 & 63), uint6ToB64(nUint24 >>> 6 & 63), uint6ToB64(nUint24 & 63)); - nUint24 = 0; - } - } - - return sB64Enc.replace(/A(?=A$|$)/g, "="); -} - - -// -// Unicode and arbitrary value safe conversion between strings and Uint8Arrays -// -Convert.Uint8Array_to_string = function(aBytes) -{ - var sView = ""; - - for (var nPart, nLen = aBytes.length, nIdx = 0; nIdx < nLen; nIdx++) - { - nPart = aBytes[nIdx]; - sView += String.fromCharCode( - nPart > 251 && nPart < 254 && nIdx + 5 < nLen ? /* six bytes */ - /* (nPart - 252 << 32) is not possible in ECMAScript! So...: */ - (nPart - 252) * 1073741824 + (aBytes[++nIdx] - 128 << 24) + (aBytes[++nIdx] - 128 << 18) + (aBytes[++nIdx] - 128 << 12) + (aBytes[++nIdx] - 128 << 6) + aBytes[++nIdx] - 128 - : nPart > 247 && nPart < 252 && nIdx + 4 < nLen ? /* five bytes */ - (nPart - 248 << 24) + (aBytes[++nIdx] - 128 << 18) + (aBytes[++nIdx] - 128 << 12) + (aBytes[++nIdx] - 128 << 6) + aBytes[++nIdx] - 128 - : nPart > 239 && nPart < 248 && nIdx + 3 < nLen ? /* four bytes */ - (nPart - 240 << 18) + (aBytes[++nIdx] - 128 << 12) + (aBytes[++nIdx] - 128 << 6) + aBytes[++nIdx] - 128 - : nPart > 223 && nPart < 240 && nIdx + 2 < nLen ? /* three bytes */ - (nPart - 224 << 12) + (aBytes[++nIdx] - 128 << 6) + aBytes[++nIdx] - 128 - : nPart > 191 && nPart < 224 && nIdx + 1 < nLen ? /* two bytes */ - (nPart - 192 << 6) + aBytes[++nIdx] - 128 - : /* nPart < 127 ? */ /* one byte */ - nPart - ); - } - - return sView; -} -Convert.string_to_Uint8Array = function(sDOMStr) -{ - var aBytes, nChr, nStrLen = sDOMStr.length, nArrLen = 0; - - /* mapping... */ - - for (var nMapIdx = 0; nMapIdx < nStrLen; nMapIdx++) - { - nChr = sDOMStr.charCodeAt(nMapIdx); - nArrLen += nChr < 0x80 ? 1 : nChr < 0x800 ? 2 : nChr < 0x10000 ? 3 : nChr < 0x200000 ? 4 : nChr < 0x4000000 ? 5 : 6; - } - - aBytes = new Uint8Array(nArrLen); - - /* transcription... */ - - for (var nIdx = 0, nChrIdx = 0; nIdx < nArrLen; nChrIdx++) - { - nChr = sDOMStr.charCodeAt(nChrIdx); - if (nChr < 128) - { - /* one byte */ - aBytes[nIdx++] = nChr; - } - else if (nChr < 0x800) - { - /* two bytes */ - aBytes[nIdx++] = 192 + (nChr >>> 6); - aBytes[nIdx++] = 128 + (nChr & 63); - } - else if (nChr < 0x10000) - { - /* three bytes */ - aBytes[nIdx++] = 224 + (nChr >>> 12); - aBytes[nIdx++] = 128 + (nChr >>> 6 & 63); - aBytes[nIdx++] = 128 + (nChr & 63); - } - else if (nChr < 0x200000) - { - /* four bytes */ - aBytes[nIdx++] = 240 + (nChr >>> 18); - aBytes[nIdx++] = 128 + (nChr >>> 12 & 63); - aBytes[nIdx++] = 128 + (nChr >>> 6 & 63); - aBytes[nIdx++] = 128 + (nChr & 63); - } - else if (nChr < 0x4000000) - { - /* five bytes */ - aBytes[nIdx++] = 248 + (nChr >>> 24); - aBytes[nIdx++] = 128 + (nChr >>> 18 & 63); - aBytes[nIdx++] = 128 + (nChr >>> 12 & 63); - aBytes[nIdx++] = 128 + (nChr >>> 6 & 63); - aBytes[nIdx++] = 128 + (nChr & 63); - } - else /* if (nChr <= 0x7fffffff) */ - { - /* six bytes */ - aBytes[nIdx++] = 252 + /* (nChr >>> 32) is not possible in ECMAScript! So...: */ (nChr / 1073741824); - aBytes[nIdx++] = 128 + (nChr >>> 24 & 63); - aBytes[nIdx++] = 128 + (nChr >>> 18 & 63); - aBytes[nIdx++] = 128 + (nChr >>> 12 & 63); - aBytes[nIdx++] = 128 + (nChr >>> 6 & 63); - aBytes[nIdx++] = 128 + (nChr & 63); - } - } - - return aBytes; -} - - -// -// Converts all characters in a string that have equivalent entities to their ampersand/entity names. -// Based on https://gist.github.com/jonathantneal/6093551 -// -Convert.string_to_html_entities = (function() -{ - 'use strict'; - - var data = '34quot38amp39apos60lt62gt160nbsp161iexcl162cent163pound164curren165yen166brvbar167sect168uml169copy170ordf171laquo172not173shy174reg175macr176deg177plusmn178sup2179sup3180acute181micro182para183middot184cedil185sup1186ordm187raquo188frac14189frac12190frac34191iquest192Agrave193Aacute194Acirc195Atilde196Auml197Aring198AElig199Ccedil200Egrave201Eacute202Ecirc203Euml204Igrave205Iacute206Icirc207Iuml208ETH209Ntilde210Ograve211Oacute212Ocirc213Otilde214Ouml215times216Oslash217Ugrave218Uacute219Ucirc220Uuml221Yacute222THORN223szlig224agrave225aacute226acirc227atilde228auml229aring230aelig231ccedil232egrave233eacute234ecirc235euml236igrave237iacute238icirc239iuml240eth241ntilde242ograve243oacute244ocirc245otilde246ouml247divide248oslash249ugrave250uacute251ucirc252uuml253yacute254thorn255yuml402fnof913Alpha914Beta915Gamma916Delta917Epsilon918Zeta919Eta920Theta921Iota922Kappa923Lambda924Mu925Nu926Xi927Omicron928Pi929Rho931Sigma932Tau933Upsilon934Phi935Chi936Psi937Omega945alpha946beta947gamma948delta949epsilon950zeta951eta952theta953iota954kappa955lambda956mu957nu958xi959omicron960pi961rho962sigmaf963sigma964tau965upsilon966phi967chi968psi969omega977thetasym978upsih982piv8226bull8230hellip8242prime8243Prime8254oline8260frasl8472weierp8465image8476real8482trade8501alefsym8592larr8593uarr8594rarr8595darr8596harr8629crarr8656lArr8657uArr8658rArr8659dArr8660hArr8704forall8706part8707exist8709empty8711nabla8712isin8713notin8715ni8719prod8721sum8722minus8727lowast8730radic8733prop8734infin8736ang8743and8744or8745cap8746cup8747int8756there48764sim8773cong8776asymp8800ne8801equiv8804le8805ge8834sub8835sup8836nsub8838sube8839supe8853oplus8855otimes8869perp8901sdot8968lceil8969rceil8970lfloor8971rfloor9001lang9002rang9674loz9824spades9827clubs9829hearts9830diams338OElig339oelig352Scaron353scaron376Yuml710circ732tilde8194ensp8195emsp8201thinsp8204zwnj8205zwj8206lrm8207rlm8211ndash8212mdash8216lsquo8217rsquo8218sbquo8220ldquo8221rdquo8222bdquo8224dagger8225Dagger8240permil8249lsaquo8250rsaquo8364euro'; - var charCodes = data.split(/[A-z]+/); - var entities = data.split(/\d+/).slice(1); - - return function encodeHTMLEntities(text) - { - return text.replace(/[\u00A0-\u2666<>"'&]/g, function (match) - { - var charCode = String(match.charCodeAt(0)); - var index = charCodes.indexOf(charCode); - return '&' + (entities[index] ? entities[index] : '#' + charCode) + ';'; - }); - }; -})(); diff --git a/profiler/vis/extern/BrowserLib/Core/Code/Core.js b/profiler/vis/extern/BrowserLib/Core/Code/Core.js deleted file mode 100644 index 4ebcb0d..0000000 --- a/profiler/vis/extern/BrowserLib/Core/Code/Core.js +++ /dev/null @@ -1,26 +0,0 @@ - -// TODO: requires function for checking existence of dependencies - - -function namespace(name) -{ - // Ensure all nested namespaces are created only once - - var ns_list = name.split("."); - var parent_ns = window; - - for (var i in ns_list) - { - var ns_name = ns_list[i]; - if (!(ns_name in parent_ns)) - parent_ns[ns_name] = { }; - - parent_ns = parent_ns[ns_name]; - } -} - - -function multiline(fn) -{ - return fn.toString().split(/\n/).slice(1, -1).join("\n"); -} diff --git a/profiler/vis/extern/BrowserLib/Core/Code/DOM.js b/profiler/vis/extern/BrowserLib/Core/Code/DOM.js deleted file mode 100644 index 963ce3f..0000000 --- a/profiler/vis/extern/BrowserLib/Core/Code/DOM.js +++ /dev/null @@ -1,526 +0,0 @@ - -namespace("DOM.Node"); -namespace("DOM.Event"); -namespace("DOM.Applet"); - - - -// -// ===================================================================================================================== -// ----- DOCUMENT NODE/ELEMENT EXTENSIONS ------------------------------------------------------------------------------ -// ===================================================================================================================== -// - - - -DOM.Node.Get = function(id) -{ - return document.getElementById(id); -} - - -// -// Set node position -// -DOM.Node.SetPosition = function(node, position) -{ - node.style.left = position[0]; - node.style.top = position[1]; -} -DOM.Node.SetX = function(node, x) -{ - node.style.left = x; -} -DOM.Node.SetY = function(node, y) -{ - node.style.top = y; -} - - -// -// Get the absolute position of a HTML element on the page -// -DOM.Node.GetPosition = function(element, account_for_scroll) -{ - // Recurse up through parents, summing offsets from their parent - var x = 0, y = 0; - for (var node = element; node != null; node = node.offsetParent) - { - x += node.offsetLeft; - y += node.offsetTop; - } - - if (account_for_scroll) - { - // Walk up the hierarchy subtracting away any scrolling - for (var node = element; node != document.body; node = node.parentNode) - { - x -= node.scrollLeft; - y -= node.scrollTop; - } - } - - return [x, y]; -} - - -// -// Set node size -// -DOM.Node.SetSize = function(node, size) -{ - node.style.width = size[0]; - node.style.height = size[1]; -} -DOM.Node.SetWidth = function(node, width) -{ - node.style.width = width; -} -DOM.Node.SetHeight = function(node, height) -{ - node.style.height = height; -} - - -// -// Get node OFFSET size: -// clientX includes padding -// offsetX includes padding and borders -// scrollX includes padding, borders and size of contained node -// -DOM.Node.GetSize = function(node) -{ - return [ node.offsetWidth, node.offsetHeight ]; -} -DOM.Node.GetWidth = function(node) -{ - return node.offsetWidth; -} -DOM.Node.GetHeight = function(node) -{ - return node.offsetHeight; -} - - -// -// Set node opacity -// -DOM.Node.SetOpacity = function(node, value) -{ - node.style.opacity = value; -} - - -DOM.Node.SetColour = function(node, colour) -{ - node.style.color = colour; -} - - -// -// Hide a node by completely disabling its rendering (it no longer contributes to document layout) -// -DOM.Node.Hide = function(node) -{ - node.style.display = "none"; -} - - -// -// Show a node by restoring its influcen in document layout -// -DOM.Node.Show = function(node) -{ - node.style.display = "block"; -} - - -// -// Add a CSS class to a HTML element, specified last -// -DOM.Node.AddClass = function(node, class_name) -{ - // Ensure the class hasn't already been added - DOM.Node.RemoveClass(node, class_name); - node.className += " " + class_name; -} - - -// -// Remove a CSS class from a HTML element -// -DOM.Node.RemoveClass = function(node, class_name) -{ - // Remove all variations of where the class name can be in the string list - var regexp = new RegExp("\\b" + class_name + "\\b"); - node.className = node.className.replace(regexp, ""); -} - - - -// -// Check to see if a HTML element contains a class -// -DOM.Node.HasClass = function(node, class_name) -{ - var regexp = new RegExp("\\b" + class_name + "\\b"); - return regexp.test(node.className); -} - - -// -// Recursively search for a node with the given class name -// -DOM.Node.FindWithClass = function(parent_node, class_name, index) -{ - // Search the children looking for a node with the given class name - for (var i in parent_node.childNodes) - { - var node = parent_node.childNodes[i]; - if (DOM.Node.HasClass(node, class_name)) - { - if (index === undefined || index-- == 0) - return node; - } - - // Recurse into children - node = DOM.Node.FindWithClass(node, class_name); - if (node != null) - return node; - } - - return null; -} - - -// -// Check to see if one node logically contains another -// -DOM.Node.Contains = function(node, container_node) -{ - while (node != null && node != container_node) - node = node.parentNode; - return node != null; -} - - -// -// Create the HTML nodes specified in the text passed in -// Assumes there is only one root node in the text -// -DOM.Node.CreateHTML = function(html) -{ - var div = document.createElement("div"); - div.innerHTML = html; - - // First child may be a text node, followed by the created HTML - var child = div.firstChild; - if (child != null && child.nodeType == 3) - child = child.nextSibling; - return child; -} - - -// -// Make a copy of a HTML element, making it visible and clearing its ID to ensure it's not a duplicate -// -DOM.Node.Clone = function(name) -{ - // Get the template element and clone it, making sure it's renderable - var node = DOM.Node.Get(name); - node = node.cloneNode(true); - node.id = null; - node.style.display = "block"; - return node; -} - - -// -// Append an arbitrary block of HTML to an existing node -// -DOM.Node.AppendHTML = function(node, html) -{ - var child = DOM.Node.CreateHTML(html); - node.appendChild(child); - return child; -} - - -// -// Append a div that clears the float style -// -DOM.Node.AppendClearFloat = function(node) -{ - var div = document.createElement("div"); - div.style.clear = "both"; - node.appendChild(div); -} - - -// -// Check to see that the object passed in is an instance of a DOM node -// -DOM.Node.IsNode = function(object) -{ - return object instanceof Element; -} - - -// -// Create an "iframe shim" so that elements within it render over a Java Applet -// http://web.archive.org/web/20110707212850/http://www.oratransplant.nl/2007/10/26/using-iframe-shim-to-partly-cover-a-java-applet/ -// -DOM.Node.CreateShim = function(parent) -{ - var shimmer = document.createElement("iframe"); - - // Position the shimmer so that it's the same location/size as its parent - shimmer.style.position = "fixed"; - shimmer.style.left = parent.style.left; - shimmer.style.top = parent.style.top; - shimmer.style.width = parent.offsetWidth; - shimmer.style.height = parent.offsetHeight; - - // We want the shimmer to be one level below its contents - shimmer.style.zIndex = parent.style.zIndex - 1; - - // Ensure its empty - shimmer.setAttribute("frameborder", "0"); - shimmer.setAttribute("src", ""); - - // Add to the document and the parent - document.body.appendChild(shimmer); - parent.Shimmer = shimmer; - return shimmer; -} - - - -// -// ===================================================================================================================== -// ----- EVENT HANDLING EXTENSIONS ------------------------------------------------------------------------------------- -// ===================================================================================================================== -// - - - -// -// Retrieves the event from the first parameter passed into an HTML event -// -DOM.Event.Get = function(evt) -{ - // Internet explorer doesn't pass the event - return window.event || evt; -} - - -// -// Retrieves the element that triggered an event from the event object -// -DOM.Event.GetNode = function(evt) -{ - evt = DOM.Event.Get(evt); - - // Get the target element - var element; - if (evt.target) - element = evt.target; - else if (e.srcElement) - element = evt.srcElement; - - // Default Safari bug - if (element.nodeType == 3) - element = element.parentNode; - - return element; -} - - -// -// Stop default action for an event -// -DOM.Event.StopDefaultAction = function(evt) -{ - if (evt && evt.preventDefault) - evt.preventDefault(); - else if (window.event && window.event.returnValue) - window.event.returnValue = false; -} - - -// -// Stops events bubbling up to parent event handlers -// -DOM.Event.StopPropagation = function(evt) -{ - evt = DOM.Event.Get(evt); - if (evt) - { - evt.cancelBubble = true; - if (evt.stopPropagation) - evt.stopPropagation(); - } -} - - -// -// Stop both event default action and propagation -// -DOM.Event.StopAll = function(evt) -{ - DOM.Event.StopDefaultAction(evt); - DOM.Event.StopPropagation(evt); -} - - -// -// Adds an event handler to an event -// -DOM.Event.AddHandler = function(obj, evt, func) -{ - if (obj) - { - if (obj.addEventListener) - obj.addEventListener(evt, func, false); - else if (obj.attachEvent) - obj.attachEvent("on" + evt, func); - } -} - - -// -// Removes an event handler from an event -// -DOM.Event.RemoveHandler = function(obj, evt, func) -{ - if (obj) - { - if (obj.removeEventListener) - obj.removeEventListener(evt, func, false); - else if (obj.detachEvent) - obj.detachEvent("on" + evt, func); - } -} - - -// -// Get the position of the mouse cursor, page relative -// -DOM.Event.GetMousePosition = function(evt) -{ - evt = DOM.Event.Get(evt); - - var px = 0; - var py = 0; - if (evt.pageX || evt.pageY) - { - px = evt.pageX; - py = evt.pageY; - } - else if (evt.clientX || evt.clientY) - { - px = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft; - py = e.clientY + document.body.scrollTop + document.documentElement.scrollTop; - } - - return [px, py]; -} - - -// -// Get the list of files attached to a drop event -// -DOM.Event.GetDropFiles = function(evt) -{ - let files = []; - if (evt.dataTransfer.items) - { - for (let i = 0; i < evt.dataTransfer.items.length; i++) - { - if (evt.dataTransfer.items[i].kind === 'file') - { - files.push(evt.dataTransfer.items[i].getAsFile()); - } - } - } - else - { - for (let i = 0; i < evt.dataTransfer.files.length; i++) - { - files.push(evt.dataTransfer.files[i]); - } - } - return files; -} - - - -// -// ===================================================================================================================== -// ----- JAVA APPLET EXTENSIONS ---------------------------------------------------------------------------------------- -// ===================================================================================================================== -// - - - -// -// Create an applet element for loading a Java applet, attaching it to the specified node -// -DOM.Applet.Load = function(dest_id, id, code, archive) -{ - // Lookup the applet destination - var dest = DOM.Node.Get(dest_id); - if (!dest) - return; - - // Construct the applet element and add it to the destination - Debug.Log("Injecting applet DOM code"); - var applet = ""; - applet += ""; - dest.innerHTML = applet; -} - - -// -// Moves and resizes a named applet so that it fits in the destination div element. -// The applet must be contained by a div element itself. This container div is moved along -// with the applet. -// -DOM.Applet.Move = function(dest_div, applet, z_index, hide) -{ - if (!applet || !dest_div) - return; - - // Before modifying any location information, hide the applet so that it doesn't render over - // any newly visible elements that appear while the location information is being modified. - if (hide) - applet.style.visibility = "hidden"; - - // Get its view rect - var pos = DOM.Node.GetPosition(dest_div); - var w = dest_div.offsetWidth; - var h = dest_div.offsetHeight; - - // It needs to be embedded in a
for correct scale/position adjustment - var container = applet.parentNode; - if (!container || container.localName != "div") - { - Debug.Log("ERROR: Couldn't find source applet's div container"); - return; - } - - // Reposition and resize the containing div element - container.style.left = pos[0]; - container.style.top = pos[1]; - container.style.width = w; - container.style.height = h; - container.style.zIndex = z_index; - - // Resize the applet itself - applet.style.width = w; - applet.style.height = h; - - // Everything modified, safe to show - applet.style.visibility = "visible"; -} diff --git a/profiler/vis/extern/BrowserLib/Core/Code/Keyboard.js b/profiler/vis/extern/BrowserLib/Core/Code/Keyboard.js deleted file mode 100644 index b233ffb..0000000 --- a/profiler/vis/extern/BrowserLib/Core/Code/Keyboard.js +++ /dev/null @@ -1,149 +0,0 @@ - -namespace("Keyboard") - - -// ===================================================================================================================== -// Key codes copied from closure-library -// https://code.google.com/p/closure-library/source/browse/closure/goog/events/keycodes.js -// --------------------------------------------------------------------------------------------------------------------- -// Copyright 2006 The Closure Library Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS-IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -Keyboard.Codes = { - WIN_KEY_FF_LINUX : 0, - MAC_ENTER : 3, - BACKSPACE : 8, - TAB : 9, - NUM_CENTER : 12, // NUMLOCK on FF/Safari Mac - ENTER : 13, - SHIFT : 16, - CTRL : 17, - ALT : 18, - PAUSE : 19, - CAPS_LOCK : 20, - ESC : 27, - SPACE : 32, - PAGE_UP : 33, // also NUM_NORTH_EAST - PAGE_DOWN : 34, // also NUM_SOUTH_EAST - END : 35, // also NUM_SOUTH_WEST - HOME : 36, // also NUM_NORTH_WEST - LEFT : 37, // also NUM_WEST - UP : 38, // also NUM_NORTH - RIGHT : 39, // also NUM_EAST - DOWN : 40, // also NUM_SOUTH - PRINT_SCREEN : 44, - INSERT : 45, // also NUM_INSERT - DELETE : 46, // also NUM_DELETE - ZERO : 48, - ONE : 49, - TWO : 50, - THREE : 51, - FOUR : 52, - FIVE : 53, - SIX : 54, - SEVEN : 55, - EIGHT : 56, - NINE : 57, - FF_SEMICOLON : 59, // Firefox (Gecko) fires this for semicolon instead of 186 - FF_EQUALS : 61, // Firefox (Gecko) fires this for equals instead of 187 - FF_DASH : 173, // Firefox (Gecko) fires this for dash instead of 189 - QUESTION_MARK : 63, // needs localization - A : 65, - B : 66, - C : 67, - D : 68, - E : 69, - F : 70, - G : 71, - H : 72, - I : 73, - J : 74, - K : 75, - L : 76, - M : 77, - N : 78, - O : 79, - P : 80, - Q : 81, - R : 82, - S : 83, - T : 84, - U : 85, - V : 86, - W : 87, - X : 88, - Y : 89, - Z : 90, - META : 91, // WIN_KEY_LEFT - WIN_KEY_RIGHT : 92, - CONTEXT_MENU : 93, - NUM_ZERO : 96, - NUM_ONE : 97, - NUM_TWO : 98, - NUM_THREE : 99, - NUM_FOUR : 100, - NUM_FIVE : 101, - NUM_SIX : 102, - NUM_SEVEN : 103, - NUM_EIGHT : 104, - NUM_NINE : 105, - NUM_MULTIPLY : 106, - NUM_PLUS : 107, - NUM_MINUS : 109, - NUM_PERIOD : 110, - NUM_DIVISION : 111, - F1 : 112, - F2 : 113, - F3 : 114, - F4 : 115, - F5 : 116, - F6 : 117, - F7 : 118, - F8 : 119, - F9 : 120, - F10 : 121, - F11 : 122, - F12 : 123, - NUMLOCK : 144, - SCROLL_LOCK : 145, - - // OS-specific media keys like volume controls and browser controls. - FIRST_MEDIA_KEY : 166, - LAST_MEDIA_KEY : 183, - - SEMICOLON : 186, // needs localization - DASH : 189, // needs localization - EQUALS : 187, // needs localization - COMMA : 188, // needs localization - PERIOD : 190, // needs localization - SLASH : 191, // needs localization - APOSTROPHE : 192, // needs localization - TILDE : 192, // needs localization - SINGLE_QUOTE : 222, // needs localization - OPEN_SQUARE_BRACKET : 219, // needs localization - BACKSLASH : 220, // needs localization - CLOSE_SQUARE_BRACKET: 221, // needs localization - WIN_KEY : 224, - MAC_FF_META : 224, // Firefox (Gecko) fires this for the meta key instead of 91 - MAC_WK_CMD_LEFT : 91, // WebKit Left Command key fired, same as META - MAC_WK_CMD_RIGHT : 93, // WebKit Right Command key fired, different from META - WIN_IME : 229, - - // We've seen users whose machines fire this keycode at regular one - // second intervals. The common thread among these users is that - // they're all using Dell Inspiron laptops, so we suspect that this - // indicates a hardware/bios problem. - // http://en.community.dell.com/support-forums/laptop/f/3518/p/19285957/19523128.aspx - PHANTOM : 255 -}; -// ===================================================================================================================== diff --git a/profiler/vis/extern/BrowserLib/Core/Code/LocalStore.js b/profiler/vis/extern/BrowserLib/Core/Code/LocalStore.js deleted file mode 100644 index 2ca5664..0000000 --- a/profiler/vis/extern/BrowserLib/Core/Code/LocalStore.js +++ /dev/null @@ -1,40 +0,0 @@ - -namespace("LocalStore"); - - -LocalStore.Set = function(class_name, class_id, variable_id, data) -{ - try - { - if (typeof(Storage) != "undefined") - { - var name = class_name + "_" + class_id + "_" + variable_id; - localStorage[name] = JSON.stringify(data); - } - } - catch (e) - { - console.log("Local Storage Set Error: " + e.message); - } -} - - -LocalStore.Get = function(class_name, class_id, variable_id, default_data) -{ - try - { - if (typeof(Storage) != "undefined") - { - var name = class_name + "_" + class_id + "_" + variable_id; - var data = localStorage[name] - if (data) - return JSON.parse(data); - } - } - catch (e) - { - console.log("Local Storage Get Error: " + e.message); - } - - return default_data; -} \ No newline at end of file diff --git a/profiler/vis/extern/BrowserLib/Core/Code/Mouse.js b/profiler/vis/extern/BrowserLib/Core/Code/Mouse.js deleted file mode 100644 index 45699cc..0000000 --- a/profiler/vis/extern/BrowserLib/Core/Code/Mouse.js +++ /dev/null @@ -1,83 +0,0 @@ - -namespace("Mouse"); - - -Mouse.State =(function() -{ - function State(event) - { - // Get button press states - if (typeof event.buttons != "undefined") - { - // Firefox - this.Left = (event.buttons & 1) != 0; - this.Right = (event.buttons & 2) != 0; - this.Middle = (event.buttons & 4) != 0; - } - else - { - // Chrome - this.Left = (event.button == 0); - this.Middle = (event.button == 1); - this.Right = (event.button == 2); - } - - // Get page-relative mouse position - this.Position = DOM.Event.GetMousePosition(event); - - // Get wheel delta - var delta = 0; - if (event.wheelDelta) - delta = event.wheelDelta / 120; // IE/Opera - else if (event.detail) - delta = -event.detail / 3; // Mozilla - this.WheelDelta = delta; - - // Get the mouse position delta - // Requires Pointer Lock API support - this.PositionDelta = [ - event.movementX || event.mozMovementX || event.webkitMovementX || 0, - event.movementY || event.mozMovementY || event.webkitMovementY || 0 - ]; - } - - return State; -})(); - - -// -// Basic Pointer Lock API support -// https://developer.mozilla.org/en-US/docs/WebAPI/Pointer_Lock -// http://www.chromium.org/developers/design-documents/mouse-lock -// -// Note that API has not been standardised yet so browsers can implement functions with prefixes -// - - -Mouse.PointerLockSupported = function() -{ - return 'pointerLockElement' in document || 'mozPointerLockElement' in document || 'webkitPointerLockElement' in document; -} - - -Mouse.RequestPointerLock = function(element) -{ - element.requestPointerLock = element.requestPointerLock || element.mozRequestPointerLock || element.webkitRequestPointerLock; - if (element.requestPointerLock) - element.requestPointerLock(); -} - - -Mouse.ExitPointerLock = function() -{ - document.exitPointerLock = document.exitPointerLock || document.mozExitPointerLock || document.webkitExitPointerLock; - if (document.exitPointerLock) - document.exitPointerLock(); -} - - -// Can use this element to detect whether pointer lock is enabled (returns non-null) -Mouse.PointerLockElement = function() -{ - return document.pointerLockElement || document.mozPointerLockElement || document.webkitPointerLockElement; -} diff --git a/profiler/vis/extern/BrowserLib/Core/Code/MurmurHash3.js b/profiler/vis/extern/BrowserLib/Core/Code/MurmurHash3.js deleted file mode 100644 index deafe7a..0000000 --- a/profiler/vis/extern/BrowserLib/Core/Code/MurmurHash3.js +++ /dev/null @@ -1,68 +0,0 @@ - -namespace("Hash"); - -/** - * JS Implementation of MurmurHash3 (r136) (as of May 20, 2011) - * - * @author Gary Court - * @see http://github.com/garycourt/murmurhash-js - * @author Austin Appleby - * @see http://sites.google.com/site/murmurhash/ - * - * @param {string} key ASCII only - * @param {number} seed Positive integer only - * @return {number} 32-bit positive integer hash - */ - -Hash.Murmur3 = function(key, seed) -{ - var remainder, bytes, h1, h1b, c1, c1b, c2, c2b, k1, i; - - remainder = key.length & 3; // key.length % 4 - bytes = key.length - remainder; - h1 = seed; - c1 = 0xcc9e2d51; - c2 = 0x1b873593; - i = 0; - - while (i < bytes) { - k1 = - ((key.charCodeAt(i) & 0xff)) | - ((key.charCodeAt(++i) & 0xff) << 8) | - ((key.charCodeAt(++i) & 0xff) << 16) | - ((key.charCodeAt(++i) & 0xff) << 24); - ++i; - - k1 = ((((k1 & 0xffff) * c1) + ((((k1 >>> 16) * c1) & 0xffff) << 16))) & 0xffffffff; - k1 = (k1 << 15) | (k1 >>> 17); - k1 = ((((k1 & 0xffff) * c2) + ((((k1 >>> 16) * c2) & 0xffff) << 16))) & 0xffffffff; - - h1 ^= k1; - h1 = (h1 << 13) | (h1 >>> 19); - h1b = ((((h1 & 0xffff) * 5) + ((((h1 >>> 16) * 5) & 0xffff) << 16))) & 0xffffffff; - h1 = (((h1b & 0xffff) + 0x6b64) + ((((h1b >>> 16) + 0xe654) & 0xffff) << 16)); - } - - k1 = 0; - - switch (remainder) { - case 3: k1 ^= (key.charCodeAt(i + 2) & 0xff) << 16; - case 2: k1 ^= (key.charCodeAt(i + 1) & 0xff) << 8; - case 1: k1 ^= (key.charCodeAt(i) & 0xff); - - k1 = (((k1 & 0xffff) * c1) + ((((k1 >>> 16) * c1) & 0xffff) << 16)) & 0xffffffff; - k1 = (k1 << 15) | (k1 >>> 17); - k1 = (((k1 & 0xffff) * c2) + ((((k1 >>> 16) * c2) & 0xffff) << 16)) & 0xffffffff; - h1 ^= k1; - } - - h1 ^= key.length; - - h1 ^= h1 >>> 16; - h1 = (((h1 & 0xffff) * 0x85ebca6b) + ((((h1 >>> 16) * 0x85ebca6b) & 0xffff) << 16)) & 0xffffffff; - h1 ^= h1 >>> 13; - h1 = ((((h1 & 0xffff) * 0xc2b2ae35) + ((((h1 >>> 16) * 0xc2b2ae35) & 0xffff) << 16))) & 0xffffffff; - h1 ^= h1 >>> 16; - - return h1 >>> 0; -} \ No newline at end of file diff --git a/profiler/vis/extern/BrowserLib/WindowManager/Code/Button.js b/profiler/vis/extern/BrowserLib/WindowManager/Code/Button.js deleted file mode 100644 index 2cbc510..0000000 --- a/profiler/vis/extern/BrowserLib/WindowManager/Code/Button.js +++ /dev/null @@ -1,131 +0,0 @@ - -namespace("WM"); - - -WM.Button = (function() -{ - var template_html = "
"; - - - function Button(text, x, y, opts) - { - this.OnClick = null; - this.Toggle = opts && opts.toggle; - - this.Node = DOM.Node.CreateHTML(template_html); - - // Set node dimensions - this.SetPosition(x, y); - if (opts && opts.w && opts.h) - this.SetSize(opts.w, opts.h); - - // Override the default class name - if (opts && opts.class) - this.Node.className = opts.class; - - this.SetText(text); - - // Create the mouse press event handlers - DOM.Event.AddHandler(this.Node, "mousedown", Bind(OnMouseDown, this)); - this.OnMouseOutDelegate = Bind(OnMouseUp, this, false); - this.OnMouseUpDelegate = Bind(OnMouseUp, this, true); - } - - - Button.prototype.SetPosition = function(x, y) - { - this.Position = [ x, y ]; - DOM.Node.SetPosition(this.Node, this.Position); - } - - - Button.prototype.SetSize = function(w, h) - { - this.Size = [ w, h ]; - DOM.Node.SetSize(this.Node, this.Size); - } - - - Button.prototype.SetText = function(text) - { - this.Node.innerHTML = text; - } - - - Button.prototype.SetOnClick = function(on_click) - { - this.OnClick = on_click; - } - - - Button.prototype.SetState = function(pressed) - { - if (pressed) - DOM.Node.AddClass(this.Node, "ButtonHeld"); - else - DOM.Node.RemoveClass(this.Node, "ButtonHeld"); - } - - - Button.prototype.ToggleState = function() - { - if (DOM.Node.HasClass(this.Node, "ButtonHeld")) - this.SetState(false); - else - this.SetState(true); - } - - - Button.prototype.IsPressed = function() - { - return DOM.Node.HasClass(this.Node, "ButtonHeld"); - } - - - function OnMouseDown(self, evt) - { - // Decide how to set the button state - if (self.Toggle) - self.ToggleState(); - else - self.SetState(true); - - // Activate release handlers - DOM.Event.AddHandler(self.Node, "mouseout", self.OnMouseOutDelegate); - DOM.Event.AddHandler(self.Node, "mouseup", self.OnMouseUpDelegate); - - DOM.Event.StopAll(evt); - } - - - function OnMouseUp(self, confirm, evt) - { - if (confirm) - { - // Only release for non-toggles - if (!self.Toggle) - self.SetState(false); - } - else - { - // Decide how to set the button state - if (self.Toggle) - self.ToggleState(); - else - self.SetState(false); - } - - // Remove release handlers - DOM.Event.RemoveHandler(self.Node, "mouseout", self.OnMouseOutDelegate); - DOM.Event.RemoveHandler(self.Node, "mouseup", self.OnMouseUpDelegate); - - // Call the click handler if this is a button press - if (confirm && self.OnClick) - self.OnClick(self); - - DOM.Event.StopAll(evt); - } - - - return Button; -})(); \ No newline at end of file diff --git a/profiler/vis/extern/BrowserLib/WindowManager/Code/ComboBox.js b/profiler/vis/extern/BrowserLib/WindowManager/Code/ComboBox.js deleted file mode 100644 index 9af0ca1..0000000 --- a/profiler/vis/extern/BrowserLib/WindowManager/Code/ComboBox.js +++ /dev/null @@ -1,237 +0,0 @@ - -namespace("WM"); - - -WM.ComboBoxPopup = (function() -{ - var body_template_html = "
"; - - var item_template_html = " \ -
\ -
\ -
\ -
\ -
"; - - - function ComboBoxPopup(combo_box) - { - this.ComboBox = combo_box; - this.ParentNode = combo_box.Node; - this.ValueNodes = [ ]; - - // Create the template node - this.Node = DOM.Node.CreateHTML(body_template_html); - - DOM.Event.AddHandler(this.Node, "mousedown", Bind(SelectItem, this)); - this.CancelDelegate = Bind(this, "Cancel"); - } - - - ComboBoxPopup.prototype.SetValues = function(values) - { - // Clear existing values - this.Node.innerHTML = ""; - - // Generate HTML nodes for each value - this.ValueNodes = [ ]; - for (var i in values) - { - var item_node = DOM.Node.CreateHTML(item_template_html); - var text_node = DOM.Node.FindWithClass(item_node, "ComboBoxPopupItemText"); - - item_node.Value = values[i]; - text_node.innerHTML = values[i]; - - this.Node.appendChild(item_node); - this.ValueNodes.push(item_node); - } - } - - - ComboBoxPopup.prototype.Show = function(selection_index) - { - // Initially match the position of the parent node - var pos = DOM.Node.GetPosition(this.ParentNode); - DOM.Node.SetPosition(this.Node, pos); - - // Take the width/z-index from the parent node - this.Node.style.width = this.ParentNode.offsetWidth; - this.Node.style.zIndex = this.ParentNode.style.zIndex + 1; - - // Setup event handlers - DOM.Event.AddHandler(document.body, "mousedown", this.CancelDelegate); - - // Show the popup so that the HTML layout engine kicks in before - // the layout info is used below - this.ParentNode.appendChild(this.Node); - - // Show/hide the tick image based on which node is selected - for (var i in this.ValueNodes) - { - var node = this.ValueNodes[i]; - var icon_node = DOM.Node.FindWithClass(node, "ComboBoxPopupItemIcon"); - - if (i == selection_index) - { - icon_node.style.display = "block"; - - // Also, shift the popup up so that the mouse is over the selected item and is highlighted - var item_pos = DOM.Node.GetPosition(this.ValueNodes[selection_index]); - var diff_pos = [ item_pos[0] - pos[0], item_pos[1] - pos[1] ]; - pos = [ pos[0] - diff_pos[0], pos[1] - diff_pos[1] ]; - } - else - { - icon_node.style.display = "none"; - } - } - - DOM.Node.SetPosition(this.Node, pos); - } - - - ComboBoxPopup.prototype.Hide = function() - { - DOM.Event.RemoveHandler(document.body, "mousedown", this.CancelDelegate); - this.ParentNode.removeChild(this.Node); - } - - - function SelectItem(self, evt) - { - // Search for which item node is being clicked on - var node = DOM.Event.GetNode(evt); - for (var i in self.ValueNodes) - { - var value_node = self.ValueNodes[i]; - if (DOM.Node.Contains(node, value_node)) - { - // Set the value on the combo box - self.ComboBox.SetValue(value_node.Value); - self.Hide(); - break; - } - } - } - - - function Cancel(self, evt) - { - // Don't cancel if the mouse up is anywhere on the popup or combo box - var node = DOM.Event.GetNode(evt); - if (!DOM.Node.Contains(node, self.Node) && - !DOM.Node.Contains(node, self.ParentNode)) - { - self.Hide(); - } - - - DOM.Event.StopAll(evt); - } - - - return ComboBoxPopup; -})(); - - -WM.ComboBox = (function() -{ - var template_html = " \ -
\ -
\ -
\ -
\ -
"; - - - function ComboBox() - { - this.OnChange = null; - - // Create the template node and locate key nodes - this.Node = DOM.Node.CreateHTML(template_html); - this.TextNode = DOM.Node.FindWithClass(this.Node, "ComboBoxText"); - - // Create a reusable popup - this.Popup = new WM.ComboBoxPopup(this); - - // Set an empty set of values - this.SetValues([]); - this.SetValue("<empty>"); - - // Create the mouse press event handlers - DOM.Event.AddHandler(this.Node, "mousedown", Bind(OnMouseDown, this)); - this.OnMouseOutDelegate = Bind(OnMouseUp, this, false); - this.OnMouseUpDelegate = Bind(OnMouseUp, this, true); - } - - - ComboBox.prototype.SetOnChange = function(on_change) - { - this.OnChange = on_change; - } - - - ComboBox.prototype.SetValues = function(values) - { - this.Values = values; - this.Popup.SetValues(values); - } - - - ComboBox.prototype.SetValue = function(value) - { - // Set the value and its HTML rep - var old_value = this.Value; - this.Value = value; - this.TextNode.innerHTML = value; - - // Call change handler - if (this.OnChange) - this.OnChange(value, old_value); - } - - - ComboBox.prototype.GetValue = function() - { - return this.Value; - } - - - function OnMouseDown(self, evt) - { - // If this check isn't made, the click will trigger from the popup, too - var node = DOM.Event.GetNode(evt); - if (DOM.Node.Contains(node, self.Node)) - { - // Add the depression class and activate release handlers - DOM.Node.AddClass(self.Node, "ComboBoxPressed"); - DOM.Event.AddHandler(self.Node, "mouseout", self.OnMouseOutDelegate); - DOM.Event.AddHandler(self.Node, "mouseup", self.OnMouseUpDelegate); - - DOM.Event.StopAll(evt); - } - } - - - function OnMouseUp(self, confirm, evt) - { - // Remove depression class and remove release handlers - DOM.Node.RemoveClass(self.Node, "ComboBoxPressed"); - DOM.Event.RemoveHandler(self.Node, "mouseout", self.OnMouseOutDelegate); - DOM.Event.RemoveHandler(self.Node, "mouseup", self.OnMouseUpDelegate); - - // If this is a confirmed press and there are some values in the list, show the popup - if (confirm && self.Values.length > 0) - { - var selection_index = self.Values.indexOf(self.Value); - self.Popup.Show(selection_index); - } - - DOM.Event.StopAll(evt); - } - - - return ComboBox; -})(); diff --git a/profiler/vis/extern/BrowserLib/WindowManager/Code/Container.js b/profiler/vis/extern/BrowserLib/WindowManager/Code/Container.js deleted file mode 100644 index 9a4598c..0000000 --- a/profiler/vis/extern/BrowserLib/WindowManager/Code/Container.js +++ /dev/null @@ -1,48 +0,0 @@ - -namespace("WM"); - - -WM.Container = (function() -{ - var template_html = "
"; - - - function Container(x, y, w, h) - { - // Create a simple container node - this.Node = DOM.Node.CreateHTML(template_html); - this.SetPosition(x, y); - this.SetSize(w, h); - } - - - Container.prototype.SetPosition = function(x, y) - { - this.Position = [ x, y ]; - DOM.Node.SetPosition(this.Node, this.Position); - } - - - Container.prototype.SetSize = function(w, h) - { - this.Size = [ w, h ]; - DOM.Node.SetSize(this.Node, this.Size); - } - - - Container.prototype.AddControlNew = function(control) - { - control.ParentNode = this.Node; - this.Node.appendChild(control.Node); - return control; - } - - - Container.prototype.ClearControls = function() - { - this.Node.innerHTML = ""; - } - - - return Container; -})(); \ No newline at end of file diff --git a/profiler/vis/extern/BrowserLib/WindowManager/Code/EditBox.js b/profiler/vis/extern/BrowserLib/WindowManager/Code/EditBox.js deleted file mode 100644 index 111898c..0000000 --- a/profiler/vis/extern/BrowserLib/WindowManager/Code/EditBox.js +++ /dev/null @@ -1,119 +0,0 @@ - -namespace("WM"); - - -WM.EditBox = (function() -{ - var template_html = " \ -
\ -
Label
\ - \ -
"; - - - function EditBox(x, y, w, h, label, text) - { - this.ChangeHandler = null; - - // Create node and locate its internal nodes - this.Node = DOM.Node.CreateHTML(template_html); - this.LabelNode = DOM.Node.FindWithClass(this.Node, "EditBoxLabel"); - this.EditNode = DOM.Node.FindWithClass(this.Node, "EditBox"); - - // Set label and value - this.LabelNode.innerHTML = label; - this.SetValue(text); - - this.SetPosition(x, y); - this.SetSize(w, h); - - this.PreviousValue = ""; - - // Hook up the event handlers - DOM.Event.AddHandler(this.EditNode, "focus", Bind(OnFocus, this)); - DOM.Event.AddHandler(this.EditNode, "keypress", Bind(OnKeyPress, this)); - DOM.Event.AddHandler(this.EditNode, "keydown", Bind(OnKeyDown, this)); - } - - - EditBox.prototype.SetPosition = function(x, y) - { - this.Position = [ x, y ]; - DOM.Node.SetPosition(this.Node, this.Position); - } - - - EditBox.prototype.SetSize = function(w, h) - { - this.Size = [ w, h ]; - DOM.Node.SetSize(this.EditNode, this.Size); - } - - - EditBox.prototype.SetChangeHandler = function(handler) - { - this.ChangeHandler = handler; - } - - - EditBox.prototype.SetValue = function(value) - { - if (this.EditNode) - this.EditNode.value = value; - } - - - EditBox.prototype.GetValue = function() - { - if (this.EditNode) - return this.EditNode.value; - - return null; - } - - - EditBox.prototype.LoseFocus = function() - { - if (this.EditNode) - this.EditNode.blur(); - } - - - function OnFocus(self, evt) - { - // Backup on focus - self.PreviousValue = self.EditNode.value; - } - - - function OnKeyPress(self, evt) - { - // Allow enter to confirm the text only when there's data - if (evt.keyCode == 13 && self.EditNode.value != "" && self.ChangeHandler) - { - var focus = self.ChangeHandler(self.EditNode); - if (!focus) - self.EditNode.blur(); - self.PreviousValue = ""; - } - } - - - function OnKeyDown(self, evt) - { - // Allow escape to cancel any text changes - if (evt.keyCode == 27) - { - // On initial edit of the input, escape should NOT replace with the empty string - if (self.PreviousValue != "") - { - self.EditNode.value = self.PreviousValue; - } - - self.EditNode.blur(); - } - } - - - return EditBox; -})(); diff --git a/profiler/vis/extern/BrowserLib/WindowManager/Code/Grid.js b/profiler/vis/extern/BrowserLib/WindowManager/Code/Grid.js deleted file mode 100644 index 1c4ef2e..0000000 --- a/profiler/vis/extern/BrowserLib/WindowManager/Code/Grid.js +++ /dev/null @@ -1,248 +0,0 @@ - -namespace("WM"); - - -WM.GridRows = (function() -{ - function GridRows(parent_object) - { - this.ParentObject = parent_object; - - // Array of rows in the order they were added - this.Rows = [ ]; - - // Collection of custom row indexes for fast lookup - this.Indexes = { }; - } - - - GridRows.prototype.AddIndex = function(cell_field_name) - { - var index = { }; - - // Go through existing rows and add to the index - for (var i in this.Rows) - { - var row = this.Rows[i]; - if (cell_field_name in row.CellData) - { - var cell_field = row.CellData[cell_field_name]; - index[cell_field] = row; - } - } - - this.Indexes[cell_field_name] = index; - } - - - GridRows.prototype.ClearIndex = function(index_name) - { - this.Indexes[index_name] = { }; - } - - GridRows.prototype.AddRowToIndex = function(index_name, cell_data, row) - { - this.Indexes[index_name][cell_data] = row; - } - - - GridRows.prototype.Add = function(cell_data, row_classes, cell_classes) - { - var row = new WM.GridRow(this.ParentObject, cell_data, row_classes, cell_classes); - this.Rows.push(row); - return row; - } - - - GridRows.prototype.GetBy = function(cell_field_name, cell_data) - { - var index = this.Indexes[cell_field_name]; - return index[cell_data]; - } - - - GridRows.prototype.Clear = function() - { - // Remove all node references from the parent - for (var i in this.Rows) - { - var row = this.Rows[i]; - row.Parent.BodyNode.removeChild(row.Node); - } - - // Clear all indexes - for (var i in this.Indexes) - this.Indexes[i] = { }; - - this.Rows = [ ]; - } - - - return GridRows; -})(); - - -WM.GridRow = (function() -{ - var template_html = "
"; - - - // - // 'cell_data' is an object with a variable number of fields. - // Any fields prefixed with an underscore are hidden. - // - function GridRow(parent, cell_data, row_classes, cell_classes) - { - // Setup data - this.Parent = parent; - this.IsOpen = true; - this.AnimHandle = null; - this.Rows = new WM.GridRows(this); - this.CellData = cell_data; - this.CellNodes = { } - - // Create the main row node - this.Node = DOM.Node.CreateHTML(template_html); - if (row_classes) - DOM.Node.AddClass(this.Node, row_classes); - - // Embed a pointer to the row in the root node so that it can be clicked - this.Node.GridRow = this; - - // Create nodes for each required cell - for (var attr in this.CellData) - { - if (this.CellData.hasOwnProperty(attr)) - { - var data = this.CellData[attr]; - - // Update any grid row index references - if (attr in parent.Rows.Indexes) - parent.Rows.AddRowToIndex(attr, data, this); - - // Hide any cells with underscore prefixes - if (attr[0] == "_") - continue; - - // Create a node for the cell and add any custom classes - var node = DOM.Node.AppendHTML(this.Node, "
"); - if (cell_classes && attr in cell_classes) - DOM.Node.AddClass(node, cell_classes[attr]); - this.CellNodes[attr] = node; - - // If this is a Window Control, add its node to the cell - if (data instanceof Object && "Node" in data && DOM.Node.IsNode(data.Node)) - { - data.ParentNode = node; - node.appendChild(data.Node); - } - - else - { - // Otherwise just assign the data as text - node.innerHTML = data; - } - } - } - - // Add the body node for any children - if (!this.Parent.BodyNode) - this.Parent.BodyNode = DOM.Node.AppendHTML(this.Parent.Node, "
"); - - // Add the row to the parent - this.Parent.BodyNode.appendChild(this.Node); - } - - - GridRow.prototype.Open = function() - { - // Don't allow open while animating - if (this.AnimHandle == null || this.AnimHandle.Complete) - { - this.IsOpen = true; - - // Kick off open animation - var node = this.BodyNode; - this.AnimHandle = Anim.Animate( - function (val) { DOM.Node.SetHeight(node, val) }, - 0, this.Height, 0.2); - } - } - - - GridRow.prototype.Close = function() - { - // Don't allow close while animating - if (this.AnimHandle == null || this.AnimHandle.Complete) - { - this.IsOpen = false; - - // Record height for the next open request - this.Height = this.BodyNode.offsetHeight; - - // Kick off close animation - var node = this.BodyNode; - this.AnimHandle = Anim.Animate( - function (val) { DOM.Node.SetHeight(node, val) }, - this.Height, 0, 0.2); - } - } - - - GridRow.prototype.Toggle = function() - { - if (this.IsOpen) - this.Close(); - else - this.Open(); - } - - - return GridRow; -})(); - - -WM.Grid = (function() -{ - var template_html = " \ -
\ -
\ -
"; - - - function Grid() - { - this.Rows = new WM.GridRows(this); - - this.Node = DOM.Node.CreateHTML(template_html); - this.BodyNode = DOM.Node.FindWithClass(this.Node, "GridBody"); - - DOM.Event.AddHandler(this.Node, "dblclick", OnDblClick); - - var mouse_wheel_event = (/Firefox/i.test(navigator.userAgent)) ? "DOMMouseScroll" : "mousewheel"; - DOM.Event.AddHandler(this.Node, mouse_wheel_event, Bind(OnMouseScroll, this)); - } - - function OnDblClick(evt) - { - // Clicked on a header? - var node = DOM.Event.GetNode(evt); - if (DOM.Node.HasClass(node, "GridRowName")) - { - // Toggle rows open/close - var row = node.parentNode.GridRow; - if (row) - row.Toggle(); - } - } - - - function OnMouseScroll(self, evt) - { - var mouse_state = new Mouse.State(evt); - self.Node.scrollTop -= mouse_state.WheelDelta * 20; - } - - - return Grid; -})(); diff --git a/profiler/vis/extern/BrowserLib/WindowManager/Code/Label.js b/profiler/vis/extern/BrowserLib/WindowManager/Code/Label.js deleted file mode 100644 index dd2d74f..0000000 --- a/profiler/vis/extern/BrowserLib/WindowManager/Code/Label.js +++ /dev/null @@ -1,31 +0,0 @@ - -namespace("WM"); - - -WM.Label = (function() -{ - var template_html = "
"; - - - function Label(x, y, text) - { - // Create the node - this.Node = DOM.Node.CreateHTML(template_html); - - // Allow position to be optional - if (x != null && y != null) - DOM.Node.SetPosition(this.Node, [x, y]); - - this.SetText(text); - } - - - Label.prototype.SetText = function(text) - { - if (text != null) - this.Node.innerHTML = text; - } - - - return Label; -})(); \ No newline at end of file diff --git a/profiler/vis/extern/BrowserLib/WindowManager/Code/Treeview.js b/profiler/vis/extern/BrowserLib/WindowManager/Code/Treeview.js deleted file mode 100644 index 539d080..0000000 --- a/profiler/vis/extern/BrowserLib/WindowManager/Code/Treeview.js +++ /dev/null @@ -1,352 +0,0 @@ - -namespace("WM"); - - -WM.Treeview = (function() -{ - var Margin = 10; - - - var tree_template_html = " \ -
\ -
\ -
\ -
\ -
\ -
\ -
"; - - - var item_template_html = " \ -
\ - \ -
\ -
\ -
\ -
\ -
"; - - - // TODO: Remove parent_node (required for stuff that doesn't use the WM yet) - function Treeview(x, y, width, height, parent_node) - { - // Cache initialisation options - this.ParentNode = parent_node; - this.Position = [ x, y ]; - this.Size = [ width, height ]; - - this.Node = null; - this.ScrollbarNode = null; - this.SelectedItem = null; - this.ContentsNode = null; - - // Setup options - this.HighlightOnHover = false; - this.EnableScrollbar = true; - this.HorizontalLayoutDepth = 1; - - // Generate an empty tree - this.Clear(); - } - - - Treeview.prototype.SetHighlightOnHover = function(highlight) - { - this.HighlightOnHover = highlight; - } - - - Treeview.prototype.SetEnableScrollbar = function(enable) - { - this.EnableScrollbar = enable; - } - - - Treeview.prototype.SetHorizontalLayoutDepth = function(depth) - { - this.HorizontalLayoutDepth = depth; - } - - - Treeview.prototype.SetNodeSelectedHandler = function(handler) - { - this.NodeSelectedHandler = handler; - } - - - Treeview.prototype.Clear = function() - { - this.RootItem = new WM.TreeviewItem(this, null, null, null, null); - this.GenerateHTML(); - } - - - Treeview.prototype.Root = function() - { - return this.RootItem; - } - - - Treeview.prototype.ClearSelection = function() - { - if (this.SelectedItem != null) - { - DOM.Node.RemoveClass(this.SelectedItem.Node, "TreeviewItemSelected"); - this.SelectedItem = null; - } - } - - - Treeview.prototype.SelectItem = function(item, mouse_pos) - { - // Notify the select handler - if (this.NodeSelectedHandler) - this.NodeSelectedHandler(item.Node, this.SelectedItem, item, mouse_pos); - - // Remove highlight from the old selection - this.ClearSelection(); - - // Swap in new selection and apply highlight - this.SelectedItem = item; - DOM.Node.AddClass(this.SelectedItem.Node, "TreeviewItemSelected"); - } - - - Treeview.prototype.GenerateHTML = function() - { - // Clone the template and locate important nodes - var old_node = this.Node; - this.Node = DOM.Node.CreateHTML(tree_template_html); - this.ChildrenNode = DOM.Node.FindWithClass(this.Node, "TreeviewItemChildren"); - this.ScrollbarNode = DOM.Node.FindWithClass(this.Node, "TreeviewScrollbar"); - - DOM.Node.SetPosition(this.Node, this.Position); - DOM.Node.SetSize(this.Node, this.Size); - - // Generate the contents of the treeview - GenerateTree(this, this.ChildrenNode, this.RootItem.Children, 0); - - // Cross-browser (?) means of adding a mouse wheel handler - var mouse_wheel_event = (/Firefox/i.test(navigator.userAgent)) ? "DOMMouseScroll" : "mousewheel"; - DOM.Event.AddHandler(this.Node, mouse_wheel_event, Bind(OnMouseScroll, this)); - - DOM.Event.AddHandler(this.Node, "dblclick", Bind(OnMouseDoubleClick, this)); - DOM.Event.AddHandler(this.Node, "mousedown", Bind(OnMouseDown, this)); - DOM.Event.AddHandler(this.Node, "mouseup", OnMouseUp); - - // Swap in the newly generated control node if it's already been attached to a parent - if (old_node && old_node.parentNode) - { - old_node.parentNode.removeChild(old_node); - this.ParentNode.appendChild(this.Node); - } - - if (this.EnableScrollbar) - { - this.UpdateScrollbar(); - DOM.Event.AddHandler(this.ScrollbarNode, "mousedown", Bind(OnMouseDown_Scrollbar, this)); - DOM.Event.AddHandler(this.ScrollbarNode, "mouseup", Bind(OnMouseUp_Scrollbar, this)); - DOM.Event.AddHandler(this.ScrollbarNode, "mouseout", Bind(OnMouseUp_Scrollbar, this)); - DOM.Event.AddHandler(this.ScrollbarNode, "mousemove", Bind(OnMouseMove_Scrollbar, this)); - } - - else - { - DOM.Node.Hide(DOM.Node.FindWithClass(this.Node, "TreeviewScrollbarInset")); - } - } - - - Treeview.prototype.UpdateScrollbar = function() - { - if (!this.EnableScrollbar) - return; - - var scrollbar_scale = Math.min((this.Node.offsetHeight - Margin * 2) / this.ChildrenNode.offsetHeight, 1); - this.ScrollbarNode.style.height = parseInt(scrollbar_scale * 100) + "%"; - - // Shift the scrollbar container along with the parent window - this.ScrollbarNode.parentNode.style.top = this.Node.scrollTop; - - var scroll_fraction = this.Node.scrollTop / (this.Node.scrollHeight - this.Node.offsetHeight); - var max_height = this.Node.offsetHeight - Margin; - var max_scrollbar_offset = max_height - this.ScrollbarNode.offsetHeight; - var scrollbar_offset = scroll_fraction * max_scrollbar_offset; - this.ScrollbarNode.style.top = scrollbar_offset; - } - - - function GenerateTree(self, parent_node, items, depth) - { - if (items.length == 0) - return null; - - for (var i in items) - { - var item = items[i]; - - // Create the node for this item and locate important nodes - var node = DOM.Node.CreateHTML(item_template_html); - var img = DOM.Node.FindWithClass(node, "TreeviewItemImage"); - var text = DOM.Node.FindWithClass(node, "TreeviewItemText"); - var children = DOM.Node.FindWithClass(node, "TreeviewItemChildren"); - - // Attach the item to the node - node.TreeviewItem = item; - item.Node = node; - - // Add the class which highlights selection on hover - if (self.HighlightOnHover) - DOM.Node.AddClass(node, "TreeviewItemHover"); - - // Instruct the children to wrap around - if (depth >= self.HorizontalLayoutDepth) - node.style.cssFloat = "left"; - - if (item.OpenImage == null || item.CloseImage == null) - { - // If there no images, remove the image node - node.removeChild(img); - } - else - { - // Set the image source to open - img.src = item.OpenImage.src; - img.style.width = item.OpenImage.width; - img.style.height = item.OpenImage.height; - item.ImageNode = img; - } - - // Setup the text to display - text.innerHTML = item.Label; - - // Add the div to the parent and recurse into children - parent_node.appendChild(node); - GenerateTree(self, children, item.Children, depth + 1); - item.ChildrenNode = children; - } - - // Clear the wrap-around - if (depth >= self.HorizontalLayoutDepth) - DOM.Node.AppendClearFloat(parent_node.parentNode); - } - - - function OnMouseScroll(self, evt) - { - // Get mouse wheel movement - var delta = evt.detail ? evt.detail * -1 : evt.wheelDelta; - delta *= 8; - - // Scroll the main window with wheel movement and clamp - self.Node.scrollTop -= delta; - self.Node.scrollTop = Math.min(self.Node.scrollTop, (self.ChildrenNode.offsetHeight - self.Node.offsetHeight) + Margin * 2); - - self.UpdateScrollbar(); - } - - - function OnMouseDoubleClick(self, evt) - { - DOM.Event.StopDefaultAction(evt); - - // Get the tree view item being clicked, if any - var node = DOM.Event.GetNode(evt); - var tvitem = GetTreeviewItemFromNode(self, node); - if (tvitem == null) - return; - - if (tvitem.Children.length) - tvitem.Toggle(); - } - - - function OnMouseDown(self, evt) - { - DOM.Event.StopDefaultAction(evt); - - // Get the tree view item being clicked, if any - var node = DOM.Event.GetNode(evt); - var tvitem = GetTreeviewItemFromNode(self, node); - if (tvitem == null) - return; - - // If clicking on the image, expand any children - if (node.tagName == "IMG" && tvitem.Children.length) - { - tvitem.Toggle(); - } - - else - { - var mouse_pos = DOM.Event.GetMousePosition(evt); - self.SelectItem(tvitem, mouse_pos); - } - } - - - function OnMouseUp(evt) - { - // Event handler used merely to stop events bubbling up to containers - DOM.Event.StopPropagation(evt); - } - - - function OnMouseDown_Scrollbar(self, evt) - { - self.ScrollbarHeld = true; - - // Cache the mouse height relative to the scrollbar - self.LastY = evt.clientY; - self.ScrollY = self.Node.scrollTop; - - DOM.Node.AddClass(self.ScrollbarNode, "TreeviewScrollbarHeld"); - DOM.Event.StopDefaultAction(evt); - } - - - function OnMouseUp_Scrollbar(self, evt) - { - self.ScrollbarHeld = false; - DOM.Node.RemoveClass(self.ScrollbarNode, "TreeviewScrollbarHeld"); - } - - - function OnMouseMove_Scrollbar(self, evt) - { - if (self.ScrollbarHeld) - { - var delta_y = evt.clientY - self.LastY; - self.LastY = evt.clientY; - - var max_height = self.Node.offsetHeight - Margin; - var max_scrollbar_offset = max_height - self.ScrollbarNode.offsetHeight; - var max_contents_scroll = self.Node.scrollHeight - self.Node.offsetHeight; - var scale = max_contents_scroll / max_scrollbar_offset; - - // Increment the local float variable and assign, as scrollTop is of type int - self.ScrollY += delta_y * scale; - self.Node.scrollTop = self.ScrollY; - self.Node.scrollTop = Math.min(self.Node.scrollTop, (self.ChildrenNode.offsetHeight - self.Node.offsetHeight) + Margin * 2); - - self.UpdateScrollbar(); - } - } - - - function GetTreeviewItemFromNode(self, node) - { - // Walk up toward the tree view node looking for this first item - while (node && node != self.Node) - { - if ("TreeviewItem" in node) - return node.TreeviewItem; - - node = node.parentNode; - } - - return null; - } - - return Treeview; -})(); diff --git a/profiler/vis/extern/BrowserLib/WindowManager/Code/TreeviewItem.js b/profiler/vis/extern/BrowserLib/WindowManager/Code/TreeviewItem.js deleted file mode 100644 index fc04088..0000000 --- a/profiler/vis/extern/BrowserLib/WindowManager/Code/TreeviewItem.js +++ /dev/null @@ -1,109 +0,0 @@ - -namespace("WM"); - - -WM.TreeviewItem = (function() -{ - function TreeviewItem(treeview, name, data, open_image, close_image) - { - // Assign members - this.Treeview = treeview; - this.Label = name; - this.Data = data; - this.OpenImage = open_image; - this.CloseImage = close_image; - - this.Children = [ ]; - - // The HTML node wrapping the item and its children - this.Node = null; - - // The HTML node storing the image for the open/close state feedback - this.ImageNode = null; - - // The HTML node storing just the children - this.ChildrenNode = null; - - // Animation handle for opening and closing the child nodes, only used - // if the tree view item as children - this.AnimHandle = null; - - // Open state of the item - this.IsOpen = true; - } - - - TreeviewItem.prototype.AddItem = function(name, data, open_image, close_image) - { - var item = new WM.TreeviewItem(this.Treeview, name, data, open_image, close_image); - this.Children.push(item); - return item; - } - - - TreeviewItem.prototype.Open = function() - { - if (this.AnimHandle == null || this.AnimHandle.Complete) - { - // Swap to the open state - this.IsOpen = true; - if (this.ImageNode != null && this.OpenImage != null) - this.ImageNode.src = this.OpenImage.src; - - // Cache for closure binding - var child_node = this.ChildrenNode; - var end_height = this.StartHeight; - var treeview = this.Treeview; - - // Reveal the children and animate their height to max - this.ChildrenNode.style.display = "block"; - this.AnimHandle = Anim.Animate( - function (val) { DOM.Node.SetHeight(child_node, val) }, - 0, end_height, 0.2, - function() { treeview.UpdateScrollbar(); }); - - // Fade the children in - Anim.Animate(function(val) { DOM.Node.SetOpacity(child_node, val) }, 0, 1, 0.2); - } - } - - - TreeviewItem.prototype.Close = function() - { - if (this.AnimHandle == null || this.AnimHandle.Complete) - { - // Swap to the close state - this.IsOpen = false; - if (this.ImageNode != null && this.CloseImage != null) - this.ImageNode.src = this.CloseImage.src; - - // Cache for closure binding - var child_node = this.ChildrenNode; - var treeview = this.Treeview; - - // Mark the height of the item for reload later - this.StartHeight = child_node.offsetHeight; - - // Shrink the height of the children and hide them upon completion - this.AnimHandle = Anim.Animate( - function (val) { DOM.Node.SetHeight(child_node, val) }, - this.ChildrenNode.offsetHeight, 0, 0.2, - function() { child_node.style.display = "none"; treeview.UpdateScrollbar(); }); - - // Fade the children out - Anim.Animate(function(val) { DOM.Node.SetOpacity(child_node, val) }, 1, 0, 0.2); - } - } - - - TreeviewItem.prototype.Toggle = function() - { - if (this.IsOpen) - this.Close(); - else - this.Open(); - } - - - return TreeviewItem; -})(); diff --git a/profiler/vis/extern/BrowserLib/WindowManager/Code/Window.js b/profiler/vis/extern/BrowserLib/WindowManager/Code/Window.js deleted file mode 100644 index 3a7ac94..0000000 --- a/profiler/vis/extern/BrowserLib/WindowManager/Code/Window.js +++ /dev/null @@ -1,318 +0,0 @@ - -namespace("WM"); - - -WM.Window = (function() -{ - var template_html = multiline(function(){/* \ -
-
-
Window Title Bar
-
-
-
-
-
-
- */}); - - - function Window(manager, title, x, y, width, height, parent_node, user_data) - { - this.Manager = manager; - this.ParentNode = parent_node || document.body; - this.userData = user_data; - this.OnMove = null; - this.OnResize = null; - this.Visible = false; - this.AnimatedShow = false; - - // Clone the window template and locate key nodes within it - this.Node = DOM.Node.CreateHTML(template_html); - this.TitleBarNode = DOM.Node.FindWithClass(this.Node, "WindowTitleBar"); - this.TitleBarTextNode = DOM.Node.FindWithClass(this.Node, "WindowTitleBarText"); - this.TitleBarCloseNode = DOM.Node.FindWithClass(this.Node, "WindowTitleBarClose"); - this.ResizeHandleNode = DOM.Node.FindWithClass(this.Node, "WindowResizeHandle"); - this.BodyNode = DOM.Node.FindWithClass(this.Node, "WindowBody"); - - // Setup the position and dimensions of the window - this.SetPosition(x, y); - this.SetSize(width, height); - - // Set the title text - this.TitleBarTextNode.innerHTML = title; - - // Hook up event handlers - DOM.Event.AddHandler(this.Node, "mousedown", Bind(this, "SetTop")); - DOM.Event.AddHandler(this.TitleBarNode, "mousedown", Bind(this, "BeginMove")); - DOM.Event.AddHandler(this.ResizeHandleNode, "mousedown", Bind(this, "BeginResize")); - DOM.Event.AddHandler(this.TitleBarCloseNode, "mouseup", Bind(this, "Hide")); - - // Create delegates for removable handlers - this.MoveDelegate = Bind(this, "Move"); - this.EndMoveDelegate = Bind(this, "EndMove") - this.ResizeDelegate = Bind(this, "Resize"); - this.EndResizeDelegate = Bind(this, "EndResize"); - } - - Window.prototype.SetOnMove = function(on_move) - { - this.OnMove = on_move; - } - - Window.prototype.SetOnResize = function(on_resize) - { - this.OnResize = on_resize; - } - - - Window.prototype.Show = function() - { - if (this.Node.parentNode != this.ParentNode) - { - this.ShowNoAnim(); - Anim.Animate(Bind(this, "OpenAnimation"), 0, 1, 1); - } - } - - - Window.prototype.ShowNoAnim = function() - { - // Add to the document - this.ParentNode.appendChild(this.Node); - this.AnimatedShow = false; - this.Visible = true; - } - - - Window.prototype.Hide = function(evt) - { - if (this.Node.parentNode == this.ParentNode && evt.button == 0) - { - if (this.AnimatedShow) - { - // Trigger animation that ends with removing the window from the document - Anim.Animate( - Bind(this, "CloseAnimation"), - 0, 1, 0.25, - Bind(this, "HideNoAnim")); - } - else - { - this.HideNoAnim(); - } - } - } - - - Window.prototype.HideNoAnim = function() - { - if (this.Node.parentNode == this.ParentNode) - { - // Remove node - this.ParentNode.removeChild(this.Node); - this.Visible = false; - } - } - - - Window.prototype.Close = function() - { - this.HideNoAnim(); - this.Manager.RemoveWindow(this); - } - - - Window.prototype.SetTop = function() - { - this.Manager.SetTopWindow(this); - } - - - - Window.prototype.SetTitle = function(title) - { - this.TitleBarTextNode.innerHTML = title; - } - - - // TODO: Update this - Window.prototype.AddControl = function(control) - { - // Get all arguments to this function and replace the first with this window node - var args = [].slice.call(arguments); - args[0] = this.BodyNode; - - // Create the control and call its Init method with the modified arguments - var instance = new control(); - instance.Init.apply(instance, args); - - return instance; - } - - - Window.prototype.AddControlNew = function(control) - { - control.ParentNode = this.BodyNode; - this.BodyNode.appendChild(control.Node); - return control; - } - - - Window.prototype.RemoveControl = function(control) - { - if (control.ParentNode == this.BodyNode) - { - control.ParentNode.removeChild(control.Node); - } - } - - - Window.prototype.Scale = function(t) - { - // Calculate window bounds centre/extents - var ext_x = this.Size[0] / 2; - var ext_y = this.Size[1] / 2; - var mid_x = this.Position[0] + ext_x; - var mid_y = this.Position[1] + ext_y; - - // Scale from the mid-point - DOM.Node.SetPosition(this.Node, [ mid_x - ext_x * t, mid_y - ext_y * t ]); - DOM.Node.SetSize(this.Node, [ this.Size[0] * t, this.Size[1] * t ]); - } - - - Window.prototype.OpenAnimation = function(val) - { - // Power ease in - var t = 1 - Math.pow(1 - val, 8); - this.Scale(t); - DOM.Node.SetOpacity(this.Node, 1 - Math.pow(1 - val, 8)); - this.AnimatedShow = true; - } - - - Window.prototype.CloseAnimation = function(val) - { - // Power ease out - var t = 1 - Math.pow(val, 4); - this.Scale(t); - DOM.Node.SetOpacity(this.Node, t); - } - - - Window.prototype.NotifyChange = function() - { - if (this.OnMove) - { - var pos = DOM.Node.GetPosition(this.Node); - this.OnMove(this, pos); - } - } - - - Window.prototype.BeginMove = function(evt) - { - // Calculate offset of the window from the mouse down position - var mouse_pos = DOM.Event.GetMousePosition(evt); - this.Offset = [ mouse_pos[0] - this.Position[0], mouse_pos[1] - this.Position[1] ]; - - // Dynamically add handlers for movement and release - DOM.Event.AddHandler(document, "mousemove", this.MoveDelegate); - DOM.Event.AddHandler(document, "mouseup", this.EndMoveDelegate); - - DOM.Event.StopDefaultAction(evt); - } - - - Window.prototype.Move = function(evt) - { - // Use the offset at the beginning of movement to drag the window around - var mouse_pos = DOM.Event.GetMousePosition(evt); - var offset = this.Offset; - var pos = [ mouse_pos[0] - offset[0], mouse_pos[1] - offset[1] ]; - this.SetPosition(pos[0], pos[1]); - - if (this.OnMove) - this.OnMove(this, pos); - - DOM.Event.StopDefaultAction(evt); - } - - - Window.prototype.EndMove = function(evt) - { - // Remove handlers added during mouse down - DOM.Event.RemoveHandler(document, "mousemove", this.MoveDelegate); - DOM.Event.RemoveHandler(document, "mouseup", this.EndMoveDelegate); - - DOM.Event.StopDefaultAction(evt); - } - - - Window.prototype.BeginResize = function(evt) - { - // Calculate offset of the window from the mouse down position - var mouse_pos = DOM.Event.GetMousePosition(evt); - this.MousePosBeforeResize = [ mouse_pos[0], mouse_pos[1] ]; - this.SizeBeforeResize = this.Size; - - // Dynamically add handlers for movement and release - DOM.Event.AddHandler(document, "mousemove", this.ResizeDelegate); - DOM.Event.AddHandler(document, "mouseup", this.EndResizeDelegate); - - DOM.Event.StopDefaultAction(evt); - } - - - Window.prototype.Resize = function(evt) - { - // Use the offset at the beginning of movement to drag the window around - var mouse_pos = DOM.Event.GetMousePosition(evt); - var offset = [ mouse_pos[0] - this.MousePosBeforeResize[0], mouse_pos[1] - this.MousePosBeforeResize[1] ]; - this.SetSize(this.SizeBeforeResize[0] + offset[0], this.SizeBeforeResize[1] + offset[1]); - - if (this.OnResize) - this.OnResize(this, this.Size); - - DOM.Event.StopDefaultAction(evt); - } - - - Window.prototype.EndResize = function(evt) - { - // Remove handlers added during mouse down - DOM.Event.RemoveHandler(document, "mousemove", this.ResizeDelegate); - DOM.Event.RemoveHandler(document, "mouseup", this.EndResizeDelegate); - - DOM.Event.StopDefaultAction(evt); - } - - - Window.prototype.SetPosition = function(x, y) - { - this.Position = [ x, y ]; - DOM.Node.SetPosition(this.Node, this.Position); - } - - - Window.prototype.SetSize = function(w, h) - { - w = Math.max(80, w); - h = Math.max(15, h); - this.Size = [ w, h ]; - DOM.Node.SetSize(this.Node, this.Size); - - if (this.OnResize) - this.OnResize(this, this.Size); - } - - - Window.prototype.GetZIndex = function() - { - return parseInt(this.Node.style.zIndex); - } - - - return Window; -})(); \ No newline at end of file diff --git a/profiler/vis/extern/BrowserLib/WindowManager/Code/WindowManager.js b/profiler/vis/extern/BrowserLib/WindowManager/Code/WindowManager.js deleted file mode 100644 index 49da617..0000000 --- a/profiler/vis/extern/BrowserLib/WindowManager/Code/WindowManager.js +++ /dev/null @@ -1,65 +0,0 @@ - -namespace("WM"); - - -WM.WindowManager = (function() -{ - function WindowManager() - { - // An empty list of windows under window manager control - this.Windows = [ ]; - } - - - WindowManager.prototype.AddWindow = function(title, x, y, width, height, parent_node, user_data) - { - // Create the window and add it to the list of windows - var wnd = new WM.Window(this, title, x, y, width, height, parent_node, user_data); - this.Windows.push(wnd); - - // Always bring to the top on creation - wnd.SetTop(); - - return wnd; - } - - - WindowManager.prototype.RemoveWindow = function(window) - { - // Remove from managed window list - var index = this.Windows.indexOf(window); - if (index != -1) - { - this.Windows.splice(index, 1); - } - } - - - WindowManager.prototype.SetTopWindow = function(top_wnd) - { - // Bring the window to the top of the window list - var top_wnd_index = this.Windows.indexOf(top_wnd); - if (top_wnd_index != -1) - this.Windows.splice(top_wnd_index, 1); - this.Windows.push(top_wnd); - - // Set a CSS z-index for each visible window from the bottom up - for (var i in this.Windows) - { - var wnd = this.Windows[i]; - if (!wnd.Visible) - continue; - - // Ensure there's space between each window for the elements inside to be sorted - var z = (parseInt(i) + 1) * 10; - wnd.Node.style.zIndex = z; - - // Notify window that its z-order has changed - wnd.NotifyChange(); - } - } - - - return WindowManager; - -})(); \ No newline at end of file diff --git a/profiler/vis/extern/BrowserLib/WindowManager/Styles/WindowManager.css b/profiler/vis/extern/BrowserLib/WindowManager/Styles/WindowManager.css deleted file mode 100644 index b9d71b3..0000000 --- a/profiler/vis/extern/BrowserLib/WindowManager/Styles/WindowManager.css +++ /dev/null @@ -1,652 +0,0 @@ - - -.notextsel -{ - /* Disable text selection so that it doesn't interfere with button-clicking */ - user-select: none; - -moz-user-select: none; /* Firefox */ - -ms-user-select: none; /* Internet Explorer */ - -khtml-user-select: none; /* KHTML browsers (e.g. Konqueror) */ - -webkit-user-select: none; /* Chrome, Safari, and Opera */ - -webkit-touch-callout: none; /* Disable Android and iOS callouts*/ - - /* Stops the text cursor over the label */ - cursor:default; -} - - - -/* ------------------------------------------------------------------------------------------------------------------ */ -/* Window Styles */ -/* ------------------------------------------------------------------------------------------------------------------ */ - -body -{ - /* Clip contents to browser window without adding scrollbars */ - overflow: hidden; -} - -.Window -{ - position:absolute; - - /* Clip all contents to the window border */ - overflow: hidden; - - background: #555; - - /*padding: 0px !important;*/ - - border-radius: 3px; - -moz-border-radius: 5px; - - -webkit-box-shadow: 1px 1px 1px #222, 1px 1px 1px #777 inset; - box-shadow: 1px 1px 1px #222, 1px 1px 1px #777 inset; -} - -/*:root -{ - --SideBarSize: 5px; -} - -.WindowBodyDebug -{ - color: #BBB; - font: 9px Verdana; - white-space: nowrap; -} - -.WindowSizeLeft -{ - position: absolute; - left: 0px; - top: 0px; - width: var(--SideBarSize); - height: 100%; -} -.WindowSizeRight -{ - position: absolute; - left: calc(100% - var(--SideBarSize)); - top:0px; - width: var(--SideBarSize); - height:100%; -} -.WindowSizeTop -{ - position: absolute; - left: 0px; - top: 0px; - width: 100%; - height: var(--SideBarSize); -} -.WindowSizeBottom -{ - position: absolute; - left: 0px; - top: calc(100% - var(--SideBarSize)); - width: 100%; - height: var(--SideBarSize); -}*/ - - -.Window_Transparent -{ - /* Set transparency changes to fade in/out */ - opacity: 0.5; - transition: opacity 0.5s ease-out; - -moz-transition: opacity 0.5s ease-out; - -webkit-transition: opacity 0.5s ease-out; -} - -.Window_Transparent:hover -{ - opacity: 1; -} - -.WindowTitleBar -{ - height: 17px; - cursor: move; - /*overflow: hidden;*/ - - border-bottom: 1px solid #303030; - border-radius: 5px; -} - -.WindowTitleBarText -{ - color: #BBB; - font: 9px Verdana; - /*white-space: nowrap;*/ - - padding: 3px; - cursor: move; -} - -.WindowTitleBarClose -{ - color: #999999; - font: 9px Verdana; - - padding: 3px; - cursor: default; -} - -.WindowTitleBarClose:hover { - color: #bbb; -} - -.WindowResizeHandle -{ - color: #999999; - font: 17px Verdana; - padding: 3px; - cursor: se-resize; - position: absolute; - bottom: -7px; - right: -3px; -} - -.WindowBody { - position: absolute; - /* overflow: hidden; */ - display: block; - padding: 10px; - border-top: 1px solid #606060; - top: 18px; - left: 0; - right: 0; - bottom: 0; - height: auto; -} - - - -/* ------------------------------------------------------------------------------------------------------------------ */ -/* Container Styles */ -/* ------------------------------------------------------------------------------------------------------------------ */ - - - -.Container -{ - /* Position relative to the parent window */ - position: absolute; - - /* Clip contents */ - /*overflow: hidden;*/ - - background:#2C2C2C; - - border: 1px black solid; - - /* Two inset box shadows to simulate depressing */ - -webkit-box-shadow: -1px -1px 1px #222 inset, 1px 1px 1px #222 inset; - box-shadow: -1px -1px 1px #222 inset, 1px 1px 1px #222 inset; -} - -/*.Panel -{*/ - /* Position relative to the parent window */ - /*position: absolute;*/ - - /* Clip contents */ - /*overflow: hidden; - - background:#2C2C2C; - - border: 1px black solid;*/ - - /* Two inset box shadows to simulate depressing */ - /*-webkit-box-shadow: -1px -1px 1px #222 inset, 1px 1px 1px #222 inset; - box-shadow: -1px -1px 1px #222 inset, 1px 1px 1px #222 inset;*/ -/*}*/ - - -/* ------------------------------------------------------------------------------------------------------------------ */ -/* Ruler Styles */ -/* ------------------------------------------------------------------------------------------------------------------ */ - - - -/*.Ruler -{ - position: absolute; - - border: dashed 1px; - - opacity: 0.35; -}*/ - - - -/* ------------------------------------------------------------------------------------------------------------------ */ -/* Treeview Styles */ -/* ------------------------------------------------------------------------------------------------------------------ */ - - - -.Treeview -{ - position: absolute; - - background:#2C2C2C; - border: 1px solid black; - overflow:hidden; - - /* Two inset box shadows to simulate depressing */ - -webkit-box-shadow: -1px -1px 1px #222 inset, 1px 1px 1px #222 inset; - box-shadow: -1px -1px 1px #222 inset, 1px 1px 1px #222 inset; -} - -.TreeviewItem -{ - margin:1px; - padding:2px; - border:solid 1px #2C2C2C; - background-color:#2C2C2C; -} - -.TreeviewItemImage -{ - float: left; -} - -.TreeviewItemText -{ - float: left; - margin-left:4px; -} - -.TreeviewItemChildren -{ - overflow: hidden; -} - -.TreeviewItemSelected -{ - background-color:#444; - border-color:#FFF; - - -webkit-transition: background-color 0.2s ease-in-out; - -moz-transition: background-color 0.2s ease-in-out; - -webkit-transition: border-color 0.2s ease-in-out; - -moz-transition: border-color 0.2s ease-in-out; -} - -/* Used to populate treeviews that want highlight on hover behaviour */ -.TreeviewItemHover -{ -} - -.TreeviewItemHover:hover -{ - background-color:#111; - border-color:#444; - - -webkit-transition: background-color 0.2s ease-in-out; - -moz-transition: background-color 0.2s ease-in-out; - -webkit-transition: border-color 0.2s ease-in-out; - -moz-transition: border-color 0.2s ease-in-out; -} - -.TreeviewScrollbarInset -{ - float: right; - - position:relative; - - height: 100%; - - /* CRAZINESS PART A: Trying to get the inset and scrollbar to have 100% height match its container */ - margin: -8px -8px 0 0; - padding: 0 1px 14px 1px; - - width:20px; - background:#2C2C2C; - border: 1px solid black; - - /* Two inset box shadows to simulate depressing */ - -webkit-box-shadow: -1px -1px 1px #222 inset, 1px 1px 1px #222 inset; - box-shadow: -1px -1px 1px #222 inset, 1px 1px 1px #222 inset; -} - -.TreeviewScrollbar -{ - position:relative; - - background:#2C2C2C; - border: 1px solid black; - - /* CRAZINESS PART B: Trying to get the inset and scrollbar to have 100% height match its container */ - padding: 0 0 10px 0; - margin: 1px 0 0 0; - - width: 18px; - height: 100%; - - border-radius:6px; - border-color:#000; - border-width:1px; - border-style:solid; - - /* The gradient for the button background */ - background-color:#666; - background: -webkit-gradient(linear, left top, left bottom, from(#666), to(#383838)); - background: -moz-linear-gradient(top, #666, #383838); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#666666', endColorstr='#383838'); - - /* A box shadow and inset box highlight */ - -webkit-box-shadow: 1px 1px 1px #222, 1px 1px 1px #777 inset; - box-shadow: 1px 1px 1px #222, 1px 1px 1px #777 inset; -} - -.TreeviewScrollbarHeld -{ - /* Reset the gradient to a full-colour background */ - background:#383838; - - /* Two inset box shadows to simulate depressing */ - -webkit-box-shadow: -1px -1px 1px #222 inset, 1px 1px 1px #222 inset; - box-shadow: -1px -1px 1px #222 inset, 1px 1px 1px #222 inset; -} - - - -/* ------------------------------------------------------------------------------------------------------------------ */ -/* Edit Box Styles */ -/* ------------------------------------------------------------------------------------------------------------------ */ - - - -.EditBoxContainer -{ - position: absolute; - padding:2px 10px 2px 10px; -} - -.EditBoxLabel -{ - float:left; - padding: 3px 4px 4px 4px; - font: 9px Verdana; -} - -.EditBox -{ - float:left; - - background:#666; - border: 1px solid; - border-radius: 6px; - padding: 3px 4px 3px 4px; - height: 20px; - - box-shadow: 1px 1px 1px #222 inset; - - transition: all 0.3s ease-in-out; -} - -.EditBox:focus -{ - background:#FFF; - outline:0; -} - - - -/* ------------------------------------------------------------------------------------------------------------------ */ -/* Label Styles */ -/* ------------------------------------------------------------------------------------------------------------------ */ - - - -.Label -{ - /* Position relative to the parent window */ - position:absolute; - - color: #BBB; - font: 9px Verdana; -} - - - -/* ------------------------------------------------------------------------------------------------------------------ */ -/* Combo Box Styles */ -/* ------------------------------------------------------------------------------------------------------------------ */ - - - -.ComboBox -{ - position:absolute; - - /* TEMP! */ - width:90px; - - /* Height is fixed to match the font */ - height:14px; - - /* Align the text within the combo box */ - padding: 1px 0 0 5px; - - /* Solid, rounded border */ - border: 1px solid #111; - border-radius: 5px; - - /* http://www.colorzilla.com/gradient-editor/#e3e3e3+0,c6c6c6+22,b7b7b7+33,afafaf+50,a7a7a7+67,797979+82,414141+100;Custom */ - background: #e3e3e3; - background: -moz-linear-gradient(top, #e3e3e3 0%, #c6c6c6 22%, #b7b7b7 33%, #afafaf 50%, #a7a7a7 67%, #797979 82%, #414141 100%); - background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#e3e3e3), color-stop(22%,#c6c6c6), color-stop(33%,#b7b7b7), color-stop(50%,#afafaf), color-stop(67%,#a7a7a7), color-stop(82%,#797979), color-stop(100%,#414141)); - background: -webkit-linear-gradient(top, #e3e3e3 0%,#c6c6c6 22%,#b7b7b7 33%,#afafaf 50%,#a7a7a7 67%,#797979 82%,#414141 100%); - background: -o-linear-gradient(top, #e3e3e3 0%,#c6c6c6 22%,#b7b7b7 33%,#afafaf 50%,#a7a7a7 67%,#797979 82%,#414141 100%); - background: -ms-linear-gradient(top, #e3e3e3 0%,#c6c6c6 22%,#b7b7b7 33%,#afafaf 50%,#a7a7a7 67%,#797979 82%,#414141 100%); - background: linear-gradient(top, #e3e3e3 0%,#c6c6c6 22%,#b7b7b7 33%,#afafaf 50%,#a7a7a7 67%,#797979 82%,#414141 100%); - filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#e3e3e3', endColorstr='#414141',GradientType=0 ); -} - -.ComboBoxPressed -{ - /* The reverse of the default background, simulating depression */ - background: #414141; - background: -moz-linear-gradient(top, #414141 0%, #797979 18%, #a7a7a7 33%, #afafaf 50%, #b7b7b7 67%, #c6c6c6 78%, #e3e3e3 100%); - background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#414141), color-stop(18%,#797979), color-stop(33%,#a7a7a7), color-stop(50%,#afafaf), color-stop(67%,#b7b7b7), color-stop(78%,#c6c6c6), color-stop(100%,#e3e3e3)); - background: -webkit-linear-gradient(top, #414141 0%,#797979 18%,#a7a7a7 33%,#afafaf 50%,#b7b7b7 67%,#c6c6c6 78%,#e3e3e3 100%); - background: -o-linear-gradient(top, #414141 0%,#797979 18%,#a7a7a7 33%,#afafaf 50%,#b7b7b7 67%,#c6c6c6 78%,#e3e3e3 100%); - background: -ms-linear-gradient(top, #414141 0%,#797979 18%,#a7a7a7 33%,#afafaf 50%,#b7b7b7 67%,#c6c6c6 78%,#e3e3e3 100%); - background: linear-gradient(top, #414141 0%,#797979 18%,#a7a7a7 33%,#afafaf 50%,#b7b7b7 67%,#c6c6c6 78%,#e3e3e3 100%); - filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#414141', endColorstr='#e3e3e3',GradientType=0 ); -} - -.ComboBoxText -{ - /* Text info */ - color: #000; - font: 9px Verdana; - - float:left; -} - -.ComboBoxIcon -{ - /* Push the image to the far right */ - float:right; - - /* Align the image with the combo box */ - padding: 2px 5px 0 0; -} - -.ComboBoxPopup -{ - position: fixed; - - background: #CCC; - - border-radius: 5px; - - padding: 1px 0 1px 0; -} - -.ComboBoxPopupItem -{ - /* Text info */ - color: #000; - font: 9px Verdana; - - padding: 1px 1px 1px 5px; - - border-bottom: 1px solid #AAA; - border-top: 1px solid #FFF; -} - -.ComboBoxPopupItemText -{ - float:left; -} - -.ComboBoxPopupItemIcon -{ - /* Push the image to the far right */ - float:right; - - /* Align the image with the combo box */ - padding: 2px 5px 0 0; -} - -.ComboBoxPopupItem:first-child -{ - border-top: 0px; -} - -.ComboBoxPopupItem:last-child -{ - border-bottom: 0px; -} - -.ComboBoxPopupItem:hover -{ - color:#FFF; - background: #2036E1; -} - - - -/* ------------------------------------------------------------------------------------------------------------------ */ -/* Grid Styles */ -/* ------------------------------------------------------------------------------------------------------------------ */ - - -.Grid { - overflow: auto; - background: #333; - height: 100%; - border-radius: 2px; -} - -.GridBody -{ - overflow-x: auto; - overflow-y: auto; - height: inherit; -} - -.GridRow -{ - display: inline-block; - white-space: nowrap; - - background:rgb(48, 48, 48); - - color: #BBB; - font: 9px Verdana; - - padding: 2px; -} - -.GridRow.GridGroup -{ - padding: 0px; -} - -.GridRow:nth-child(odd) -{ - background:#333; -} - -.GridRowCell -{ - display: inline-block; -} -.GridRowCell.GridGroup -{ - color: #BBB; - - /* Override default from name */ - width: 100%; - - padding: 1px 1px 1px 2px; - border: 1px solid; - border-radius: 2px; - - border-top-color:#555; - border-left-color:#555; - border-bottom-color:#111; - border-right-color:#111; - - background: #222; -} - -.GridRowBody -{ - /* Clip all contents for show/hide group*/ - overflow: hidden; - - /* Crazy CSS rules: controls for properties don't clip if this isn't set on this parent */ - position: relative; -} - - - -/* ------------------------------------------------------------------------------------------------------------------ */ -/* Button Styles */ -/* ------------------------------------------------------------------------------------------------------------------ */ - - - -.Button -{ - /* Position relative to the parent window */ - position:absolute; - - border-radius:4px; - - /* Padding at the top includes 2px for the text drop-shadow */ - padding: 2px 5px 3px 5px; - - color: #BBB; - font: 9px Verdana; - text-shadow: 1px 1px 1px black; - text-align: center; - - background-color:#555; - - /* A box shadow and inset box highlight */ - -webkit-box-shadow: 1px 1px 1px #222, 1px 1px 1px #777 inset; - box-shadow: 1px 1px 1px #222, 1px 1px 1px #777 inset; -} - -.Button:hover { - background-color: #616161; -} - -.Button.ButtonHeld -{ - /* Reset the gradient to a full-colour background */ - background:#383838; - - /* Two inset box shadows to simulate depressing */ - -webkit-box-shadow: -1px -1px 1px #222 inset, 1px 1px 1px #222 inset; - box-shadow: -1px -1px 1px #222 inset, 1px 1px 1px #222 inset; -} diff --git a/profiler/vis/index.html b/profiler/vis/index.html deleted file mode 100644 index fa8c324..0000000 --- a/profiler/vis/index.html +++ /dev/null @@ -1,69 +0,0 @@ - - - - - - Remotery Viewer - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/dev/euph/engine/datastructs/Archetype.java b/src/dev/euph/engine/datastructs/Archetype.java deleted file mode 100644 index 9be2495..0000000 --- a/src/dev/euph/engine/datastructs/Archetype.java +++ /dev/null @@ -1,56 +0,0 @@ -package dev.euph.engine.datastructs; - -import java.util.Arrays; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -public class Archetype { - //region Fields - private final Set> componentClasses; - //endregion - - //region Constructor - @SafeVarargs - public Archetype(Class... componentClasses){ - this.componentClasses = new HashSet<>(); - this.componentClasses.addAll(Arrays.asList(componentClasses)); - } - public Archetype(List> componentClasses){ - this.componentClasses = new HashSet<>(); - this.componentClasses.addAll(componentClasses); - } - //endregion - - - public boolean contains(Class componentClass) { - for(Class _componentClass : componentClasses){ - if(componentClass.isAssignableFrom(_componentClass)){ - return true; - } - } - return false; - } - - //region Overrides Comparison - @Override - public int hashCode() { - return componentClasses.hashCode(); - } - @Override - public boolean equals(Object obj) { - if(this == obj)return true; - if(obj == null || obj.getClass() != this.getClass()) return false; - - Archetype other = (Archetype) obj; - return other.hashCode() == this.hashCode(); - } - //endregion - - //region Getter - public Set> getComponentClasses() { - return componentClasses; - } - //endregion - -} diff --git a/src/dev/euph/engine/datastructs/octree/Octree.java b/src/dev/euph/engine/datastructs/octree/Octree.java deleted file mode 100644 index 803463a..0000000 --- a/src/dev/euph/engine/datastructs/octree/Octree.java +++ /dev/null @@ -1,22 +0,0 @@ -package dev.euph.engine.datastructs.octree; - -import org.joml.Vector3f; - -import java.util.List; - -public class Octree { - private final OctreeNode root; - - public Octree(int maxCapacity, Vector3f center, float halfSize) { - root = new OctreeNode(maxCapacity, center, halfSize); - } - - public void insert(Vector3f position, T data) { - root.insert(position, data); - } - - public List query(Vector3f position, float radius) { - return root.query(position, radius); - } -} - diff --git a/src/dev/euph/engine/datastructs/octree/OctreeNode.java b/src/dev/euph/engine/datastructs/octree/OctreeNode.java deleted file mode 100644 index a79c7da..0000000 --- a/src/dev/euph/engine/datastructs/octree/OctreeNode.java +++ /dev/null @@ -1,153 +0,0 @@ -package dev.euph.engine.datastructs.octree; - -import org.joml.Vector3f; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import static java.lang.Math.max; -import static java.lang.String.join; - -public class OctreeNode { - private final int maxCapacity; - private final Vector3f center; - private final float halfSize; - private final List positions; - private final Map dataMap; - private OctreeNode[] children; - - public OctreeNode(int maxCapacity, Vector3f center, float halfSize) { - this.maxCapacity = maxCapacity; - this.center = center; - this.halfSize = halfSize; - this.positions = new ArrayList<>(); - this.dataMap = new HashMap<>(); - this.children = null; - } - - public void insert(Vector3f position, T data) { - // Check if the item fits within this node's region - if (isOutOfBounds(position)) { - return; - } - - // If the node is not subdivided, add the item to its data list and map - if (children == null) { - positions.add(position); - dataMap.put(position, data); - - // Subdivide if the capacity is exceeded - if (positions.size() > maxCapacity) { - subdivide(); - } - } else { - // Otherwise, insert the item into the appropriate child node - for (OctreeNode child : children) { - child.insert(position, data); - } - } - } - - public List query(Vector3f position, float radius) { - List result = new ArrayList<>(); - - if (isOutOfBounds(position)) { - return result; - } - - // Add data items from this node if their positions are within the query radius - for (Vector3f itemPosition : positions) { - if (itemPosition.distance(position) <= radius) { - result.add(dataMap.get(itemPosition)); - } - } - - // Recursively query child nodes - if (children != null) { - for (OctreeNode child : children) { - result.addAll(child.query(position, radius)); - } - } - - return result; - } - - private boolean isOutOfBounds(Vector3f position) { - float minX = center.x - halfSize; - float maxX = center.x + halfSize; - float minY = center.y - halfSize; - float maxY = center.y + halfSize; - float minZ = center.z - halfSize; - float maxZ = center.z + halfSize; - - return ( - position.x < minX || - position.x > maxX || - position.y < minY || - position.y > maxY || - position.z < minZ || - position.z > maxZ - ); - } - - - private void subdivide() { - OctreeNode[] childNodes = new OctreeNode[8]; - - float quarterSize = halfSize / 2.0f; - - // Calculate centers for each child node - Vector3f[] childCenters = new Vector3f[]{ - new Vector3f(center.x - quarterSize, center.y - quarterSize, center.z - quarterSize), - new Vector3f(center.x + quarterSize, center.y - quarterSize, center.z - quarterSize), - new Vector3f(center.x - quarterSize, center.y + quarterSize, center.z - quarterSize), - new Vector3f(center.x + quarterSize, center.y + quarterSize, center.z - quarterSize), - new Vector3f(center.x - quarterSize, center.y - quarterSize, center.z + quarterSize), - new Vector3f(center.x + quarterSize, center.y - quarterSize, center.z + quarterSize), - new Vector3f(center.x - quarterSize, center.y + quarterSize, center.z + quarterSize), - new Vector3f(center.x + quarterSize, center.y + quarterSize, center.z + quarterSize) - }; - - // Create and initialize child nodes - for (int i = 0; i < 8; i++) { - childNodes[i] = new OctreeNode<>(maxCapacity, childCenters[i], quarterSize); - } - children = childNodes; // Assign the initialized children array to the class field - - // Redistribute positions and data from this node to children - for (int i = positions.size() - 1; i >= 0; i--) { - Vector3f position = positions.get(i); - T data = dataMap.remove(position); // Remove data from the current node - - // Find the child that contains the position and add data to it - for (OctreeNode child : children) { - if (!child.isOutOfBounds(position)) { - child.insert(position, data); - break; - } - } - - // Remove the position from the current node - positions.remove(i); - } - } - - public List getPositions() { - List allPositions = new ArrayList<>(positions); - - if (children != null) { - for (OctreeNode child : children) { - allPositions.addAll(child.getPositions()); - } - } - - return allPositions; - } - - public OctreeNode[] getChildren() { - return children; - } - -} diff --git a/src/dev/euph/engine/datastructs/pipeline/Pipeline.java b/src/dev/euph/engine/datastructs/pipeline/Pipeline.java deleted file mode 100644 index d769578..0000000 --- a/src/dev/euph/engine/datastructs/pipeline/Pipeline.java +++ /dev/null @@ -1,42 +0,0 @@ -package dev.euph.engine.datastructs.pipeline; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; - -public class Pipeline { - private final Collection> pipelineStages; - - public Pipeline() { - this.pipelineStages = new ArrayList<>(); - } - public Pipeline(PipelineStage pipelineStage) { - pipelineStages = Collections.singletonList(pipelineStage); - } - private Pipeline(Collection> pipelineStages) { - this.pipelineStages = new ArrayList<>(pipelineStages); - } - public Pipeline addStage(PipelineStage pipelineStage){ - final ArrayList> newPipelineStages = new ArrayList<>(pipelineStages); - newPipelineStages.add(pipelineStage); - return new Pipeline<>(newPipelineStages); - } - - /** - * @param input the input of the pipeline - * @return the Output of the pipelines execution - * @throws PipelineRuntimeException if there is an error durring execution - */ - @SuppressWarnings("all") - public Output execute(Input input) { - try { - Object output = input; - for (final PipelineStage pipelineStage : pipelineStages){ - output = pipelineStage.execute(output); - } - return (Output) output; - } catch (Exception exception){ - throw new PipelineRuntimeException(exception); - } - } -} diff --git a/src/dev/euph/engine/datastructs/pipeline/PipelineRuntimeException.java b/src/dev/euph/engine/datastructs/pipeline/PipelineRuntimeException.java deleted file mode 100644 index 1644e0c..0000000 --- a/src/dev/euph/engine/datastructs/pipeline/PipelineRuntimeException.java +++ /dev/null @@ -1,16 +0,0 @@ -package dev.euph.engine.datastructs.pipeline; - -public class PipelineRuntimeException extends RuntimeException{ - public PipelineRuntimeException(String message) { - super("Pipeline Runtime Exception: " + message); - } - public PipelineRuntimeException(String message, Throwable cause) { - super("Pipeline Runtime Exception: " + message, cause); - } - public PipelineRuntimeException(Throwable cause) { - super("Pipeline Runtime Exception: " + cause); - } - protected PipelineRuntimeException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { - super("Pipeline Runtime Exception: " + message, cause, enableSuppression, writableStackTrace); - } -} diff --git a/src/dev/euph/engine/datastructs/pipeline/PipelineStage.java b/src/dev/euph/engine/datastructs/pipeline/PipelineStage.java deleted file mode 100644 index 7903d74..0000000 --- a/src/dev/euph/engine/datastructs/pipeline/PipelineStage.java +++ /dev/null @@ -1,5 +0,0 @@ -package dev.euph.engine.datastructs.pipeline; - -public interface PipelineStage { - Output execute(Input input); -} diff --git a/src/dev/euph/engine/ecs/Component.java b/src/dev/euph/engine/ecs/Component.java deleted file mode 100644 index af67448..0000000 --- a/src/dev/euph/engine/ecs/Component.java +++ /dev/null @@ -1,37 +0,0 @@ -package dev.euph.engine.ecs; - -import java.util.ArrayList; -import java.util.List; - -public abstract class Component { - - //region Fields - protected Entity entity = null; - protected List> requiredComponents = new ArrayList<>(); - //endregion - - public void OnReady(){} - public void OnUpdate(float deltaTime){} - public void OnDestroy(){} - - //region Component Management - protected void requireComponent(Class componentClass) { - requiredComponents.add(componentClass); - } - protected boolean hasRequiredComponents(Entity entity) { - - for (Class requiredClass : requiredComponents) { - if (entity.getComponent(requiredClass) == null) { - return false; - } - } - return true; - } - //endregion - - //region Getter - public Entity getEntity() { - return entity; - } - //endregion -} diff --git a/src/dev/euph/engine/ecs/Entity.java b/src/dev/euph/engine/ecs/Entity.java deleted file mode 100644 index 12e321d..0000000 --- a/src/dev/euph/engine/ecs/Entity.java +++ /dev/null @@ -1,101 +0,0 @@ -package dev.euph.engine.ecs; - -import java.util.ArrayList; -import java.util.List; - -public class Entity { - //region Fields - private boolean destroyPending = false; - private boolean componentsChanged = false; - private String name; - private final List components; - //endregion - - //region Constructor - public Entity(String name){ - this.name = name; - components = new ArrayList<>(); - } - //endregion - - //region Component Management - public T addComponent(T component) { - - if (!component.hasRequiredComponents(this)) { - throw new IllegalArgumentException("Cannot add Component. Missing required Components."); - } - - this.components.add(component); - this.componentsChanged = true; - - if(component.entity != null){ - component.entity.removeComponent(component.getClass()); - } - component.entity = this; - - return component; - } - - public T getComponent(Class componentClass){ - for(Component c : components){ - if(componentClass.isAssignableFrom(c.getClass())){ - try { - return componentClass.cast(c); - }catch (ClassCastException e){ - e.printStackTrace(); - assert false : "Error: Casting component."; - } - } - } - return null; - } - public void removeComponent(Class componentClass){ - for(int i=0; i < components.size(); i++){ - if(componentClass.isAssignableFrom(components.get(i).getClass())){ - components.remove(i); - this.componentsChanged = true; - return; - } - } - } - //endregion - - public void ready(){ - for (Component component : components) { - component.OnReady(); - } - } - public void update(float deltaTime){ - for (Component component : components) { - component.OnUpdate(deltaTime); - } - } - public void destroy(){ - destroyPending = true; - for (Component component : components) { - component.OnDestroy(); - } - } - - //region Getter/Setter - public boolean isDestroyPending(){ - return destroyPending; - } - public List getComponents() { - return components; - } - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - protected boolean areComponentsChanged() { - return componentsChanged; - } - protected void setComponentsChanged(boolean componentsChanged) { - this.componentsChanged = componentsChanged; - } - //endregion -} diff --git a/src/dev/euph/engine/ecs/Scene.java b/src/dev/euph/engine/ecs/Scene.java deleted file mode 100644 index ddc7bc1..0000000 --- a/src/dev/euph/engine/ecs/Scene.java +++ /dev/null @@ -1,186 +0,0 @@ -package dev.euph.engine.ecs; - -import dev.euph.engine.datastructs.Archetype; - -import java.util.*; - -public class Scene { - - //region Fields - private boolean isRunning = false; - private final List entities; - private final Map, List> entitiesByArchetype; - private final Map, Set>> subclassesByComponent; - private final Map, List> componentInstancesByType; - //endregion - - //region Constructor - public Scene(){ - entities = new ArrayList<>(); - entitiesByArchetype = new HashMap<>(); - subclassesByComponent = new HashMap<>(); - componentInstancesByType = new HashMap<>(); - } - //endregion - - public void start(){ - if(isRunning)return; - isRunning = true; - - for(Entity entity : entities){ - entity.ready(); - } - } - public void update(float deltaTime) { - List entitiesToRemove = new ArrayList<>(); - - for (Entity entity : entities) { - entity.update(deltaTime); - - if (entity.isDestroyPending()) { - removeEntityFromArchetype(entity); - entitiesToRemove.add(entity); - - // Remove the components of the entity from the componentInstancesByType map - for (Component component : entity.getComponents()) { - Class componentClass = component.getClass(); - List componentInstances = componentInstancesByType.get(componentClass); - if (componentInstances != null) { - componentInstances.remove(component); - } - } - } else if (entity.areComponentsChanged()) { - removeEntityFromArchetype(entity); - - List> components = new ArrayList<>(); - for (Component component : entity.getComponents()) { - components.add(component.getClass()); - } - Archetype newArchetype = new Archetype<>(components); - List archetypeEntities = entitiesByArchetype.computeIfAbsent(newArchetype, key -> new ArrayList<>()); - archetypeEntities.add(entity); - - for (Component component : entity.getComponents()) { - Class componentClass = component.getClass(); - List componentInstances = componentInstancesByType.computeIfAbsent(componentClass, key -> new ArrayList<>()); - componentInstances.add(component); - } - - entity.setComponentsChanged(false); - } - } - - entities.removeAll(entitiesToRemove); - } - - public void addEntityToScene(Entity entity) { - if (entities.contains(entity)) { - return; - } - entities.add(entity); - - List> components = new ArrayList<>(); - for (Component component : entity.getComponents()) { - components.add(component.getClass()); - } - Archetype newArchetype = new Archetype<>(components); - - for (Class componentClass : components) { - // Update subclasses mapping - updateSubclassesMapping(componentClass); - - // Add component instance to the componentInstancesByType map - List componentInstances = componentInstancesByType.computeIfAbsent(componentClass, key -> new ArrayList<>()); - componentInstances.add(entity.getComponent(componentClass)); // added - - // Handle subclasses: If the componentClass has a superclass, add the component instance to its list too - Class superClass = componentClass.getSuperclass(); - while (Component.class.isAssignableFrom(superClass)) { - if (Component.class.isAssignableFrom(superClass)) { - List superClassInstances = componentInstancesByType.computeIfAbsent(superClass.asSubclass(Component.class), key -> new ArrayList<>()); - superClassInstances.add(entity.getComponent(componentClass)); - } - superClass = superClass.getSuperclass(); - } - } - - List archetypeEntities = entitiesByArchetype.computeIfAbsent(newArchetype, key -> new ArrayList<>()); - archetypeEntities.add(entity); - - if (!isRunning) { - return; - } - entity.ready(); - } - @SafeVarargs - public final List getEntitiesWithComponents(boolean exactMatch, Class... componentClasses) { - List filteredEntities = new ArrayList<>(); - - if (exactMatch) { - Archetype requestedArchetype = new Archetype<>(componentClasses); - List matchingEntities = entitiesByArchetype.get(requestedArchetype); - if (matchingEntities != null) { - filteredEntities.addAll(matchingEntities); - } - } else { - Set> componentSet = new HashSet<>(); - for (Class componentClass : componentClasses) { - componentSet.add(componentClass); - componentSet.addAll(subclassesByComponent.getOrDefault(componentClass, Collections.emptySet())); - } - - for (Class componentClass : componentSet) { - List componentInstances = componentInstancesByType.get(componentClass); - if (componentInstances != null) { - filteredEntities.addAll(componentInstances.stream() - .map(Component::getEntity) - .toList()); - } - } - - } - - return filteredEntities; - } - public List getComponentInstances(Class componentClass){ - return componentInstancesByType.get(componentClass); - } - - //region ECS Utility Functions - private void removeEntityFromArchetype(Entity entity) { - for (Map.Entry, List> entry : entitiesByArchetype.entrySet()) { - Archetype archetype = entry.getKey(); - List archetypeEntities = entry.getValue(); - if (archetypeEntities.remove(entity)) { - if (archetypeEntities.isEmpty()) { - entitiesByArchetype.remove(archetype); - } - break; - } - } - } - private void updateSubclassesMapping(Class componentClass) { - Set> subclasses = getSubclasses(componentClass); - subclassesByComponent.put(componentClass, subclasses); - } - private Set> getSubclasses(Class componentClass) { - Set> subclasses = new HashSet<>(); - for (Class clazz : subclassesByComponent.keySet()) { - if (componentClass.isAssignableFrom(clazz)) { - subclasses.add(clazz); - } - } - return subclasses; - } - //endregion - - //region Getter - public boolean isRunning() { - return isRunning; - } - public List getEntities() { - return entities; - } - //endregion - -} diff --git a/src/dev/euph/engine/ecs/components/Camera.java b/src/dev/euph/engine/ecs/components/Camera.java deleted file mode 100644 index e820fcb..0000000 --- a/src/dev/euph/engine/ecs/components/Camera.java +++ /dev/null @@ -1,38 +0,0 @@ -package dev.euph.engine.ecs.components; - -import dev.euph.engine.ecs.Component; -import dev.euph.engine.managers.Window; - -import java.awt.*; - - -public class Camera extends Component { - - //region Fields - public float fov = 90f; - public float nearPlane = 0.1f; - public float farPlane = 500f; - public Color backgroundColor = new Color(0, 200, 255); - public Window window; - //endregion - - //region Constructor - public Camera(Window window){ - requireComponent(Transform.class); - this.window = window; - } - public Camera(Window window, float fov, float nearPlane, float farPlane, Color backgroundColor) { - requireComponent(Transform.class); - this.window = window; - this.fov = fov; - this.nearPlane = nearPlane; - this.farPlane = farPlane; - this.backgroundColor = backgroundColor; - } - //endregion - - - public Window getWindow() { - return window; - } -} diff --git a/src/dev/euph/engine/ecs/components/MeshRenderer.java b/src/dev/euph/engine/ecs/components/MeshRenderer.java deleted file mode 100644 index bc163eb..0000000 --- a/src/dev/euph/engine/ecs/components/MeshRenderer.java +++ /dev/null @@ -1,30 +0,0 @@ -package dev.euph.engine.ecs.components; - -import dev.euph.engine.ecs.Component; -import dev.euph.engine.render.Material; -import dev.euph.engine.resources.Mesh; - -public class MeshRenderer extends Component { - - //region Fields - private final Mesh mesh; - private final Material material; - //endregion - - //region Constructor - public MeshRenderer(Mesh mesh, Material material) { - requireComponent(Transform.class); - this.mesh = mesh; - this.material = material; - } - //endregion - - //region Getter - public Mesh getMesh() { - return mesh; - } - public Material getMaterial() { - return material; - } - //endregion -} diff --git a/src/dev/euph/engine/ecs/components/Transform.java b/src/dev/euph/engine/ecs/components/Transform.java deleted file mode 100644 index 8102dce..0000000 --- a/src/dev/euph/engine/ecs/components/Transform.java +++ /dev/null @@ -1,108 +0,0 @@ -package dev.euph.engine.ecs.components; - -import dev.euph.engine.ecs.Component; -import dev.euph.engine.math.TransformationMatrix; -import org.joml.Matrix4f; -import org.joml.Quaternionf; -import org.joml.Vector3f; - -import static java.lang.Math.toRadians; - -public class Transform extends Component { - //region Fields - private Vector3f position; - private Vector3f rotation; - private Vector3f scale; - private TransformationMatrix transformationMatrix; - //endregion - - //region Constructor - public Transform() { - this(new Vector3f(), new Vector3f(), new Vector3f(1)); - } - public Transform(Vector3f position, Vector3f rotation, Vector3f scale) { - this.position = position; - this.rotation = rotation; - this.scale = scale; - calculateTransformationMatrix(); - } - public Transform(Transform transform) { - this.position = transform.position; - this.rotation = transform.rotation; - this.scale = transform.scale; - calculateTransformationMatrix(); - } - //endregion - - //region Utility - private void calculateTransformationMatrix() { - transformationMatrix = new TransformationMatrix(this); - } - //endregion - - //region Getter/Setter - public Vector3f getPosition() { - return new Vector3f(position); - } - - public void setPosition(Vector3f position) { - this.position = position; - calculateTransformationMatrix(); - } - - public Vector3f getRotation() { - return new Vector3f(rotation); - } - - public void setRotation(Vector3f rotation) { - this.rotation = rotation; - calculateTransformationMatrix(); - } - public Vector3f getScale() { - return new Vector3f(scale); - } - - public void setScale(Vector3f scale) { - this.scale = scale; - calculateTransformationMatrix(); - } - public TransformationMatrix getTransformationMatrix() { - return transformationMatrix; - } - - public Vector3f getUp() { - Quaternionf q = new Quaternionf().rotateXYZ( - (float) toRadians(rotation.x), - (float) toRadians(rotation.y), - (float) toRadians(rotation.z) - ); - return q.positiveY(new Vector3f()); - } - - public Vector3f getDown() { - return new Vector3f(getUp()).negate(); - } - - public Vector3f getRight() { - Quaternionf q = new Quaternionf().rotateY((float) toRadians(rotation.y)); - return q.positiveX(new Vector3f()); - } - - public Vector3f getLeft() { - return new Vector3f(getRight()).negate(); - } - - public Vector3f getForward() { - return new Vector3f(getBack()).negate(); - } - - public Vector3f getBack() { - Quaternionf q = new Quaternionf().rotateXYZ( - (float) toRadians(rotation.x), - (float) toRadians(rotation.y), - (float) toRadians(rotation.z) - ); - return q.positiveZ(new Vector3f()); - } - //endregion -} \ No newline at end of file diff --git a/src/dev/euph/engine/ecs/components/lights/DirectionalLight.java b/src/dev/euph/engine/ecs/components/lights/DirectionalLight.java deleted file mode 100644 index db70a71..0000000 --- a/src/dev/euph/engine/ecs/components/lights/DirectionalLight.java +++ /dev/null @@ -1,11 +0,0 @@ -package dev.euph.engine.ecs.components.lights; - -import org.joml.Vector3f; - -import java.awt.*; - -public class DirectionalLight extends LightSource{ - public DirectionalLight(Color color) { - super(color, LightType.Directional); - } -} diff --git a/src/dev/euph/engine/ecs/components/lights/LightSource.java b/src/dev/euph/engine/ecs/components/lights/LightSource.java deleted file mode 100644 index a80d4d2..0000000 --- a/src/dev/euph/engine/ecs/components/lights/LightSource.java +++ /dev/null @@ -1,45 +0,0 @@ -package dev.euph.engine.ecs.components.lights; - -import dev.euph.engine.ecs.Component; -import dev.euph.engine.ecs.components.Transform; -import org.joml.Vector3f; - -import java.awt.*; - -public abstract class LightSource extends Component { - - public static class LightType { - public static final int Directional = 0; - public static final int Point = 1; - public static final int Spot = 2; - public static final int Area = 3; - } - - //region Fields - private Color color; - private final int type; - //endregion - - //region Constructor - public LightSource(Color color, int type) { - requireComponent(Transform.class); - this.color = color; - this.type = type; - } - //endregion - - //region Getter - public Color getColor() { - return color; - } - - public void setColor(Color color) { - this.color = color; - } - - public int getType() { - return type; - } - //endregion -} - diff --git a/src/dev/euph/engine/ecs/components/lights/PointLight.java b/src/dev/euph/engine/ecs/components/lights/PointLight.java deleted file mode 100644 index 68a7e65..0000000 --- a/src/dev/euph/engine/ecs/components/lights/PointLight.java +++ /dev/null @@ -1,13 +0,0 @@ -package dev.euph.engine.ecs.components.lights; - -import org.joml.Vector3f; - -import java.awt.*; - -public class PointLight extends LightSource{ - private Vector3f attenuation; - public PointLight(Color color, Vector3f attenuation) { - super(color, LightType.Point); - this.attenuation = attenuation; - } -} diff --git a/src/dev/euph/engine/ecs/components/lights/SpotLight.java b/src/dev/euph/engine/ecs/components/lights/SpotLight.java deleted file mode 100644 index 5284240..0000000 --- a/src/dev/euph/engine/ecs/components/lights/SpotLight.java +++ /dev/null @@ -1,24 +0,0 @@ -package dev.euph.engine.ecs.components.lights; - -import org.joml.Vector3f; - -import java.awt.*; - -public class SpotLight extends LightSource{ - //region Fields - public float coneAngle; - public float hardCutoff; - public float softCutoff; - private Vector3f attenuation; - //endregion - - //region Constructor - public SpotLight(Color color, Vector3f attenuation, float coneAngle, float hardCutoff, float softCutoff) { - super(color, LightType.Spot); - this.attenuation = attenuation; - this.coneAngle = coneAngle; - this.hardCutoff = hardCutoff; - this.softCutoff = softCutoff; - } - //endregion -} diff --git a/src/dev/euph/engine/managers/Input.java b/src/dev/euph/engine/managers/Input.java deleted file mode 100644 index bae361c..0000000 --- a/src/dev/euph/engine/managers/Input.java +++ /dev/null @@ -1,12 +0,0 @@ -package dev.euph.engine.managers; - -public class Input { - /* TODO: - - Add Some Kind of Input Action map - - Input Action Maps should be replace and editable. - - Input Action should have different Modes: Button, Axis, 2Axis - - Any Combination of Inputs that fits should be able to be bound to actions. - - Inputs that dont Fit should either be splittable in smaller parts, or combinable with some struct. - - The Output of an Input Action should either be directly readable, or/-and bindable to some callback. - */ -} diff --git a/src/dev/euph/engine/managers/ShaderManager.java b/src/dev/euph/engine/managers/ShaderManager.java deleted file mode 100644 index 7838c88..0000000 --- a/src/dev/euph/engine/managers/ShaderManager.java +++ /dev/null @@ -1,41 +0,0 @@ -package dev.euph.engine.managers; - -import dev.euph.engine.render.Shader; -import dev.euph.engine.util.Path; - -import java.io.IOException; -import java.util.HashMap; -import java.util.Map; - -public class ShaderManager { - - private final Map shaders; - - public ShaderManager() { - shaders = new HashMap<>(); - try { - Shader defaultShader = new Shader( - "default", - Path.VERTEX_SHADERS + "default.glsl", - Path.FRAGMENT_SHADERS + "default.glsl" - ); - addShader(defaultShader); - } catch (IOException e) { - throw new RuntimeException(e); - } - - } - - public void addShader(Shader shader) { - shaders.put(shader.getName(), shader); - } - - public Shader getShader(String shaderName) { - return shaders.get(shaderName); - } - - public void cleanup() { - shaders.values().forEach(Shader::close); - shaders.clear(); - } -} diff --git a/src/dev/euph/engine/managers/Window.java b/src/dev/euph/engine/managers/Window.java deleted file mode 100644 index 814bf8f..0000000 --- a/src/dev/euph/engine/managers/Window.java +++ /dev/null @@ -1,157 +0,0 @@ -package dev.euph.engine.managers; - -import dev.euph.engine.util.Time; -import org.lwjgl.glfw.GLFWErrorCallback; -import org.lwjgl.glfw.GLFWVidMode; -import org.lwjgl.glfw.GLFWWindowSizeCallback; -import org.lwjgl.opengl.GL; - -import java.util.Objects; - -import static org.lwjgl.glfw.Callbacks.glfwFreeCallbacks; -import static org.lwjgl.glfw.GLFW.*; -import static org.lwjgl.opengl.GL11.glViewport; - -public class Window { - private final long id; - private final int initalWidth; - private int width; - private final int initalHeight; - private int height; - private Time time; - private GLFWWindowSizeCallback windowSizeCallback; - public Window(int width, int height, String title){ - //setup error callback to use for errors - GLFWErrorCallback.createPrint(System.err).set(); - - //initialize GLFW - if (!glfwInit()) { - throw new IllegalStateException("Unable to initialize GLFW"); - } - - time = new Time(); - - //configure the window - glfwDefaultWindowHints(); - - //set the window to be resizable - glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE); - glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); - initalWidth = width; - this.width = width; - initalHeight = height; - this.height = height; - - //create the window - id = glfwCreateWindow(width, height, title, 0, 0); - if (id == 0) { - throw new RuntimeException("Failed to create the GLFW window"); - } - - //set up the callback to close the window when the user presses the 'X' - glfwSetKeyCallback(id, (window, key, scancode, action, mods) -> { - if(key == GLFW_KEY_ESCAPE && action == GLFW_RELEASE) { - glfwSetWindowShouldClose(window, true); - } - }); - - windowSizeCallback = new GLFWWindowSizeCallback() { - @Override - public void invoke(long window, int newWidth, int newHeight) { - adjustViewport(newWidth, newHeight); - } - }; - - glfwSetWindowSizeCallback(id, windowSizeCallback); - - //Get the resolution of the primary monitor - GLFWVidMode vidmode = glfwGetVideoMode(glfwGetPrimaryMonitor()); - //Center the window - assert vidmode != null; - glfwSetWindowPos( - id, - (vidmode.width() - width) / 2, - (vidmode.height() - height) / 2 - ); - - //set the opengl context to the current window - glfwMakeContextCurrent(id); - //enable v-sync - glfwSwapInterval(1); - //make the window visible - glfwShowWindow(id); - //bind glfw window context for lwjgl - GL.createCapabilities(); - //start the delta time - time.startDeltaTime(); - } - - private void adjustViewport(int newWidth, int newHeight) { - this.width = newWidth; - this.height = newHeight; - - float aspectRatio = (float) initalWidth / initalHeight; - - int newViewportWidth = newWidth; - int newViewportHeight = (int) (newWidth / aspectRatio); - - if (newViewportHeight > newHeight) { - newViewportWidth = (int) (newHeight * aspectRatio); - newViewportHeight = newHeight; - } - - // Calculate the position to center the viewport - int x = (newWidth - newViewportWidth) / 2; - int y = (newHeight - newViewportHeight) / 2; - - // Set the OpenGL viewport - glViewport(x, y, newViewportWidth, newViewportHeight); - } - - public boolean isCloseRequested() { - return glfwWindowShouldClose(id); - } - public void updateWindow() { - //update the window - glfwSwapBuffers(id); - - //process inputs - glfwPollEvents(); - //update the delta time - time.updateDeltaTime(); - } - public void destroyWindowy() { - //run the cleanup - cleanUp(); - //terminate GLFW and free the error callback - glfwTerminate(); - Objects.requireNonNull(glfwSetErrorCallback(null)).free(); - } - private void cleanUp() { - //free the window callbacks and destroy the window - glfwFreeCallbacks(id); - glfwDestroyWindow(id); - } - public long getId() { - return id; - } - - public int getInitalWidth() { - return initalWidth; - } - public int getInitalHeight() { - return initalHeight; - } - - public Time getTime() { - return time; - } - - public int getWidth() { - return width; - } - - public int getHeight() { - return height; - } -} diff --git a/src/dev/euph/engine/math/ProjectionMatrix.java b/src/dev/euph/engine/math/ProjectionMatrix.java deleted file mode 100644 index 3f10cc2..0000000 --- a/src/dev/euph/engine/math/ProjectionMatrix.java +++ /dev/null @@ -1,34 +0,0 @@ -package dev.euph.engine.math; - -import dev.euph.engine.ecs.components.Camera; -import dev.euph.engine.managers.Window; -import org.joml.Matrix4f; -import org.lwjgl.BufferUtils; - -import java.nio.IntBuffer; - -import static org.lwjgl.glfw.GLFW.glfwGetWindowSize; - -public class ProjectionMatrix extends Matrix4f { - - public ProjectionMatrix(Camera camera) { - super(); - calcualteProjection(camera.getWindow(), camera.fov, camera.nearPlane, camera.farPlane); - } - - private void calcualteProjection(Window window, float fov, float nearPlane, float farPlane) { - float aspectRatio = (float) window.getInitalWidth() / (float) window.getInitalHeight(); - - float y_scale = (float) (1f / Math.tan(Math.toRadians(fov / 2f))) * aspectRatio; - float x_scale = y_scale / aspectRatio; - float frustum_length = farPlane - nearPlane; - - m00(x_scale); - m11(y_scale); - m22(-((farPlane + nearPlane) / frustum_length)); - m23(-1); - m32(-((2 * nearPlane * farPlane) / frustum_length)); - m33(0); - } - -} diff --git a/src/dev/euph/engine/math/TransformationMatrix.java b/src/dev/euph/engine/math/TransformationMatrix.java deleted file mode 100644 index 3c9d48f..0000000 --- a/src/dev/euph/engine/math/TransformationMatrix.java +++ /dev/null @@ -1,32 +0,0 @@ -package dev.euph.engine.math; - -import dev.euph.engine.ecs.components.Transform; -import org.joml.Matrix4f; -import org.joml.Vector3f; - -public class TransformationMatrix extends Matrix4f { - - public TransformationMatrix(Vector3f translation, Vector3f rotation, Vector3f scale) { - super(); - super.identity(); - super.translate(translation); - super.rotate((float) Math.toRadians(rotation.x % 360), new Vector3f(1, 0, 0)); - super.rotate((float) Math.toRadians(rotation.y % 360), new Vector3f(0, 1, 0)); - super.rotate((float) Math.toRadians(rotation.z % 360), new Vector3f(0, 0, 1)); - super.scale(scale); - } - public TransformationMatrix(Transform transform) { - super(); - super.identity(); - super.translate(transform.getPosition()); - Vector3f rotation = transform.getRotation(); - super.rotate((float) Math.toRadians(rotation.x % 360), new Vector3f(1, 0, 0)); - super.rotate((float) Math.toRadians(rotation.y % 360), new Vector3f(0, 1, 0)); - super.rotate((float) Math.toRadians(rotation.z % 360), new Vector3f(0, 0, 1)); - super.scale(transform.getScale()); - } - public TransformationMatrix() { - super(); - super.identity(); - } -} diff --git a/src/dev/euph/engine/math/ViewMatrix.java b/src/dev/euph/engine/math/ViewMatrix.java deleted file mode 100644 index 541ec55..0000000 --- a/src/dev/euph/engine/math/ViewMatrix.java +++ /dev/null @@ -1,21 +0,0 @@ -package dev.euph.engine.math; - -import dev.euph.engine.ecs.components.Camera; -import dev.euph.engine.ecs.components.Transform; -import org.joml.Math; -import org.joml.Matrix4f; -import org.joml.Vector3f; - -public class ViewMatrix extends Matrix4f { - - public ViewMatrix(Camera camera) { - super(); - Transform cameraTransform = camera.getEntity().getComponent(Transform.class); - super.identity(); - super.rotate(Math.toRadians(cameraTransform.getRotation().x), new Vector3f(1, 0, 0)); - super.rotate(Math.toRadians(cameraTransform.getRotation().y), new Vector3f(0, 1, 0)); - super.rotate(Math.toRadians(cameraTransform.getRotation().z), new Vector3f(0, 0, 1)); - Vector3f pos = new Vector3f(cameraTransform.getPosition()); - super.translate(pos.negate()); - } -} diff --git a/src/dev/euph/engine/render/ForwardRenderer.java b/src/dev/euph/engine/render/ForwardRenderer.java deleted file mode 100644 index 8a8e883..0000000 --- a/src/dev/euph/engine/render/ForwardRenderer.java +++ /dev/null @@ -1,109 +0,0 @@ -package dev.euph.engine.render; - -import dev.euph.engine.ecs.Entity; -import dev.euph.engine.ecs.components.Camera; -import dev.euph.engine.ecs.components.MeshRenderer; -import dev.euph.engine.ecs.components.Transform; -import dev.euph.engine.managers.ShaderManager; -import dev.euph.engine.math.ProjectionMatrix; -import dev.euph.engine.math.TransformationMatrix; -import dev.euph.engine.math.ViewMatrix; -import dev.euph.engine.resources.Mesh; -import dev.euph.engine.resources.TexturedMesh; -import dev.euph.engine.ecs.Scene; - -import static org.lwjgl.opengl.GL30.*; -public class ForwardRenderer implements IRenderPipeline { - //region Fields - private Scene scene; - private final ShaderManager shaderManager; - public Camera activeCamera; - //endregion - - //region Constructor - public ForwardRenderer(Scene scene, ShaderManager shaderManager) { - this.scene = scene; - this.shaderManager = shaderManager; - } - //endregion - - //region Rendering - public void init(){ - glEnable(GL_DEPTH_TEST); - } - public void render() { - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - if (activeCamera == null) { - glClearColor(0.0f, 0.0f, 0.0f, 1.0f); - return; - } - - glClearColor( - activeCamera.backgroundColor.getRed(), - activeCamera.backgroundColor.getGreen(), - activeCamera.backgroundColor.getBlue(), - activeCamera.backgroundColor.getAlpha() - ); - - Shader shader = shaderManager.getShader("default"); - if (shader == null) return; - - shader.start(); - ViewMatrix viewMatrix = new ViewMatrix(activeCamera); - shader.loadMatrix4(shader.getUniformLocation("viewMatrix"), viewMatrix); - ProjectionMatrix projectionMatrix = new ProjectionMatrix(activeCamera); - shader.loadMatrix4(shader.getUniformLocation("projectionMatrix"), projectionMatrix); - - shader.stop(); - - for (Entity entity : scene.getEntitiesWithComponents(false, MeshRenderer.class)) { - renderEntity(entity, entity.getComponent(MeshRenderer.class)); - } - } - private void renderEntity(Entity entity, MeshRenderer meshRenderer) { - Mesh mesh = meshRenderer.getMesh(); - Material material = meshRenderer.getMaterial(); - Shader shader = material.getShader(); - - if (shader == null || mesh == null) return; - - shader.start(); - shader.bindAttributes(); - - TransformationMatrix transformationMatrix = entity.getComponent(Transform.class).getTransformationMatrix(); - shader.loadMatrix4(shader.getUniformLocation("transformationMatrix"), transformationMatrix); - - material.useMaterial(); - - if (mesh instanceof TexturedMesh texturedMesh) { - glBindVertexArray(texturedMesh.getVaoId()); - } else { - glBindVertexArray(mesh.getVaoId()); - } - - shader.enableAttributes(); - - glDrawElements(GL_TRIANGLES, mesh.getVertexCount(), GL_UNSIGNED_INT, 0); - - shader.disableAttributes(); - glBindVertexArray(0); - - shader.stop(); - } - //endregion - - //region Getter/Setter - public Scene getScene() { - return scene; - } - public void setScene(Scene scene) { - this.scene = scene; - } - public Camera getActiveCamera() { - return activeCamera; - } - public void setActiveCamera(Camera activeCamera) { - this.activeCamera = activeCamera; - } - //endregion -} diff --git a/src/dev/euph/engine/render/IRenderPipeline.java b/src/dev/euph/engine/render/IRenderPipeline.java deleted file mode 100644 index 19a38d8..0000000 --- a/src/dev/euph/engine/render/IRenderPipeline.java +++ /dev/null @@ -1,6 +0,0 @@ -package dev.euph.engine.render; - -public interface IRenderPipeline { - void render(); - void init(); -} diff --git a/src/dev/euph/engine/render/Material.java b/src/dev/euph/engine/render/Material.java deleted file mode 100644 index 7561858..0000000 --- a/src/dev/euph/engine/render/Material.java +++ /dev/null @@ -1,63 +0,0 @@ -package dev.euph.engine.render; - -import dev.euph.engine.resources.Texture; -import org.lwjgl.opengl.GL30; - -import java.awt.*; -import java.util.HashMap; -import java.util.Map; - -import static org.lwjgl.opengl.GL11.GL_TEXTURE_2D; - -public class Material { - - private final Shader shader; - private Texture albedoTexture; - private Color color; - - public Material(Shader shader) { - this.shader = shader; - this.albedoTexture = null; - this.color = Color.WHITE; - } - - public void setAlbedoTexture(Texture albedoTexture) { - this.albedoTexture = albedoTexture; - } - - public void setColor(Color color) { - this.color = color; - } - - public void useMaterial() { - shader.start(); - - // Bind albedoTexture if it is set - int useAlbedoTextureLocation = shader.getUniformLocation("useAlbedoTexture"); - if (albedoTexture != null) { - int albedoTextureLocation = shader.getUniformLocation("albedoTexture"); - shader.loadInt(albedoTextureLocation, 0); // Use texture unit 0 - GL30.glActiveTexture(GL30.GL_TEXTURE0); - GL30.glBindTexture(GL_TEXTURE_2D, albedoTexture.getId()); - - shader.loadBoolean(useAlbedoTextureLocation, true); - } else { - shader.loadBoolean(useAlbedoTextureLocation, false); - } - - int colorLocation = shader.getUniformLocation("color"); - shader.loadVector4(colorLocation, new org.joml.Vector4f(color.getRed() / 255f, color.getGreen() / 255f, color.getBlue() / 255f, 1f)); - } - - - public void cleanup() { - shader.close(); - if (albedoTexture != null) { - GL30.glDeleteTextures(albedoTexture.getId()); - } - } - - public Shader getShader() { - return shader; - } -} diff --git a/src/dev/euph/engine/render/PBRMaterial.java b/src/dev/euph/engine/render/PBRMaterial.java deleted file mode 100644 index 8118c51..0000000 --- a/src/dev/euph/engine/render/PBRMaterial.java +++ /dev/null @@ -1,83 +0,0 @@ -package dev.euph.engine.render; - -import dev.euph.engine.resources.Texture; -import org.lwjgl.opengl.GL30; - -import static org.lwjgl.opengl.GL11.GL_TEXTURE_2D; - -public class PBRMaterial extends Material { - private Texture normalTexture; - private Texture metallicTexture; - private Texture roughnessTexture; - private Texture aoTexture; - - public PBRMaterial(Shader shader) { - super(shader); - } - - public void setNormalTexture(Texture normalTexture) { - this.normalTexture = normalTexture; - } - - public void setMetallicTexture(Texture metallicTexture) { - this.metallicTexture = metallicTexture; - } - - public void setRoughnessTexture(Texture roughnessTexture) { - this.roughnessTexture = roughnessTexture; - } - - public void setAoTexture(Texture aoTexture) { - this.aoTexture = aoTexture; - } - - @Override - public void useMaterial() { - super.useMaterial(); // Call the base class useMaterial method to bind the albedoTexture - - if (normalTexture != null) { - int normalTextureLocation = getShader().getUniformLocation("normalTexture"); - getShader().loadInt(normalTextureLocation, 1); // Use texture unit 1 for normal texture - GL30.glActiveTexture(GL30.GL_TEXTURE1); - GL30.glBindTexture(GL_TEXTURE_2D, normalTexture.getId()); - } - - if (metallicTexture != null) { - int metallicTextureLocation = getShader().getUniformLocation("metallicTexture"); - getShader().loadInt(metallicTextureLocation, 2); // Use texture unit 2 for metallic texture - GL30.glActiveTexture(GL30.GL_TEXTURE2); - GL30.glBindTexture(GL_TEXTURE_2D, metallicTexture.getId()); - } - - if (roughnessTexture != null) { - int roughnessTextureLocation = getShader().getUniformLocation("roughnessTexture"); - getShader().loadInt(roughnessTextureLocation, 3); // Use texture unit 3 for roughness texture - GL30.glActiveTexture(GL30.GL_TEXTURE3); - GL30.glBindTexture(GL_TEXTURE_2D, roughnessTexture.getId()); - } - - if (aoTexture != null) { - int aoTextureLocation = getShader().getUniformLocation("aoTexture"); - getShader().loadInt(aoTextureLocation, 4); // Use texture unit 4 for ambient occlusion texture - GL30.glActiveTexture(GL30.GL_TEXTURE4); - GL30.glBindTexture(GL_TEXTURE_2D, aoTexture.getId()); - } - } - - @Override - public void cleanup() { - super.cleanup(); - if (normalTexture != null) { - GL30.glDeleteTextures(normalTexture.getId()); - } - if (metallicTexture != null) { - GL30.glDeleteTextures(metallicTexture.getId()); - } - if (roughnessTexture != null) { - GL30.glDeleteTextures(roughnessTexture.getId()); - } - if (aoTexture != null) { - GL30.glDeleteTextures(aoTexture.getId()); - } - } -} diff --git a/src/dev/euph/engine/render/Shader.java b/src/dev/euph/engine/render/Shader.java deleted file mode 100644 index 5c6de3e..0000000 --- a/src/dev/euph/engine/render/Shader.java +++ /dev/null @@ -1,167 +0,0 @@ -package dev.euph.engine.render; - -import org.joml.Matrix4f; -import org.joml.Vector2f; -import org.joml.Vector3f; -import org.joml.Vector4f; -import org.lwjgl.BufferUtils; - -import java.io.BufferedReader; -import java.io.FileReader; -import java.io.IOException; -import java.nio.FloatBuffer; -import java.util.HashMap; -import java.util.Map; - -import static org.lwjgl.opengl.GL40.*; - -public class Shader implements AutoCloseable { - //region Fields - private final String name; - private final int programId; - private final int vertexShaderId; - private final int fragmentShaderId; - - private static final FloatBuffer matrixBuffer = BufferUtils.createFloatBuffer(16); - private final Map uniformLocations = new HashMap<>(); - //endregion - - //region Constructors - public Shader(String name, String vertexFile, String fragmentFile) throws IOException { - this.name = name; - vertexShaderId = loadShader(vertexFile, GL_VERTEX_SHADER); - fragmentShaderId = loadShader(fragmentFile, GL_FRAGMENT_SHADER); - programId = glCreateProgram(); - glAttachShader(programId, vertexShaderId); - glAttachShader(programId, fragmentShaderId); - bindAttributes(); - glLinkProgram(programId); - glValidateProgram(programId); - getAllUniformLocations(); - } - //endregion - - //region Public Methods - public void start() { - glUseProgram(programId); - } - - public void stop() { - glUseProgram(0); - } - - @Override - public void close() { - stop(); - glDetachShader(programId, vertexShaderId); - glDetachShader(programId, fragmentShaderId); - glDeleteShader(vertexShaderId); - glDeleteShader(fragmentShaderId); - glDeleteProgram(programId); - } - //endregion - - //region Uniform Locations - protected void getAllUniformLocations() { - int numUniforms = glGetProgrami(programId, GL_ACTIVE_UNIFORMS); - - for (int i = 0; i < numUniforms; i++) { - String name = glGetActiveUniformName(programId, i); - int location = glGetUniformLocation(programId, name); - - uniformLocations.put(name, location); - } - } - - public int getUniformLocation(String uniformName) { - if (uniformLocations.containsKey(uniformName)) { - return uniformLocations.get(uniformName); - } - - int location = glGetUniformLocation(programId, uniformName); - uniformLocations.put(uniformName, location); - return location; - } - //endregion - - //region Attributes - public void enableAttributes(){ - glEnableVertexAttribArray(0); - glEnableVertexAttribArray(1); - glEnableVertexAttribArray(2); - } - public void disableAttributes(){ - glDisableVertexAttribArray(0); - glDisableVertexAttribArray(1); - glDisableVertexAttribArray(2); - } - public void bindAttributes() { - bindAttribute(0, "vertexPositions"); - bindAttribute(1, "textureCoords"); - bindAttribute(2, "normals"); - } - protected void bindAttribute(int attribute, String variableName) { - glBindAttribLocation(programId, attribute, variableName); - } - //endregion - - //region Uniform Loaders - public void loadBoolean(int location, boolean value) { - glUniform1f(location, value ? 1f : 0f); - } - - public void loadInt(int location, int value) { - glUniform1i(location, value); - } - - public void loadFloat(int location, float value) { - glUniform1f(location, value); - } - - public void loadVector2(int location, Vector2f value) { - glUniform2f(location, value.x, value.y); - } - - public void loadVector3(int location, Vector3f value) { - glUniform3f(location, value.x, value.y, value.z); - } - public void loadVector4(int location, Vector4f value) { - glUniform4f(location, value.x, value.y, value.z, value.w); - } - - public void loadMatrix4(int location, Matrix4f value) { - value.get(matrixBuffer); - glUniformMatrix4fv(location, false, matrixBuffer); - } - //endregion - - //region Helper Methods - private static int loadShader(String file, int type) throws IOException{ - StringBuilder shaderSource = new StringBuilder(); - try (BufferedReader reader = new BufferedReader(new FileReader(file))) { - String line; - while ((line = reader.readLine()) != null) { - shaderSource.append(line).append("\n"); - } - } catch (IOException exception) { - System.err.println("Could not read file!"); - exception.printStackTrace(); - throw exception; - } - int shaderId = glCreateShader(type); - glShaderSource(shaderId, shaderSource); - glCompileShader(shaderId); - if(glGetShaderi(shaderId, GL_COMPILE_STATUS) == GL_FALSE){ - System.err.println(glGetShaderInfoLog(shaderId, 512)); - System.err.println("Couldn't compile shader!"); - System.exit(-1); - } - return shaderId; - } - //endregion - - - public String getName() { - return name; - } -} diff --git a/src/dev/euph/engine/resources/Mesh.java b/src/dev/euph/engine/resources/Mesh.java deleted file mode 100644 index a92ec8c..0000000 --- a/src/dev/euph/engine/resources/Mesh.java +++ /dev/null @@ -1,51 +0,0 @@ -package dev.euph.engine.resources; - -import org.lwjgl.BufferUtils; - -import java.nio.FloatBuffer; -import java.nio.IntBuffer; - -import static org.lwjgl.opengl.GL30.*; - -public class Mesh { - protected int vaoId; - protected int vertexCount; - - public Mesh(float[] vertices, int[] indices) { - vertexCount = indices.length; - vaoId = glGenVertexArrays(); - glBindVertexArray(vaoId); - - int vboId = glGenBuffers(); - glBindBuffer(GL_ARRAY_BUFFER, vboId); - glBufferData(GL_ARRAY_BUFFER, createFloatBuffer(vertices), GL_STATIC_DRAW); - glVertexAttribPointer(0, 3, GL_FLOAT, false, 0, 0); - glBindBuffer(GL_ARRAY_BUFFER, 0); - - int eboId = glGenBuffers(); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, eboId); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, createIntBuffer(indices), GL_STATIC_DRAW); - - glBindVertexArray(0); - } - - public int getVaoId() { - return vaoId; - } - - public int getVertexCount() { - return vertexCount; - } - - protected FloatBuffer createFloatBuffer(float[] array) { - FloatBuffer buffer = BufferUtils.createFloatBuffer(array.length); - buffer.put(array).flip(); - return buffer; - } - - protected IntBuffer createIntBuffer(int[] array) { - IntBuffer buffer = BufferUtils.createIntBuffer(array.length); - buffer.put(array).flip(); - return buffer; - } -} diff --git a/src/dev/euph/engine/resources/Texture.java b/src/dev/euph/engine/resources/Texture.java deleted file mode 100644 index d2d21b6..0000000 --- a/src/dev/euph/engine/resources/Texture.java +++ /dev/null @@ -1,22 +0,0 @@ -package dev.euph.engine.resources; - -public class Texture { - private final int textureId; - private final int height; - private final int width; - public Texture(int id, int width, int height) { - textureId = id; - this.width = width; - this.height = height; - } - - public int getId() { - return textureId; - } - public int getHeight() { - return height; - } - public int getWidth() { - return width; - } -} diff --git a/src/dev/euph/engine/resources/TexturedMesh.java b/src/dev/euph/engine/resources/TexturedMesh.java deleted file mode 100644 index f5953cc..0000000 --- a/src/dev/euph/engine/resources/TexturedMesh.java +++ /dev/null @@ -1,36 +0,0 @@ -package dev.euph.engine.resources; - -import java.nio.FloatBuffer; - -import static org.lwjgl.opengl.GL11.GL_FLOAT; -import static org.lwjgl.opengl.GL15.*; -import static org.lwjgl.opengl.GL15.GL_STATIC_DRAW; -import static org.lwjgl.opengl.GL20.glVertexAttribPointer; -import static org.lwjgl.opengl.GL30.glBindVertexArray; -import static org.lwjgl.opengl.GL30.glGenVertexArrays; - -public class TexturedMesh extends Mesh { - private float[] textureCoords; - - public TexturedMesh(float[] vertices, float[] textureCoords, int[] indices) { - super(vertices, indices); - - glBindVertexArray(this.vaoId); - - // Create and bind a new vertex buffer for the texture coordinates - int vboTextureCoords = glGenBuffers(); - glBindBuffer(GL_ARRAY_BUFFER, vboTextureCoords); - glBufferData(GL_ARRAY_BUFFER, createFloatBuffer(textureCoords), GL_STATIC_DRAW); - glVertexAttribPointer(1, 2, GL_FLOAT, false, 0, 0); - glBindBuffer(GL_ARRAY_BUFFER, 0); - - glBindVertexArray(0); - } - - - public float[] getTextureCoords() { - return textureCoords; - } - - // Additional methods specific to textured mesh, if needed... -} diff --git a/src/dev/euph/engine/resources/loader/MeshLoader.java b/src/dev/euph/engine/resources/loader/MeshLoader.java deleted file mode 100644 index 8cab836..0000000 --- a/src/dev/euph/engine/resources/loader/MeshLoader.java +++ /dev/null @@ -1,112 +0,0 @@ -package dev.euph.engine.resources.loader; - -import dev.euph.engine.resources.Mesh; -import dev.euph.engine.resources.TexturedMesh; -import org.lwjgl.assimp.*; - -import java.nio.IntBuffer; -import java.util.ArrayList; -import java.util.List; - -public class MeshLoader { - - public static Mesh loadMesh(String filepath) { - try { - AIScene scene = Assimp.aiImportFile(filepath, Assimp.aiProcess_Triangulate | Assimp.aiProcess_FlipUVs); - - if (scene == null || scene.mNumMeshes() == 0) { - throw new RuntimeException("Failed to load mesh: " + filepath); - } - - AIMesh mesh = AIMesh.create(scene.mMeshes().get(0)); - - List verticesList = new ArrayList<>(); - List indicesList = new ArrayList<>(); - - for (int i = 0; i < mesh.mNumVertices(); i++) { - AIVector3D vertex = mesh.mVertices().get(i); - verticesList.add(vertex.x()); - verticesList.add(vertex.y()); - verticesList.add(vertex.z()); - } - - AIFace.Buffer faceBuffer = mesh.mFaces(); - while (faceBuffer.remaining() > 0) { - AIFace face = faceBuffer.get(); - IntBuffer buffer = face.mIndices(); - while (buffer.remaining() > 0) { - indicesList.add(buffer.get()); - } - } - - float[] vertices = listToArray(verticesList); - int[] indices = listToIntArray(indicesList); - - return new Mesh(vertices, indices); - } catch (Exception e) { - e.printStackTrace(); - throw new RuntimeException("Error loading mesh: " + filepath); - } - } - - public static TexturedMesh loadTexturedMesh(String filepath) { - try { - AIScene scene = Assimp.aiImportFile(filepath, Assimp.aiProcess_Triangulate | Assimp.aiProcess_FlipUVs); - - if (scene == null || scene.mNumMeshes() == 0) { - throw new RuntimeException("Failed to load mesh: " + filepath); - } - - AIMesh mesh = AIMesh.create(scene.mMeshes().get(0)); - - List verticesList = new ArrayList<>(); - List indicesList = new ArrayList<>(); - List texCoordsList = new ArrayList<>(); - - for (int i = 0; i < mesh.mNumVertices(); i++) { - AIVector3D vertex = mesh.mVertices().get(i); - verticesList.add(vertex.x()); - verticesList.add(vertex.y()); - verticesList.add(vertex.z()); - - AIVector3D texCoord = mesh.mTextureCoords(0).get(i); - texCoordsList.add(texCoord.x()); - texCoordsList.add(texCoord.y()); - } - - AIFace.Buffer faceBuffer = mesh.mFaces(); - while (faceBuffer.remaining() > 0) { - AIFace face = faceBuffer.get(); - IntBuffer buffer = face.mIndices(); - while (buffer.remaining() > 0) { - indicesList.add(buffer.get()); - } - } - - float[] vertices = listToArray(verticesList); - int[] indices = listToIntArray(indicesList); - float[] texCoords = listToArray(texCoordsList); - - return new TexturedMesh(vertices, texCoords, indices); - } catch (Exception e) { - e.printStackTrace(); - throw new RuntimeException("Error loading mesh: " + filepath); - } - } - - private static float[] listToArray(List list) { - float[] array = new float[list.size()]; - for (int i = 0; i < list.size(); i++) { - array[i] = list.get(i); - } - return array; - } - - private static int[] listToIntArray(List list) { - int[] array = new int[list.size()]; - for (int i = 0; i < list.size(); i++) { - array[i] = list.get(i); - } - return array; - } -} diff --git a/src/dev/euph/engine/resources/loader/TextureLoader.java b/src/dev/euph/engine/resources/loader/TextureLoader.java deleted file mode 100644 index 1426b50..0000000 --- a/src/dev/euph/engine/resources/loader/TextureLoader.java +++ /dev/null @@ -1,39 +0,0 @@ -package dev.euph.engine.resources.loader; - -import dev.euph.engine.resources.Texture; -import dev.euph.engine.util.Path; -import org.lwjgl.BufferUtils; -import org.lwjgl.opengl.GL11; -import org.lwjgl.opengl.GL30; - -import java.nio.ByteBuffer; -import java.nio.IntBuffer; - -import static org.lwjgl.stb.STBImage.stbi_image_free; -import static org.lwjgl.stb.STBImage.stbi_load; - -public class TextureLoader { - - public static Texture loadTexture(String path){ - int textureID = GL11.glGenTextures(); - GL11.glBindTexture(GL11.GL_TEXTURE_2D, textureID); - - GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL11.GL_REPEAT); - GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL11.GL_REPEAT); - GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR); - GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR); - - IntBuffer width = BufferUtils.createIntBuffer(1); - IntBuffer height = BufferUtils.createIntBuffer(1); - IntBuffer channels = BufferUtils.createIntBuffer(1); - ByteBuffer image = stbi_load(Path.RES + path, width, height, channels, 0); - if(image == null){ - throw new RuntimeException("Failed to load texture: " + path); - }else{ - GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA, width.get(0), height.get(0), 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, image); - GL30.glGenerateMipmap(GL11.GL_TEXTURE_2D); - } - stbi_image_free(image); - return new Texture(textureID, width.get(0), height.get(0)); - } -} diff --git a/src/dev/euph/engine/util/Config.java b/src/dev/euph/engine/util/Config.java deleted file mode 100644 index b15a799..0000000 --- a/src/dev/euph/engine/util/Config.java +++ /dev/null @@ -1,7 +0,0 @@ -package dev.euph.engine.util; - -public class Config { - public static final int MAX_LIGHTS = 6; - public static final int MAX_LIGHT_DISTANCE = 100; - -} diff --git a/src/dev/euph/engine/util/Path.java b/src/dev/euph/engine/util/Path.java deleted file mode 100644 index 241f533..0000000 --- a/src/dev/euph/engine/util/Path.java +++ /dev/null @@ -1,8 +0,0 @@ -package dev.euph.engine.util; - -public class Path { - public static final String RES = "res/"; - public static final String SHADERS = RES + "shader/"; - public static final String VERTEX_SHADERS = SHADERS + "vs/"; - public static final String FRAGMENT_SHADERS = SHADERS + "fs/"; -} diff --git a/src/dev/euph/engine/util/Time.java b/src/dev/euph/engine/util/Time.java deleted file mode 100644 index 720f73a..0000000 --- a/src/dev/euph/engine/util/Time.java +++ /dev/null @@ -1,29 +0,0 @@ -package dev.euph.engine.util; - -import org.lwjgl.glfw.GLFW; - -public class Time { - private long lastFrameTime; - private float delta; - - public void startDeltaTime(){ - lastFrameTime = getCurrentTime(); - } - public void updateDeltaTime(){ - long currentFrameTime = getCurrentTime(); - delta = (currentFrameTime - lastFrameTime) / 1000f; - lastFrameTime = currentFrameTime; - } - - public float getDeltaTime(){ - return delta; - } - - public float getScaledDeltaTime(){ - return delta*50f; - } - - private long getCurrentTime(){ - return (long) (GLFW.glfwGetTime() * 1000L); - } -}